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:

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));
    }
}

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

@Html.Sitecore().RenderingToken()

The result of this method looks like this:

<input name="uid" type="hidden" value="c151068e-9bb9-4899-a470-27560f551338"/>

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

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);
    }
}

Decorate POST actions with the ValidRenderingToken attribute:

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

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]

// note: no attribute here
public ActionResult Index()
{
    var model = GetModel();
    return View(model);
}

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

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: