To The Controller And Back. Part 1 – Routing

Martina Welander has recently posted two great articles on POSTing forms with Sitecore MVC (part 1, part 2). I am very excited to see MVC in context of Sitecore being posted about more and more. Here at BrainJocks we fully embraced MVC since the day it came out officially supported in 6.6 and never looked back.

Option 1: SitecoreRouteName

In Martina’s examples forms are created using BeginRouteForm() helpers (on @Html or @Ajax) with the SitecoreRouteName as a route along with a Sitecore’s @Html.Sitecore().FormHandler().

It goes like this:

using Html.BeginRouteForm(Sitecore.Mvc.Configuration.MvcSettings.SitecoreRouteName, ...)
{
    // either
    @Html.Sitecore().FormHandler("<controller>", "<action>")
    // or
    @Html.Sitecore().FormHandler()
}

You can use the parameterless version if your rendering’s definition item defines the form’s controller and action:

image2014-7-17 12-1-51

It’s a very nice and clean way but it’s not the only way.

Before we begin, do you know what exactly this combination does and how it works? Let’s find out.

BeginRouteForm()

When you use the BeginRouteForm() helper to build the form it generates the action the form will submit to. With the SitecoreRouteName the action will point to the current page – the one your rendering is running on (*).

This is important. We will get back to it.

Setup

It all begins with the <initialize> pipeline and the processor that registers default routes. This is where MvcSettings.SitecoreRouteName is associated with {*pathInfo} catch-all route. It’s also where Sitecore.Mvc.Routing.RouteHttpHandler is assigned to all registered routes and this is how MVC request pipelines are attached.

Routing

The routing part actually happens in the <httpRequestBegin> and there are two things we need to look at:

  • Sitecore.Mvc.Pipelines.HttpRequest.TransferRoutedRequest. Just as the name suggests it handles routed requests – requests with a URL matching a route in the MVC routing table. The default route is scIsFallThrough so the pipeline will fall through (i.e. will keep running). That’s because {*pathInfo} route is a catch-all route and at this stage Sitecore is not yet sure what it represents.
  • The next one is Sitecore.Mvc.Pipelines.HttpRequest.TransferMvcLayout that will determine whether the layout document on the current page item is an MVC view. There’s a kernel processor that runs before TransferMvcLayout and sets the Context.Page.FilePath so that it points to the layout document file. The TransferMvcLayout now only needs to see whether the file extension matches its expectations (i.e. .cshtml). It’s where this familiar trace comes from:
    MVC Layout detected - transfering to ASP.NET MVC
    

POST

The <mvc.requestBegin> takes over and will immediately trap the POST with the ExecuteFormHandler processor. It will use scController and scAction supplied by the @Html.Sitecore().FormHandler() and will let your controller take it from here.

PageContext.Current.Item

Your controller can now do its thing and it also has access to the current page item via PageContext.Current.Item. Here’s how it works. Calling into Item property of the PageContext will run the <mvc.getPageItem> which knows where to get the item from. Remember how the BeginRouteForm() helper generated the current page url for the form’s action? The GetFromRouteUrl processor knows how to decipher the URL into the item path for the current site.

Option 2: scItemPath

Like I said in the beginning the SitecoreRouteName is not the only way. What if you need your controller to run in context of a different item, not the current page item? Your form, for example, could be generated by the rendering with a datasource and you would much rather your controller executed in context of that item. Or maybe you have an $.ajax(); that isn’t even attached to a form. Then what?

BeginRouteForm()

Instead of using the SitecoreRouteName you can use your own route and you won’t need the FormHandler but you will need the scItemPath route value:

using Ajax.BeginRouteForm("<route-name>", 
                          new { scItemPath = "<ID or Path>" }, 
                          ... )
{
    // no need to use FormHandler, it will be a "routed" request
}

Just like with the SitecoreRouteName, your route will generate the action for the form but you need to set it up first.

Setup

You need to register your route. If you are running MVC with Sitecore you probably already have your own RegisterRoutes processor injected into the <initialize> pipeline:

RouteTable.Routes.MapRoute("<route-name>", "<controller>/<action>/{*scItemPath}", new
{
    controller = "<controller>",
    action = "<action>",
    scItemPath = "{*scItemPath}"
});

Routing

This will be a routed request and TransferRoutedRequest will abort <httpRequestBegin> once it finds a route to run. Your route.

PageContext.Current.Item

Just like with the SitecoreRouteName you can trust PageContext to do the right thing. This time though, the <mvc.getPageItem> will use a different processor – GetFromRouteValue – that deciphers the {*scItemPath} of your route and knows to treat it as the item path or item ID.

You can set scItemPath to be the current page:

... new { scItemPath = Model.PageItem.ID } // same as PageContext.Current.Item

Or to be datasource item of the rendering:

... new { scItemPath = Model.Item.ID } // same as Model.Rendering.Item

Or any other item to your liking:

... new { scItemPath = "<ID or Path>" } 

Next time I will talk about Model.IsValid and how to properly handle it (and test it) for both traditional and AJAX forms. Stay tuned!
[divider top=”1″]

p.s. @dmo_sc has posted a few very nice visual diagrams about MVC pipelines and contexts.

(*) – To explain how the BeginRouteForm() helper used with SitecoreRouteName generates the action’s URL that points to the current page is TL;DR and rather of an academic interest. In short (and if I am not missing anything), the helper will ask the route to generate the URL and the catch-all SitecoreRouteName route always has the current page in its ParsedRoute thanks to RouteTable.Routes.GetRouteData() called by the TransferRoutedRequest during <httpRequestBegin>. It does so when searching for a matching route and any URL matches the {*pathInfo} route.

Pavel Veller

1 comment on To The Controller And Back. Part 1 – Routing

To The Controller And Back. Part 2 – Validation | Jocks to the CoreAugust 11, 2014 - Reply

[…] part 1 I looked at how Sitecore routes controllers that the forms POST to. Let’s see how you can go […]

Add a Comment

Your email address will not be published. Required fields are marked *

Or request call back