Getting started with Sitecore MVC as an ASP.NET MVC developer

Sitecore introduced MVC back in version 6.6.0 in the middle of 2012. I'm just now getting started with it. Here's a getting started guide from the perspective of an experience ASP.NET MVC developer. Hopefully it's also useful for people who aren't experienced MVC developers.
June 07 2013

I've recently returned to Sitecore development after time away playing in enterprise applications development and then a greenfields ASP.NET MVC 4/WebApi SPA (which was a bunch of fun) .  While I’ve been gone Sitecore has released version 6.6.0 and version 7.0.  Version 6.6.0 was significant in that it includes MVC as a first class citizen.  Sitecore already had an extensible architecture through it’s various pipelines and events, and now with MVC it should be fantastically testable too (Although I’m not going to talk about the testing side of things here and now).

Being very much in the standard ASP.NET MVC mind frame, I at first struggled to mentally model how Sitecore’s MVC worked.  ASP.NET MVC routes a url to an action on a controller, which is responsible for building a (view) model and passing it through to a view.  There’s a 1 to 1 mapping between the url and the controller action.

Things work a little differently with MVC in Sitecore.  Sitecore builds its pages using layouts, sublayouts, and renderings and in the middle of converting an existing page across to MVC I bumped up against wondering just which controller action would be assembling my view.  I got MVC working (just barely) with the base layout but then my mind exploded trying to guess what Sitecore would do with the sublayouts and renderings that can be composed into the main layout. 

Sitecore does something I didn’t expect: it calls multiple controller actions, one for each rendering (when a Controller Rendering is used).  But more on that later.

Initially Sitecore MVC seemed to me to be suffering an identity crisis.  Is it MVC, is it MVVM, is it both?  The answer is yes and more.  It’s all those things and web forms, if you want it to be.

Ok, I’ll admit it’s quasi MVVM (Model-View-ViewModel). Sitecore will use it’s internal default controller when one isn’t specified, so it’s possible to bind a view to a model and have the (view)model do some thinking of it’s own.  This is the method used in the initial posts by John West.   Using this method you’re not explicitly working with a controller, so while it’s MVC under the covers, to me it felt more like MVVM and how I do Silverlight and WPF applications. 

Using this method is easy.  You don’t need to define that extra Controller class, although you do need to create a Model item in Sitecore.  However, it doesn’t provide very well for unit testing, and as craftsman we should be unit testing our code. The experience didn’t sit right with me.

I found Glass Mapper in a watershed moment.  Using Glass Mapper and it’s Windsor support I can compose applications that closely align with what I’m doing in standard ASP.NET MVC sites. That is, defining Controllers and Actions, injecting dependencies into the controllers and using an IoC container (Windsor) to resolve those dependencies, providing a unit testable application. Fantastic.

I spent a couple of days stumbling around converting my existing Layout to this format.  My site has an overall layout that contains a Header, a Footer, and the bits in between.  In other words it’s a site the same as any other on the internet.  In my header I have a contact phone number and a main menu, and in the footer I have a potential list (four lists actually) of links to important pages on my site. 

I store the lists of links in my Home template, allowing the list name and links to be updated by a content author.  I store the contact phone number in my Contact-Us page, using my Contact Us template, which contains the phone number, email, address, and other relevant contact information that would appear on the contact us page. 

In a Normal ASP.NET MVC application this information is in the Master Layout view, simplified by putting each section a partial view getting it’s data from a ViewBag populated by a base controllers OnActionExecuted event.  After a bit of playing around (and some cursing and coffee breaks) I stabilised on a configuration that works and feels “natural”.

My Layout contains no logic, and no custom model.  I inherit my Layout from System.Web.Mvc.WebViewPage (not the generic form).  Whereas in ASP.NET MVC I used @Html.Partial(“MyPartialView”), in Sitecore I create a Controller Rendering and call @Html.Sitecore().Rendering(“/sitecore/layout/Renderings/My Rendering”).  So similar I kick myself for not realising it early.  But it is a learning journey…

Being controller renderings, each rendering has a corresponding controller action where the view model is populated.  I’m following a more traditional ASP.NET MVC interpretation of view models, where my view models are DTO’s that perform no (or almost no) logic – any logic they do perform is presentational.

Here is the header from the existing site in non MVC form.

Mammoth Header 

As mentioned earlier, the phone number in the top right hand is stored on a sub page and I need to retrieve that from that page and add it to my view model.  This is where Glass Mapper comes into it’s own, taking the role played by NHibernate or Entity Framework (or your ORM of choice) in traditional ASP.NET MVC applications.

Here’s a snipped of my controller

public class HomeController : AbstractController
{
    // Can get rid of this when IoC is setup
    public HomeController() : base(new SitecoreContext(), new SitecoreService("master")) { }

