Friday, 20 June 2014

Umbraco, MVC and Strongly Typed View Models

I've recently returned from Codegarden, the annual Umbraco conference in Copenhagen. It was my second time there, and as previously had a great time and learnt a lot about the CMS and the various ways people are using it.

There was a definite theme around many of the technical sessions, including the one I was asked to give, around the idea of strongly typed models and best practices and techniques for using them.

Initially I was perhaps naively hoping that in these conversations with other developers tackling these issues, we'd might come to a consensus as to the best approach. Not surprisingly that didn't happen, and actually if anything the picture is changing further due to some interesting work on the core that Stephan presented. But that's no bad thing, options are good and it's clear that different developers and teams have different priorities in this area.

Having mulled it all over a bit on the way home, seems to me we have (or soon will have) six (yes, six!) general approaches we can take in this area of using MVC patterns with Umbraco. There's a kind of continuum that ranges from working solely in the views, through to full route hijacking and mapping to view models - and teams can pick and choose where they are most comfortable along this line.

Here's my take on these approaches:

Views Only

With this method the developer simply works direct in the views, using surface controllers only for the things they are necessary for like rendering child action partials and processing form posts. All property field rendering, querying of the node tree and even Examine searches can be carried out here.

Advantages:
  • Easiest way to get started for people new to Umbraco and/or MVC
  • Accessible to developers who don't work within Visual Studio
Disadvantages:
  • It might be using MVC rendering, but it's not really MVC in terms of the architectural pattern. The views with this approach have too much logic and resonsibility. For larger applications this is likely to be hard to maintain.
  • Not so easy for shared teams with front-end developers unfamiliar with Umbraco APIs. Simply hard to see the HTML from the C#.
  • Likely to run into issues with dynamics, which although initially easier to write and understand, break down when it comes to more complex tasks - as demonstrated in Iluma's talk at Codegarden.

Surface Controllers and Child Actions

With this method the main view has little code other than an @Html.Action reference to a surface controller action method. This method returns a strongly typed partial, based on a view model that can be custom created for the required display.

Advantages:
  • Gives us strong typing and the ability to provide our own view model
  • Improved separation of concerns
  • No need for creating controllers for route hijacking
  • Can be used to extend the views only approach perhaps just for more complex pages that might benefit from using a customised view model - no need for all or nothing.
Disadvantages:
  • More complex request cycle: view --> controller action --> partial and so less obvious exactly what code is where when it comes to maintenance

Custom View Models that Inherit from RenderModel

This is the approach taken by the Hybrid Framework, so called because it allows you to extend the existing Umbraco model that is passed to the view with your own additional properties and collections. Route hijacking is used too, where the node querying is carried out and a collection passed to the view via the view model. Because the view model extends default RenderModel though, you can still use it's methods like GetPropertyValue as you like.

Advantages:
  • Provides a proper MVC structure with controllers and views having their own defined responsibilities.
  • The hybrid approach means you don't have to go all the way to creating a populating a custom view model if you prefer not to.
Disadvantages:
  • We still have some view code that could be simpler to work with

Code Generated Content Models

This is based on a new feature demonstrated at the conference, the ability to code generate strongly typed classes based on the data in your document types. I definitely need to dig into this more to understand the full details, but the idea is that having generated these classes using a custom tool, instead of getting a working with an IPublishedContent in your views - you'll get an instance of a strongly typed content model class instead.

Because the generated classes are partial, you can augment them with your own properties and methods.

Coupled with this, another new feature is being developed to allow queries to be referenced in the views but stored elsewhere in code. This would considerably clean up an approach where querying is done in the views, as the code would not need to include all the usual LINQ statements here, rather the query would be accessed via a syntax along the lines of @foreach var item in Umbraco.Queries(alias).

Another tool that supports this pattern is Umbraco CodeGen. This package also demonstrated at Codegarden piggybacks on the XML files generated by the uSync package to provide a mechanism to both generate strongly typed models from your document types, and to make changes to your document types following amends to the code.

Advantages:
  • Looks like this will be a great technique for cleaning up views and giving front-end developers an easier model to work with.
  • Will be supported in the core, meaning familiarity with the approach is likely to become fairly widespread around the Umbraco community.
Disadvantages:
  • In MVC applications, there's often a distinction made between the domain model and the view model. The former is your application entities that are generally closely affiliated with your persistence mechanism (usually, a database). The latter are a code representation of the particular data for a view.

    It's common to treat these things quite separately, and utilise a mapping layer when translating from one to the other. In Umbraco, our domain model is Umbraco itself - the database of content and the document and data types - so it could be argued that these content models are really another representation of this. It will be easier to work with being strongly typed rather than instances and collections of IPublished content, but as of itself it's not strictly a view model.

  • Whilst these content models could be used in views directly quite comfortably when it comes to property values, for queries it's not so obvious. You would either be left continuing to query in the views, or use the Umbraco helper queries collection as noted above.

It's important to note though that once the latter is available, the combination of the content models and the queries collection will offer a way to have very clean views with little logic. Even if strictly a view model isn't being used, this may well be enough to solve the "too much logic in the views" problem for most people - and possibly me too once I've played around with it a bit more.

POCO View Models and Mapping

This approach is my favoured at the moment. It is based on creating a pure view model for each view, and using route hijacking to intercept requests and construct these view models. The different in approach from the Hybrid Framework though is that these view model classes don't inherit from Umbraco's RenderModel - they are simple POCOs with no dependency on Umbraco at all.

Unless there's a need to retain access to the Umbraco helper in the view - perhaps for accessing Dictionary values - the view can be strongly typed direct to the view model, using the @model syntax. And as such front-end developers needn't be working with Umbraco APIs at all, just intellisense discoverable properties and collections.

The mapping approach itself from Umbraco's IPublished content to the view model can be simplified drastically by using the Umbraco Mapper package - in the same way that AutoMapper works between C# classes, this package has been designed to use conventions with configurable overrides to flexibly map from Umbraco content to view model instances.

Advantages:
  • This pattern utilisises MVC best practices, with Umbraco "domain models" mapped to custom view models for each view, leaving very keen view code with little or no logic
Disadvantages:
  • There is a little more work involved to firstly create the view model and then handle the mapping, though the Umbraco Mapper package takes away a lot of the grunt work around this.

Code Generated Content Models With Mapping

This approach is really combination of the previous two, taking on board the point that the code mapped content models are not strictly view models, and may need augmenting with other information drawn from elsewhere in the node tree or even other sources.

So we would as in the previous scenario create our own view models in addition to the generated content models, and use mapping to transfer data from one to the other. In this case though, rather than using the Umbraco Mappper package, given we are mapping between class instances, we can utilise AutoMapper - a well established component for these types of operations.

Advantages:
  • Another pattern that utilisises MVC best practices, with Umbraco "domain models" mapped to custom view models for each view, leaving very keen view code with little or no logic
Disadvantages:
  • There is a little more work involved to firstly create the view model and then handle the mapping, though the AutoMapper component takes away a lot of the grunt work around this.

Conclusions

Having arrived at CodeGarden to talk about three methods for working with MVC in Umbraco, I've come away with six! So is that progress? Not sure... but personally I look on this a positive thing, that there are options for teams to select between and decide what works best for them.

I'm looking forward to reviewing and experimenting with the new features coming out of the core and considering how best they fit with my team's preferred ways of working. I'm sure others will be doing this same as there was a lot of discussion on this topic this year.

1 comment:

  1. thanks, helps some of us fill the gaps as i wasn't at code garden :( , will go through the article in more detail.

    ReplyDelete