Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sitecore MVC - how to handle multiple forms on page

I've been looking at Sitecore MVC but I'm stuck at how to handle a case where my page has two controller renderings and each contains a form. I want the individual controllers to handle their HttpPost and return the whole page after post.

I've set up a simple example. Both controllers are similar:

public class ExampleController : Sitecore.Mvc.Controllers.SitecoreController
{
    public override ActionResult Index()
    {
        return View("Index");
    }

    [HttpPost]
    public ActionResult Index(string formPostData)
    {
        ViewBag.SaveForLater = formPostData;
        return Index();
    }
}

The views look like this:

@using Sitecore.Mvc
@using (Html.BeginRouteForm(Sitecore.Mvc.Configuration.MvcSettings.SitecoreRouteName, FormMethod.Post))
{
    @Html.AntiForgeryToken()
    var term = ViewBag.SaveForLater as string;
    if (!string.IsNullOrEmpty(term))
    {
        <p>Submitted: @term</p>
    }
    <p>
        @Html.Sitecore().FormHandler("Example", "Index")
        <input type="text" name="formPostData" placeholder="Enter something" />
        <input type="submit" name="submit" value="Search" />
    </p>
}

With this setup both forms submit their data but the returned page consists only of the partial view and not the whole page.

If I replace the line @Html.Sitecore().FormHandler("Example", "Index") with @Html.Sitecore().FormHandler() then the whole page is returned but the post action for both forms is processed.

Neither scenario is ideal. I must be missing something and would appreciate a pointer.

like image 982
Tom Troughton Avatar asked Apr 15 '14 12:04

Tom Troughton


1 Answers

Unfortunately there are multiple ways how you can integrate with Sitecore MVC and Sitecore doesn't provide many best practice examples. One example you can find here.

In our projects we do it a bit different, because we want to use as much as possible the conventions etc. from default ASP.NET MVC. I try to include a complete simple example in this post.

We add two different hidden fields to the form with the current action and the current controller, the view looks like this:

@model Website.Models.TestViewModel

@using (Html.BeginForm())
{
    @Html.LabelFor(model => model.Text)
    @Html.TextBoxFor(model => model.Text)

    <input type="submit" value="submit" />

    <input type="hidden" name="fhController" value="TestController" />
    <input type="hidden" name="fhAction" value="Index" />
}

With this simple ViewModel:

namespace Website.Models
{
    public class TestViewModel
    {
        public string Text { get; set; }
    }
}

Then we have created a custom attribute which checks if the current controller/action is the same as posted:

public class ValidateFormHandler : ActionMethodSelectorAttribute
{
    public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
    {
        var controller = controllerContext.HttpContext.Request.Form["fhController"];
        var action = controllerContext.HttpContext.Request.Form["fhAction"];    

        return !string.IsNullOrWhiteSpace(controller)
            && !string.IsNullOrWhiteSpace(action)
            && controller == controllerContext.Controller.GetType().Name
            && methodInfo.Name == action;
    }
}

The controller actions then gets the new attribute:

namespace Website.Controllers
{
    public class TestController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        [HttpPost]
        [ValidateFormHandler]
        public ActionResult Index(TestViewModel model)
        {
            return View(model);
        }
    }
}

We always return the view resolved by ASP.NET MVC. By convention this is the view with the same name as the action within the folder with the same name as the controller.

This approach works very well for us. If you would like to add the AntiForgeryToken, this also works fine.

like image 145
Kevin Brechbühl Avatar answered Sep 20 '22 03:09

Kevin Brechbühl