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:
[su_note note_color=”#fafafa”]It’s a very nice and clean way but it’s not the only way. [/su_note]
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 theSitecoreRouteName
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 whereMvcSettings.SitecoreRouteName
is associated with{*pathInfo}
catch-all route. It’s also whereSitecore.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 isscIsFallThrough
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 beforeTransferMvcLayout
and sets theContext.Page.FilePath
so that it points to the layout document file. TheTransferMvcLayout
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("&amp;amp;amp;amp;lt;route-name&amp;amp;amp;amp;gt;", new { scItemPath = "&amp;amp;amp;amp;lt;ID or Path&amp;amp;amp;amp;gt;" }, ... ) { // 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("&amp;amp;amp;amp;lt;route-name&amp;amp;amp;amp;gt;", "&amp;amp;amp;amp;lt;controller&amp;amp;amp;amp;gt;/&amp;amp;amp;amp;lt;action&amp;amp;amp;amp;gt;/{*scItemPath}", new { controller = "&amp;amp;amp;amp;lt;controller&amp;amp;amp;amp;gt;", action = "&amp;amp;amp;amp;lt;action&amp;amp;amp;amp;gt;", 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 trustPageContext
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.ItemOr to be datasource item of the rendering:
... new { scItemPath = Model.Item.ID } // same as Model.Rendering.ItemOr any other item to your liking:
... new { scItemPath = "&amp;amp;amp;amp;lt;ID or Path&amp;amp;amp;amp;gt;" }[su_note note_color=”#fafafa”]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![/su_note][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 withSitecoreRouteName
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-allSitecoreRouteName
route always has the current page in itsParsedRoute
thanks toRouteTable.Routes.GetRouteData()
called by theTransferRoutedRequest
during<httpRequestBegin>
. It does so when searching for a matching route and any URL matches the{*pathInfo}
route.
Pingback: To The Controller And Back. Part 2 – Validation | Jocks to the Core
Excellent blog! Do you have any tips for aspiring writers?
I’m planning to start my own blog soon but I’m a little lost on everything.
Would you suggest starting with a free platform like WordPress or go for a paid option? There are so many
options out there that I’m totally overwhelmed ..
Any tips? Bless you!