Since we can use MVC to build our Sitecore websites, our productivity has greatly improved. But one thing we still have to work around, is that there are some ‘legacy’ aspx/ascx modules our clients like to use. One of the most important, being the webforms for marketeers module (wffm). Solutions have been provided to render MVC controls inside a ASPX template, but it feels wrong that we have to use aspx-master templates just because of some legacy code. We would like to use WFFM inside razor of course!
So, would it be possible to use those legacy usercontrols inside a razor layout? Ofcourse! Sitecore being one of the most flexible CMS’s it has lots of places to hook into the rendering engine. In this blog post, I’ll first take you through the code of this solution, so you can understand how to extend the MVC rendering pipeline to make this possible. Next, I’ll show you how to use the technique in the real world, demonstrating the WFFM module inside sitecore.
The source discussed in the screenshots below, can be found at https://github.com/efocus-nl/sitecore-mvc-usercontrolrenderer
The code
For MVC based websites, in the App_ConfigIncludeSitecore.MVC.config we can find the pipeline <mvc.getRenderer>. This feels like the place we have to hook into. Using dotpeek, resharper or reflector to have a peek at Sitecore.Mvc.Pipelines.Response.GetRenderer.GetViewRenderer, we can see that we have to return an instance of Renderer.
The renderer class, has a method Render receiving a TextWriter
Excellent, this is what we should use to create something that renders legacy UserControls.
So we’ll create a GetUserControlRenderer pipeline step, returning a UserControlRenderer.
The GetuserControlRenderer is the easy part, we’ll have to check if the current item being rendered uses a usercontrol or webcontrol as it’s renderingtemplate. This would look like this:
Our UserControlRender is where the complex stuff happens. The UserControlRenderer creates a new SitecorePlaceholder, passing the current RenderingItem to it, and then calls the RenderView. The sitecoreplaceholder is a class inheriting from System.Web.Mvc.ViewUserControl, a class helping as to render a usercontrol as we would like it. As you can see in the screenshot below, we temporarily change the TextWriter of the current ViewContext to the instance passed by sitecore.
In the SitecorePlaceholder’s Init, we add a form to it’s own children collection. Because usercontrols rely on a <form runat=’server’> being available for it’s postbacks, viewstate, etc, we have to wrap the control inside a form. This means, each usercontrol is rendered in it’s own form, so it will have it’s own viewstate! The caveat to this approach is, having multiple usercontrols on 1 MVC page, will not work exactly as expected: on postback, the ‘other’ controls won’t have their post-data available. This should not lead to too much problems, but keep this in mind when adding multiple usercontrols to one page.
After adding this form, it adds the control we are rendering to the child-collection of that form. It does this by asking the Sitecore.Context.Page.Renderings collection for the correct rendering. The rendering returned has a GetControl method returning the correct usercontrol.
You see we use a ‘SitecoreForm’, this is a simple class inheriting from System.Web.UI.HtmlControls.HtmlForm. The only extra thing it does is marking the current control’s ‘AddedToPage’ property to true (sitecore uses that property to check if all renderings are outputted on the current page).
The OnInit of this placeholder is called when the Page’s OnInit is called, as would be with any UserControl. But hey!, we don’t have Page, now do we! We’re inside an MVC website. Also, how come the Sitecore.Context.Page is initialized with renderings, it would not be ? This magic happens in our RenderView override inside the sitecoreplaceholder. We’ll have to use some evil reflection to get to the correct sitecore methods, but it gets the job done:
The renderview creates an instance of PageHolderContainerPage, a simple class inheriting from System.Web.Mvc.ViewPage. What it does extra, is generating an Id for the child-controls. It then sets this Page instance as the current handler on the HttpContext, to trick sitecore into thinking we’re doing an aspx request. This fooling happens in the InitializePageContext method:
So, here is the evil stuff. We set the private field ‘page’ on the PageContext to our own page instance, and then call the PageContext’s Initialize method if it hasn’t been initialized yet. We check this by checking the Renderings collection as it get’s filled inside the initialize. We also hook into the LayoutPageEvent, to build-up the current context.
After these initializations and build-ups, we can finally call the RenderView() on our page, rendering our content inside our razorview.
That concludes the explanation of this technique. Head over to https://github.com/efocus-nl/sitecore-mvc-usercontrolrenderer, and include that code in your project to make this work.
Using it with WFFM
While you can now place any web- or usercontrol rendering inside a sitecore placeholder, and it would just work, there is something special about using WFFM. Wffm scans the current layout for <sc:placeholder /> tags, and let’s the user choose where to place the new marketing form.
In razor, obviously, we don’t use the <sc:placeholder /> and unfortunately, the module does not let itself fool by adding one in comment. While we probably could hook into the forms-module somewhere, for now, I’ll explain how to place a web form ‘by hand’. This means the ‘Insert form’ button in the Ribbon doesn’t work for now.
On the page you want to add a marketing form, open the presentation details dialog.
Open the device-editor, and click add to add the webformscomponent to your placeholder. Select the RenderingsModulesWeb Forms for MarketeersForm component, fill in your placeholder name and click ‘Select’.
Now, when coming back to the device editor, select your just added component and hit the ‘Edit’ button.
There, in the FormID field, select the form you wish to display on this page
Now, after publishing your changes, you should be able to see a working form on your website.