Sitecore MVC in a multisite environment: area’s

by Chris van de Steeg. 0 Comments

If you are creating a multisite environment using Sitecore & MVC (or any MVC site for that matter), chance is you have to do some weird naming in your controllers to avoid naming conflicts.

This is why Microsoft introduced area’s in their implementation of MVC (as did others before them)

Using them in Sitecore is actually pretty easy, thanks to (again) sitecore’s flexible pipilines.

First, we extend the Initialize pipeline with our own step:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <!-- Loader -->
      <initialize>
        <processor type="BoC.Sitecore.Pipelines.InitializeRoutes, Thieme.Framework" />
      </initialize>
    </pipelines>
  </sitecore>
</configuration>

Then add our class to the project:

using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Sitecore.Configuration;
using Sitecore.Mvc.Configuration;
using Sitecore.Pipelines;

namespace BoC.Sitecore.Pipelines
{
    public class InitializeRoutes
    {
        public virtual void Process(PipelineArgs args)
        {
            var scRoute = RouteTable.Routes[MvcSettings.SitecoreRouteName] as Route;
            var indx = RouteTable.Routes.IndexOf(scRoute);
            AreaRegistration.RegisterAllAreas();
            foreach (var info in Factory.GetSiteInfoList())
            {
                if (info.Properties["mvcArea"] != null)
                {
                    var newRoute = new Route(scRoute.Url, 
                        new RouteValueDictionary(scRoute.Defaults), 
                        new RouteValueDictionary(scRoute.Constraints), 
                        new RouteValueDictionary(scRoute.Constraints), 
                        scRoute.RouteHandler);
                    newRoute.DataTokens.Add("area", info.Properties["mvcArea"]);
                    newRoute.Constraints.Add("sc-issite", new IsCorrectSiteContraint(info.Name));

                    if (info.Properties["mvcNamespaces"] != null)
                    {
                        newRoute.DataTokens["Namespaces"] = info.Properties["mvcNamespaces"].Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                        newRoute.DataTokens["UseNamespaceFallback"] = false;
                    }

                    RouteTable.Routes.Insert(indx, newRoute);
                }

            }
        }

        public class IsCorrectSiteContraint : IRouteConstraint
        {
            private readonly string _siteId;

            public IsCorrectSiteContraint(string siteId)
            {
                _siteId = siteId;
            }

            public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values,
                RouteDirection routeDirection)
            {
                return global::Sitecore.Sites.SiteContext.Current.Name == _siteId;
            }
        }
    }
}

Now what this code does, is to add a route to the routecollection for every Sitecore-site configured. It uses a routeconstraint to validate if the route is applyable for the current request. If it is applyable the property ‘mvcArea’ of the configured website is set as the MVC area for that request. So, we will have to extend the <site> tag of the sitecore configuration with an mvcArea attribute, like so:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <sites>
      <site  mvcarea="Corporate" mvcnamespaces="Corporate.Framework.*,Website.Corporate.*" language="nl-NL" content="web" database="web" scheme="http" patch:before="site[@name='website']" name="Corporate" hostname="coporate.com" targethostname="coporate.com" virtualfolder="/" physicalfolder="/" rootpath="/sitecore/content/Corporate" startitem="Home" allowdebug="true" cachehtml="true" htmlcachesize="10MB" enablepreview="true" enablewebedit="true" enabledebugger="true" disableclientdata="false" />
    </sites>
  </sitecore>
</configuration>

As you can see, we also specify “mvcnamespaces”, although these are optional, it allows you to tell MVC where to look for your controllers.

It’s this easy. So now you’re ready to go and create your controllers & views in the correct manner. Don’t forget to place your views in the correct folder (in this case: areas\corporate\views\)

Usercontrol renderings in a Sitecore MVC website (== wffm for mvc!)

by Chris van de Steeg. 6 Comments

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_Config\Include\Sitecore.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.

image

The renderer class, has a method Render receiving a TextWriter

image

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:

image

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.

image

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.

image

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:

image

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:

image

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.

image

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. image

Open the device-editor, and click add to add the webformscomponent to your placeholder. Select the Renderings\Modules\Web Forms for Marketeers\Form component, fill in your placeholder name and click “Select”.

image

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

image

Now, after publishing your changes, you should be able to see a working form on your website.

image

eFocus crawler based Lucene Websearch for Sitecore (open source!)

by Chris van de Steeg. 2 Comments

If you work with Sitecore 7, you probably know that Sitecore 7 has greatly improved the search capabilities. But still, there is no native way to search through your generated html. There are several paid solutions like the dtSearch module for sitecore, but still, they all feel not to deepily integrated with Sitecore.

At eFocus we created our custom Websearch module, which crawls your website (or any website) and then adds the html content to your Sitecore Lucene index. It’s a total integrated Sitecore solution, so configuring and using it is a piece of cake.

The project uses (a slightly modified version of) the awesome NCrawler for the crawling part.

Go checkout the module at the Sitecore Marketplace or take a look at the source on GitHub