How And Why We Do Umbraco MVC

As a .NET developer the introduction of MVC in Umbraco was a huge step in the right direction for us, so we dived head first into the wonderful world of Umbraco MVC.

We had already switched most of our code over to Razor in macroscripts, but having to go through a masterpage for template was kinda redundant and old fashioned. However with MVC we could realize a long time dream of leaving all that behind and stick with our good buddy Razor!

With MVC comes a pattern of separation of concerns. Which of course is one of the main reason why we love MVC, this allows us to completely separate the views from the logic.

STORY TIME!

We started out as pioneers into the Umbraco MVC immediately upon release.

As this was in the early MVC releases there wasn't a whole lot of documentation on the subject.

We tried a lot of things trying to create custom controllers, including the black arts of custom routing in the early Umbraco MVC days.

http://our.umbraco.org/forum/developers/api-questions/38048-Umbraco-411-MVC-Custom-Routing-Content-is-null-How-can-I-load-content?p=1

However, in time, there was this one link from our lovely Umbraco HQ, leading us on a righteous path.

We used this to create our first project in Umbraco MVC.

http://our.umbraco.org/documentation/Reference/Templating/Mvc/custom-controllers

It was built around a BaseRenderModel which inherits the RenderModel and was used in the layout.

And a BaseController which inherits the Umbraco.Web.Mvc.RenderMvcController, setting all the global stuff, like Navigation bars, Google analytics script and SEO parameters.

Then all we had to do was always to inherit from those.

It worked just fine, but we wanted more!

Clean Views!

The RenderModel gave full access to Umbraco methods and as such we could still do all the logic in the view.

Now we really wanted a clean separation and being able to dig directly into Umbraco from the view, violates that. So we grabbed our pirate hat and went into uncharted waters! Yarr!

What we did was creating a View with a custom Model, only containing the data that the View needs.

With this we now had clean and strongly typed Views that only knew about the Model being passed, just like regular MVC.

With clean Views the next stop to a great coding standard in MVC is, in our humble opinion, Don't Repeat Yourself (DRY) code. With stuff like News being shown on more than one page we found ourselves writing some of the same code and some of the same property aliases more than once. So we created a Mapper for each DocType so we could just call the mapper and get a model back filled with the data we needed. This way, if we ever needed to change the DocType, we only had to update the mapper.

This was also very easy with inherited doctypes as their model just inherited exactly as they do in Umbraco and then pass them through the mappers needed.

Our dear colleague Nick Frederiksen made a blogpost on this back in May.

http://ndesoft.dk/2013/05/25/how-to-create-a-real-mvc-app-using-umbraco/ 

How we do it now

After a few projects we realized that we had left our Controllers barren and moved all of that code to the mappers. So we refactored it a bit, still having a clear definition of when to use what.

So now we've moved the code back in the Controllers, unless the data is on more than one page or if the DocType doesn't have a template, then we make a mapper. We keep the DRY principle of only having the code in one place. So if you at some point need an additional property on your news, you only have to change it a single place, regardless of how many pages you use it in. The same concept goes for partial views!

It is a relatively small change we made, but Nick did make a blog post about the change as the first one generated a lot of debate.

http://ndesoft.dk/2013/08/10/real-mvc-app-using-umbraco-revisited/

Can we do it better?

There is generally always room for improvements so naturally we haven't stopped trying to improve this and neither should you. Pick up the telescope and look in the horisont!


As it is now we always only have one model for each DocType, however sometimes we use the DocType somewhere else and only need a few of the properties on that page.
For example we have an article page and want to pull the top 3 articles into the frontpage. However on the frontpage we only want a small teaser text and a headline, not the whole bodytext nor the images and what else might be attached to the article.

So here we ideally want a smaller viewmodel just with the properties that we need in the given context.

While this isn't so difficult, we simply create another model and map it in either a mapper or in the controller depending on the amount of places it is used. There is one glaring issue.

Our property alias, those pesky magic strings, are now in more than once place.

It is still up for debate internally but the proposed solution is to replace the mapper class with a DocType class.

It would look something like this.

public static class ArticleDocType
{
	public const string TeaserPropertyAlias = "teaser";
        public const string HeaderPropertyAlias = "header";
        public const string BodyTextPropertyAlias = "bodyText";

