Beware of View Compilation with Helix Features!

Totally sensationalist headline for a small issue but now I’ve got your attention… 😛

View precompilation is cool, it lets us catch errors at compile time, helping us to fail faster and we can avoid the performance hit of compilation at runtime since the views are already compiled. We no longer need to ship the cshtml files resulting in smaller deployments and it can also help solve issues with large numbers of physical view files. 

However I’ve noticed an interesting issue when creating Helix features, in that, in some situations the wrong view will be rendered for a component.

What?

Imagine we have two Helix Features, which output as assemblies FeatureA.dll and FeatureB.dll. They both have components (View or Controller rendering it doesn’t matter) that reference the same view name in their respective projects e.g. ~/Views/Test.aspx.
Despite the fact they are in two different assemblies, with view precompilation enabled, Feature B’s view will always be rendered out to screen when asking for FeatureA:

Controller Action method asks for View A and gets View B

This is obviously less than ideal as when adding Feature B, a developer would not envisage Feature A being affected by their change. The issue would not be caught unless we explicitly test Feature A is still working correctly when deploying Feature B (full regression test perhaps).

This occurs with the latest versions (at the time of writing) of two popular precompilation libraries available on Nuget:

Why?

Diving deep into the StackExchange.Precompilation library… 

When called upon to render a view the view engine code in the StackExchange code loops through all types in each loaded assembly and checks whether any of the types inherit or implement WebPageRenderingBase (which your compiled views will do)

It retrieves the [CompiledFromFileAttribute] from the compiled view class:

namespace ASP {
[CompiledFromFile("E:\\dev\\Sandbox\\Sitecore\\Components\\Test\\TestLayout\\Test.cshtml")] 
public class _Page_Components_Test_TestLayout_Test_cshtml : WebViewPage<TestGlassModel> 
{   
protected HttpApplication ApplicationInstance

This absolute path retrieved from the attribute is clearly unique between assemblies, but unfortunately it is converted into a virtual path, and the uniqueness is lost.

This virtual path is added as the key to a _views dictionary together with the compiled view type as per below:

private readonly Dictionary<string, Type> _views;           
foreach (var view in viewTypes)                
{                   
var attr = view.GetCustomAttribute<CompiledFromFileAttribute>();                   
  if (attr != null)                   
  {
      _views[MakeVirtualPath(attr.SourceFile, sourceDirectory)] = view; // Dictionary value can get overwritten here when MakeVirtualPath generates a string which is already present in the dictionary.                   
}               
}

The loop then continues through the list of assemblies.

The problem becomes apparent when two assemblies have the same generated virtual view path, the assembly that comes alphabetically last wins out and overwrites the value corresponding to the virtual path key string in the _views dictionary.

The Razor Generator code works in a similar way and uses a _mappings dictionary for the views also keyed on the virtual view path and hence exhibits the same problem.

By Decompiling the views we can immediately see the issue with the Razor Generator approach. They have identical virtual views stored in the [PageVirtualPath] attribute so are indistinguishable:

Now having two Helix Features with the same virtual view paths is likely to be a rare occurrence but it’s an interesting observation nonetheless. Its slightly more likely to crop up in those solutions where View compilation has existed in the project for a long period of time. The copying of physical views is not necessary, so the developers will never experience physical view copy clashes in local builds when adding new Features. Its also worth being observant when attempting to moduralise vertically sliced Sitecore solutions where views form part of a Component/Feature folder together with concerns such as the Controller and Viewmodel. Controllers returning a local view may end up with the same virtual path though still highly unlikely as hopefully the folder structure will be distinct enough.

The workaround is to ensure you have different folder paths and or view names for each Feature when using View compilation .

Sample demo

In order to demonstrate the issue with a code sample I’ve pushed a demo to Github which you can download and run with IIS Express.  I’ve removed the dependency on Sitecore for demo purposes since it is not a requirement in order to convey the problem.

Leave a Reply

Your email address will not be published. Required fields are marked *