I’m going to diverge from the Helix-evangelizing trend in favor of some more specific advice on how to approach Sitecore presentation logic. This blog post will help developers with 80% of the most common kinds of programming encountered in Sitecore.
I’ve found that the patterns discussed in this post will greatly help your team with:
- Locating bugs quickly
- Addressing performance problems
- Re-skinning sites
- Maintaining scope for data
- Maintaining scope for logic
This is a proven approach to Sitecore development that has served my teams well, since the introduction of MVC in Sitecore circa 2012. It is framework agnostic. You can use ModelMapper, GlassMapper, Synthesis or nothing but Sitecore Items and get great results.
The Core Pattern
When working with my teams I usually use the above simplified diagram to discuss where code should go. A request invokes the Controller, which then interacts with a Repository. The Repository retrieves Items from Sitecore and returns a ViewModel to the Controller, which then passes it to the View.
In this pattern, responsibilities are crystal clear: The Controller handles the transmission of state to the Repository, the Repository handles interacting with the Sitecore data APIs to generate the ViewModel, which should be ready-for-use by the View. Depending upon which framework you’re using, ready-for-use might even include FieldRenderer support.
One of the most obvious benefits to this pattern is the ability to centralize access to Sitecore data in the Repository. Controllers responsible for different Renderings that require the same Items can share a Repository. Expensive Item queries, or ViewModels that require many queries to generate, can be cached at the Repository level; available across multiple requests. If you use the Sitecore caching APIs, you can even make these cached assets sensitive to Publishing operations.
Utilizing this pattern means you need to favor the use of ViewModels over directly providing Items to the View. In an ideal world, neither the Controller nor the View make any direct reference to the Sitecore namespace. This follows the core concepts of Model-View-Controller:
- The Model should be a data bucket, with zero smarts or logic
- The Model should be exactly what the View needs to render, no more no less.
- The Model should provide data to the View that requires almost no processing. This includes things like sorting.
The Controller’s job is to figure out the context of the request and get the Repository to spit back the correct ViewModel. That Context is going to come from the RenderingContext object provided by Sitecore.
Some assumptions involved in using this Pattern
- Almost all Renderings are Controller Renderings. (The exception is Renderings that need to act like Sublayouts. They can be View Renderings)
- Controllers should have one, and only one Action method. (And to really leverage Sitecore, it should be named “Index”)
- Controllers should get all their context from Sitecore.Mvc.Presentation.RenderingContext.Current
- Any calls to Sitecore.Context in Controllers, Repositories or Views should be considered a violation of the pattern.
- Controllers are allowed access to Sitecore.Context.Site because this value is not exposed in RenderingContext.Current.
- Sitecore.Context.PageMode is allowed in Controllers and Views again, because this is the only source of this particular state.
In this design pattern, it is extremely common to have Repositories injected into Controllers on instantiation. This gives an outstanding way to mock Sitecore access if you’re into Unit Testing, as well as the ability to provide some implementation flexibility in the future. The following snippet shows and example of such a service request:
public MyController(IRepository<ViewModelType> repository)
this.Repository = repository;
Context is Everything
Before we start elaborating, let’s take a minute to discuss how much the context of a Request has an impact on Sitecore, and your Renderings. Consider the following:
- Does the appearance of my Rendering change in Experience Editor vs. what the “anonymous” user sees?
- Does the content I’m rendering exist in more than one Language?
- Do I need to use the ContentSearch API to retrieve Items from a search Index?
- Does this Rendering ask the Author to set a Datasource?
- Does the appearance of this Rendering change from site-to-site in my installation?
- Is the content of this Rendering in-progress or finalized?
Poorly scoped data access is the #1 source of bugs in any Sitecore installation, and the number of occurrences of data scope bugs increases the more compartmentalized the solution becomes. That’s right, Helix will introduce more bugs, because developers lean on things like Sitecore.Context, which isn’t always giving the right answer back.
Guarding against Context Errors
The best way to prevent Context errors from invading your solution is to ensure any code that accesses Database or ContentSearch follows these rules:
- Never assume the Context.Database is available to you, or that it is the correct database for your query.
- Never assume that the Context.Language is available to you, or that it is correct for your query.
- Never assume that the Context.Site is available to you, or that it is the correct site for your query.
- For security & performance reasons, you should always scope your queries to a known Ancestor Item. never start your search from /sitecore.
Any methods on your Repositories that return Items or ViewModels should demand the above facts as arguments. However, you can infer some facts if you’re provided with an appropriate object:
- SiteInfo and SiteContext can provide you with Database, Index, Language, and StartItem parameters for context.
- Accepting an Item as a method argument provides you with a Database and a Language. It can often scope your query as well.
The Controller Controls the Context
A Controller Rendering should retrieve the Context from Sitecore and then pass appropriate context to the Repository and (if necessary) the View. Interesting Context objects that are managed from the Controller include:
- Rendering Item (the definition in Sitecore that referenced the Controller)
- Datasource Item (if set independently, or inferred as Page Item below)
- Page Item (effectively Sitecore.Context.Item)
- Security Domain (seldom used, as Sitecore’s data layer usually handles it)
- Site (only available via Sitecore.Context.Site)
Having discussed the jobs of each member in this design pattern, and the important of context, let’s elaborate on our diagram to include some of these key pieces and expose some opportunities for flexibility.
The ControllerContext Object
Providing another area to remove dependencies on the Sitecore namespace, the ControllerContext provides a Facade around RenderingContext.Current and Sitecore.Context. It exposes the most useful information to the developer in a convenient class that could be mocked during Unit Testing, or injected on Controller instantiation.
The ModelBuilder Object
Allowing a further division of responsibility, the ModelBuilder handles the transformation of Items into ViewModels or ViewModel properties. It could be injected into the Repository to provide some versatility to the Repository’s output without changing the Repository’s core data retrieval process.
Using a ModelBuilder when working with lists of Items requires some thought. It’s not always the appropriate solution:
Can the ModelBuilder accept an IQueryable so that it can efficiently decorate the output of the Repository without incurring a looping penalty?
Can a custom SearchResultItem returned from a ContentSearch query meet the needs of a ViewModel without needing a discrete ModelBuilder?
If a ModelBuilder must be used on a realized result set, can the resulting ViewModel be cached to reduce overhead on the next request to the Repository?
The ViewResolver Object
Often, when I see Controller Renderings in Sitecore, I see the path to the View as a Constant somewhere in code, if not simply a static string directly within the return statement:
return View("viewpath", model);
By delegating the resolution of the View (or at least the View Path) to its own object, we now have the opportunity to create a single Controller that can service multiple views through a single Action, depending upon context. The most promising use case involves a multi-site Sitecore instance where content types are consistent from site-to-site, but the rendered markup differs significantly.
Even More Context!
Consider what opportunities the Sitecore.Context.Device could provide to this design pattern! A single controller action could use the same ViewModel to produce not only HTML output, but JSON for consumption by a rich client, or by a non-browser Internet enabled device. Consider how easy it would be to turn a News Archive page into an RSS feed just by changing the View…
A Few Words about Helix
The design pattern described here is not only Helix compatible, but closes a number of gaps in the overall Helix approach. One could easily imagine a Helix Feature that contains Repositories, ViewModels, and Controllers, but uses a Project-level ViewResolver to apply the correct presentation output at runtime. This flexibility exceeds the default Helix approach where Views are embedded in the Feature itself, and therefore immutable across the Project-level Tenants and Sites in your installation.
What Happens Next?
I’d encourage you to look at your current Rendering code and determine if it would pass compatibility with this design pattern:
- Does it adequately guard against context errors?
- Does it isolate the task of retrieving data from Sitecore with any traffic control logic?
- Does it keep the Views simple?
- Could you mock out the Sitecore parts for unit testing?
Watch this site for further architecture topics and examples.