Sitecore MVC - Multiple Forms


With Sitecore, forms can be presented to the page as either a View Rendering or a Controller Rendering. This article will focus on forms in Controller Renderings.

Sitecore MVC renders the page with different renderings, potentially including multiple controller renderings. In 'pure' ASP.Net MVC a form is always posted to a specific action, which is marked with the [HttpPost] attribute. This is not possible in Sitecore because the page is rendered through the rendering pipeline and not through a single action. While there are a few ways to do this with Sitecore, they get away from the ASP.Net MVC way of doing things.

Two forms on one page is a problem

When two (or more) forms are on a MVC page and one of them is submitted, all available controller actions are evaluated and those decorated with the [HttpPost] ActionMethodSelector will be selected over all other actions. This causes all form submission actions to be handled when we only want to handle the one that was submitted by the user.

Fear not! There is a way to validate each post action to determine if it is the correct action for the current form post.

Create an extension method that will render a hidden input with the uniqueId of the current rendering:

[code language="csharp"]public static class SitecoreHelperExtensions{ public static MvcHtmlString RenderingToken(this SitecoreHelper helper) { if (helper.CurrentRendering == null) return null;

var tagBuilder = new TagBuilder("input"); tagBuilder.Attributes["type"] = "hidden"; tagBuilder.Attributes["name"] = "uid"; tagBuilder.Attributes["value"] = helper.CurrentRendering.UniqueId.ToString();

return new MvcHtmlString(tagBuilder.ToString(TagRenderMode.SelfClosing)); }}[/code]

Call this method inside an MVC form to add a hidden input with the rendering's uniqueId to the form:

[code language="html"]@Html.Sitecore().RenderingToken()[/code]

The result of this method looks like this:

[code language="html"]<input name="uid" type="hidden" value="c151068e-9bb9-4899-a470-27560f551338"/>[/code]

Create an attribute that will check this uniqueId value to make sure that the form posts to the correct action:

[code language="csharp"]public class ValidRenderingTokenAttribute : ActionMethodSelectorAttribute{ public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo) { var rendering = RenderingContext.CurrentOrNull; if (rendering == null) return false;

Guid postedId; return Guid.TryParse(controllerContext.HttpContext.Request.Form["uid"], out postedId) && postedId.Equals(rendering.Rendering.UniqueId); }}[/code]

Decorate POST actions with the ValidRenderingToken attribute:

[code language="csharp"][HttpPost][ValidRenderingToken]public ActionResult Index(FormModel model){ model.Message = "Valid rendering token found here"; return View(model);}[/code]

Ensure that the default actions for your controllers do not have the [HttpGet] attribute, as this will confuse the Sitecore rendering pipeline and an error will be thrown indicating that it cannot find an action that permits a post. It either needs an unmarked action or an action with [HttpPost]

[code language="csharp"]// note: no attribute herepublic ActionResult Index(){ var model = GetModel(); return View(model);}[/code]

For information on Sitecore Forms see Martina Welanders article on Posting Forms in Sitecore MVC: Part 1 - View Renderings and part 2 - Controller Renderings.