        public static T Map<T>(IPublishedContent content, UmbracoHelper helper, T model)
            where T : ArticleModel
        {
            model.Header = content.GetPropertyValue<string>(HeaderPropertyAlias);
            model.Body = content.GetPropertyValue<IHtmlString>(BodyTextPropertyAlias);
            model.Teaser = content.GetPropertyValue<string>(TeaserPropertyAlias);
            
            return model;
        }

        public static ArticleModel Map(IPublishedContent content, UmbracoHelper helper)
        {
            return Map(content, helper, new ArticleModel());
        }


        public static IEnumerable<ArticleModel> Map(IEnumerable<IPublishedContent> contents, UmbracoHelper helper)
        {
            return contents.Select(x => Map(x, helper)).WhereNotNull();
        }
}

The DocType class will still be a static class with all the mappers regarding that specific DocType, just like the old mappers. The addition is constant strings for all the property aliases.

Then in any controller or in a mapper on this class we can reference those constants, keeping the property aliases in check.

So, is this the final revision?
Probably not, we always want to improve on the way we code and as Umbraco MVC gets a bit more established best practices will settle. So this is our, current, proposition for a best practice in Umbraco MVC. As always this is completely open for debate for anyone so if you have anything to add or comment, you are more than welcome to hop aboard!

But why go through all this trouble?

It might seem like a lot of work right off the bat, but once you get into the flow it makes things a lot easier. So we'll summarize some of the positive things about it.

  • Code is not being compiled at runtime. Because of this you will notice code mistakes faster because your solution simply won't build.

  • You get Unobtrusive validation out of the box with data annotations in the models!
    Having been used to the ASP.NET validators and custom JavaScript this is a huge deal. Being able to slam a dataannotation on the model properties and having the validation "Just Work!". That is truly amazing.

    All you have to do is add these two lines to your appSettings in the config file.

    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />

    We did run into a small speedbump as we generally have validationmessages in the dictionary for multi-lingual sites. The best way we found around this was to create our own data annotations and have them inherit from the original and extend them with a dictionary name.

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
    public class UmbracoRangeAttribute : RangeAttribute, IClientValidatable
    {
    	public UmbracoRangeAttribute(int minimum, int maximum, string dictionaryKey)
    		: base(minimum, maximum)
    	{
    		this.ErrorMessage = dictionaryKey;
    	}
    
    	public override string FormatErrorMessage(string name)
    	{
    		return umbraco.library.GetDictionaryItem(base.FormatErrorMessage(name));
    	}
    
    
    	public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    	{
    		// Kodus to "Chad" http://stackoverflow.com/a/9914117
    		var rule = new ModelClientValidationRule
    		{
    			ErrorMessage = umbraco.library.GetDictionaryItem(this.ErrorMessage),
    			ValidationType = "range"
    		};
    
    		rule.ValidationParameters.Add("min", this.Minimum);
    		rule.ValidationParameters.Add("max", this.Maximum);
    		yield return rule;
    	}
    }

    Rangevalidator Example

    A quick guide and walkthrough of this can be found here.

    http://ndesoft.dk/2013/04/20/how-to-make-localized-models-in-umbraco-mvc/

  • Also with the MVC and the MVC routing, creating forms and Ajax posts and calls is insanely easy. Most of the time you won't even have to write any JavaScript. If needed it is very easy to extend it with OnCompleted events etc.

    @using (Ajax.BeginForm("Order", "ShopSurface", new AjaxOptions() { HttpMethod = "POST", OnSuccess = "OnSuccess", OnBegin = "Showoverlay" }))
    {
       @Html.Partial("_LibaryInfo", Model.Library)
       <input type="submit" value="Order now"/>
    }

     

  • Keeping the code DRY makes the code much easier to maintain. Because you won't have to search through the whole solution for each small change.

  • Last but not least. This is one your boss will love! Having a better separation of code makes it easier to divide the tasks and work multiple people on the same project, speeding up the development process. For example being able to mock the model before the controller is completed. Thus making it possible for a developer to create the view without the logic being done.

Now we know there is a steep learning curve for MVC, but we firmly believe it is a more efficient way of working with the web.

That being said, we are not saying this is the only way of creating an Umbraco MVC site, but it helped us immensely and hope it can help you create even better Umbraco sites than you did before!

Merry Christmas & Happy coding!

Kasper Julius Holm

Kasper is on Twitter as

Niels Ellegaard

Niels is on Twitter as