I have set up a document type in Umbraco, and have created a custom controller & model for this document type.
The custom controller inherits from : RenderMvcController class and the views inherit the model through #inherits Umbraco.Web.Mvc.UmbracoViewPage<com.something.model>
This all works fine for any HttpGet requests. However as soon as I want to do a form post back to the controller using #using (Html.BeginUmbracoForm("SomeAction", "SomeController", null, new { #class = "some-class" }))
I get the following error message: Could not find a Surface controller route in the RouteTable for controller name SomeController
From all the documentation that I was able to find it always refers to SurfaceControllers when it comes to form posts. Is there a way to change the routing so that it would post to my custom controller, rather then another controller that inherits from the SurfaceController class?
If you are going to post the form in this way, you need two controllers. One for the Document Type that inherits from MvcRenderController (as you already have) and a second which inherits from the SurfaceController.
The surface controller just needs a single POST action that does one of the following things:
// e.g. if modelstate is invalid
return CurrentUmbracoPage();
// redirecting back to original page after a successful post
return RedirectToCurrentUmbracoPage();
// Redirecting to another page after a successful post
return RedirectToUmbracoPage(12345)
This has been taken from the documentation here: http://our.umbraco.org/documentation/Reference/Templating/Mvc/forms
Strictly speaking the initial document type controller is not necessary in this scenario as it does not play a part in the post of the form.
If you want to post directly to the custom controller then you should not use Html.BeginUmbracoForm, you should just post directly to the current URL. The complication here is that it is then a little tricky to bind your model as a parameter of the post action. Your view model will have to inherit from RenderModel in the following way:
public class BaseModel : RenderModel
{
public BaseModel() :
base(UmbracoContext.Current.PublishedContentRequest.PublishedContent) { }
}
According to http://our.umbraco.org/forum/developers/api-questions/38662-(v6)-Could-not-find-a-Surface-controller-route-error?p=0,
SomeController Needs to inherit from SurfaceController, not RenderMvcController.
Alternatively, you could create a dedicate route and point it directly to your controller:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute("someController", "some/someAction",
new { controller = "Some", action = "SomeAction" });
}
}
Related
I want to remove controller name from URL for specific Controller.
My Controller name is Product
I found some link to do this
Routing with and without controller name
MVC Routing without controller
But all the above links done in route config file. and those are affecting other controller too. I want to do it using Attribute Routing.
Can it is possible? As I want to do this for only Product controller.
I have tried to do it on action like this
[Route("Sample/{Name}")]
but it is not working.
Gabriel's answer is right, however, it can be a bit misleading since you're asking for MVC and that answer is for Web API.
In any case, what you want is to put the annotation over the class definition instead of an action method. MVC example would be like:
[RoutePrefix("SomethingOtherThanProduct")]
public class ProductController : Controller
{
public ActionResult Index()
{
...
return View();
}
}
I'm also dropping this as an answer since you may find the following article helpful: [Attribute] Routing in ASP.NET MVC 5 / WebAPI 2
Make sure you set the RoutePrefix attribute on the whole controller class, as well as using the Route attribute on the action.
[RoutePrefix("notproducts")]
public class ProductsController : ApiController
{
[Route("")]
public IEnumerable<Product> Get() { ... }
}
I am developing a website that has a View called _MainPage in View\Shared folder that other views use that for basic layout of my website, so I have a section in this view that populates data from database in fact this section is latest news of the site and I should show latest news in this section ,in simple way I should make section for example called latestNews :
<ul class="news-list">
#RenderSection("latestNews",required:false)
</ul>
and in each views I should fill this section by razor that data came from relevant controller :
#foreach (var item in Model.News)
{
<div>
<p>#Html.Raw(item.Body)<br />ادامه مطلب</p>
</div>
}
in fact I have latest news in every views in footer of my pages.
now my question is : How can define a custom controller for my View (_MainPage) that haven't do these routine in each view. Is there any generic way to accomplish that?
Any public method in a controller class is an action method. Every action method in a controller class can be invoked via an URL from web browser or a view page in application.
Your scenario is some dynamic information (data) need to displayed on couple pages in your application. Generally we end up in adding that data to model passed to views by action methods. We duplicate same data in multiple models where we brake DRY (Don’t Repeat Yourself) principle.
To handle this scenario ASP.NET MVC provides child action methods. Any action method can be child an action method but child actions are action methods invoked from within a view, you can not invoke child action method via user request (URL).
We can annotate an action method with [ChildActionOnly] attribute for creating a child action. Normally we use child action methods with partial views, but not every time.
[ChildActionOnly] represents an attribute that is used to indicate that an action method should be called only as a child action.
[ChildActionOnly]
public ActionResult GetNews(string category)
{
var newsProvider = new NewsProvider();
var news = newsProvider.GetNews(category);
return View(news);
}
Above child action method can be invoked inside any view in the application using Html.RenderAction() or Html.Action() method.
#Html.Action("GetNews", "Home", new { category = "Sports"})
(or)
#{ Html.RenderAction("GetNews", "Home", new { category = "finance"}); }
Instead of a section, you can use Html.RenderAction to specify a controller and an action. The action should return a partial view which is then integrated within the calling site.
I have two controllers named as forms and mobile, In the forms controller I had a return statement calling a action in mobile controller like below:
public class formscontroller: customcontroller
{
public ActionResult submit(int? id = null, string jsString = null)
{ code code
if (CSConfigurationMgr.IsMobileUrl(Request.UrlReferrer.AbsoluteUri.PathFromURL()))
return RedirectToAction("home", "mobile", new { success = true });
return code
}
}
home is an action
public ActionResult home(string res)
{
code code
}
But I am not able to hit that function, it is directly taking me to the base controller of forms, which is named as custom.
Is there a work around?
It is possible that "condition" never evaluates to true.
Please check that and ensure that the code execution gets to that point.
Might be more helpful if you post both controllers here (if they aren't too large).
It seems like a routing problem to me.
Maybe you haven't defined a route to your mobile/home/res controller and then the routing engine is redirecting you to your default one param route?
Assuming home is the action on the mobile controller, then perhaps your signature is incorrect on the home action because it declares a string argument and you're sending a boolean.
I'm putting together a wizard in mvc3/c#. I have a model setup roughly
public interface IStepView {}
public class Step1View : IStepView {}
public class Step2View : IStepView {}
I have a parent view which displays 1 of 2 partial views for these steps.
I would like the form submission for Step 2 to use a custom action on the same controller. Reading similar posts it seems what I need to do is add a custom route like so
RouteTable.Routes.MapRoute("Step2Route", "", new { controller = "Demo", action = "MyAction" });
which I wire together on the Main.cshtml
#using (Html.BeginForm())
{
#Html.BeginRouteForm("Step2Route", new { controller = "RolloverController", action = "Stuff" })
// and so on for each Step I want to use a custom action
}
Is this the way to do it?
If you didn't know the number of steps in the wizard, a custom route might make sense to allow for tracking of progress in the wizard.
i.e. Wizard/{WizardName}Step/{StepNumber}/
But since it looks like you do know the steps, your actions on the controller can correspond to them without custom routes:
i.e RegistrationWizard/EnterInfo, RegistrationWizard/Confirm, RegistrationWizard/Success
Create a get and post method for each action on the controller. Take the same model and pass it along using RedirectToAction or store it in session, so that you keep track of the changes that the user is making to the data before committing the data on the final step.
I am trying to build a functionality where I need to a create a candidate's profile in our application. There are two steps/UI's to create a candidate's profile:
1 - Create template...where the user enters candidate's information.
2 - Preview template...where the user will be shown a preview of how their profile would look like once they add the profile to our system.
I have already created the views to support these UI's via a controller called "CandidateController" which contains few action methods:
1- [HttpGet] "Create" that returns a Create template.
[HttpGet]
public ViewResult Create()
2- [HttpPost] "Preview" that returns a Preview template.
[HttpPost]
public ActionResult Preview(ProfileViewModel viewModel)
Now what I need to implement is to have a button/link in the Create template that would call the action method [HttpPost] Preview in the controller.
Challenge
I am also wondering if there is a way that the model binder would load the ViewModel object for me if am able to call the HttpPost Preview action method from the first create template.
I am looking for a suggestion/help to how to best achieve this kind a functionality.
Any help will be deeply appreciated.
Challenge I am also wondering if there is a way that the model binder
would load the ViewModel object for me if am able to call the HttpPost
Preview action method from the first create template.
You could use either a standard form or an AJAX call to invoke the Preview POST action and pass all the property values of the view model then. All the values you pass in this request will be the values that will be bound by the default model binder. Here's an article explaining how the default model binder expects the parameters to be named for more complex structure such as lists and dictionaries.
Example with AJAX:
$.ajax({
url: '#Url.Action("Preview")',
type: 'POST',
data: { Prop1: 'value 1', Prop2: 'value 2' },
success: function(result) {
// TODO: do something with the result returned from the POST action
}
});
If you don't want to use AJAX you could use a standard form with hidden fields:
#using (Html.BeginForm())
{
#Html.Hidden("Prop1", "value 1")
#Html.Hidden("Prop2", "value 2")
...
<button type="submit">Preview</button>
}
OK so here are the options that I had to get around:
As Darin suggested you may go with the unobtrusive way by using $.ajax(options), however the thing is you might want to go this way only if you want to do a partial page update or if you want to work on updating/dumping new html in the same view.
And if you don't want to use the Ajax, instead of using Hidden fields, you can simply use the TempData property in MVC, this is how I implemented my targeted functionality using TempData. p.s.below...
[HttpPost]
public ActionResult Create(ViewModel viewModel)
{
this.TempData["profile"] = viewModel;
return RedirectToAction("Preview");
}
public ActionResult Preview()
{
if (TempData["profile"] != null)
{
return View((ViewModel)TempData["profile"]);
}
// Handle invalid request...
return null;
}
So, this solution worked pretty well for me, where I did not write any JavaScript or unnecessary HTML. AND thanks Darin for directing me to a starting point.