    public HomeController(ISitecoreContext context, ISitecoreService service) : base(context, service) { }

    [HttpGet]
    public ActionResult Index()
    {
        var model = SitecoreContext.GetHomeItem<Home>();

        return View(model);
    }

    public ActionResult Footer()
    {
        var model = SitecoreContext.GetHomeItem<Home>();

        return View("_Footer", model);
    }

    public ActionResult Metadata()
    {
        var model = SitecoreContext.GetCurrentItem<IMetadataItem>();

        return View("_Metadata", model);
    }

    public ActionResult Header()
    {
        var home = SitecoreContext.GetHomeItem<Home>();
        var contactUs = SitecoreContext.GetItem<ContactUs>("/sitecore/content/Home/Contact Us") ?? new ContactUs();

        var model = new SiteHeader()
        {
            ContactPhone = contactUs.GeneralPhone
        };

        return View("_Header", model);
    }
}

In the Header method body you’ll see SitecoreContext. That’s provided by Glass Mapper (you really should go check it out if you haven’t already).  Using Glass, I retrieve the data corresponding to the item at the location “/sitecore/content/Home/Contact Us”.  I could have used a GUID too. Think of it as the ID of the document to retrieve, with Glass doing a cast to the ContactUs type.  From there it’s a matter of building my view model and passing it off to the view. This is totally familiar to any asp.net mvc developer.

Here are some of my view models thus far: Home, ContactUs, and SiteHeader.  Home and ContactUs are based on templates in Sitecore (table in my SQL database – to the traditional ASP.NET MVC dev – and my “business entities”) and SiteHeader is a true View Model DTO (data transfer object).

[SitecoreType(TemplateName = "Home Page")]
public class Home : ContentItem
{
    [SitecoreField]
    public virtual string FeatureDocuments { get; set; }

    [SitecoreField]
    public virtual string ListHeading1 { get; set; }

    [SitecoreField]
    public virtual IEnumerable<ContentItem> ListLinks1 { get; set; }

    [SitecoreField]
    public virtual string ListHeading2 { get; set; }

    [SitecoreField]
    public virtual IEnumerable<ContentItem> ListLinks2 { get; set; }

    [SitecoreField]
    public virtual string ListHeading3 { get; set; }

    [SitecoreField]
    public virtual IEnumerable<ContentItem> ListLinks3 { get; set; }

    [SitecoreField]
    public virtual string ListHeading4 { get; set; }

    [SitecoreField]
    public virtual IEnumerable<ContentItem> ListLinks4 { get; set; }
}


[SitecoreType(TemplateName = "Contact Us")]
public class ContactUs : ContentItem
{
    [SitecoreField]
    public virtual string Address { get; set; }
    
    //[SitecoreInfo]
    //public virtual string GeneralEmail { get; set; }

    [SitecoreField]
    public virtual string GeneralPhone { get; set; }

    //[SitecoreInfo]
    //public virtual IEnumerable<StaffMember> Contacts { get; set; }
}

public class SiteHeader
{
    public string ContactPhone { get; set; }
}

 

I will use Glass Mappers fluent mapping capabilities once I get enough bits working and understanding is good.  That will bring me closer to my own comfort zone with FluentNHibernate.  For now, I’m using Attribute mapping, much like you can do with Entity Framework.

 

I setup my Controller Rendering appropriately in Sitecore:

Siteheader

Provided you’ve followed convention and placed your controller into the Controllers folder in your solution Sitecore will be able to find it.  You don’t need to include “Controller” at the end. Convention, remember.

My Razor rendering looks like a typical ASP.NET MVC (surprised?)

@model sitecore.Models.SiteHeader

<div id="heading">   
    <div class="grid_6">
        <a href="/"><img src="/images/logo.png" alt="logo" /></a>                 
    </div>
    <div class="grid_6 push_12">
        <div class="ContactPhone">
            <h3>@Model.ContactPhone</h3>
        </div>
    </div>                       
    <div class="nav grid_15">
        <!-- main menu
            <sc:xslfile runat="server" renderingid="{295AC096-6885-4A03-BA02-EEF5D26E8313}" id="Xslfile2" path="/xsl/Main Menu.xslt"></sc:xslfile>
            -->
    </div>
    <div class="clear"></div>
</div>

You’ll notice I’ve still got old way of doing things in there.  I haven’t got around to converting the menu yet. That’s today’s job!

Hopefully this post contains enough goodness to prevent another developer stumbling around getting a blooded forehead.

I've glossed over a lot other questions developers would have with getting setup with Sitecore, including:

  • How do I setup my Sitecore solution, and
  • How do I work with a team of devlopers on my Sitecore solution,
  • How do I deploy my Sitecore solution.

I aim to answer those questions in the near future :)

Post a comment

comments powered by Disqus