Sitecore MVC Independent Experience Editor Views | Microsoft
Microsoft Blog

Sitecore MVC – Independent Experience Editor Views

I find that many of the components I develop in Sitecore need custom markup in the Experience Editor that shouldn’t be present on the live site. Although it’s possible to add this custom markup into my components’ views with @if (Sitecore.Context.PageMode.IsExperienceEditor), I prefer to keep branching logic out of my views as much as possible.

Fortunately Sitecore is built on top of ASP.NET MVC, so it’s easy to create a custom view engine that will serve up different views for our components in the Experience Editor. In this article I’ll demonstrate a custom view engine that will render views suffixed with .EE when users are in the Experience Editor.

Create the View Engine

The first step is to create the ExperienceEditorViewEngine. I’ve chosen to implement this as a RazorViewEngine decorator so I can easily apply it to other view engines in my applications.

Here’s a breakdown of the view engine:

The MVC framework will call FindView and FindPartialView to find views and partial views, respectively. If users aren’t in the Experience Editor, a NullViewEngineResult will be returned and the next view engine in the view engine collection will continue the search; otherwise, the view name or partial view name will be transformed into the Experience Editor view name (e.g., MyView.EE) and the underlying view engine will search for it. If the view is found, it’ll be returned; otherwise the next view engine in the view engine collection will continue the search.

The MVC framework will call ReleaseView to release views after they have rendered.

Returns true if the user is in the Experience Editor, false otherwise.

When MVC is unable to find a view, it throws an exception that notifies you that the view could not be found and lists all of the locations searched, like in the image below.

View Search Locations

ViewEngineResult has two constructors: one that takes a view when a view is found, and one that takes a collection of searched paths when a view is not found. If a view is not found by any view engine, all of the searched paths will be unioned together and returned in an exception. Since the ExperienceEditorViewEngine isn’t going to do any work when users are not in the Experience Editor, this method just returns a ViewEngineResult with no searched paths.

In MVC you can lookup views by specifying just the view name, e.g., MyView, or by specifying the application-relative path, e.g., ~/Views/Example/MyView.cshtml. If the view name has been specified as an application-relative path, .EE will be added just before the extension, e.g., ~/Views/Example/MyView.EE.cshtml. If the view name is not an application-relative path, .EE will be appended onto the view name, and the view engine will look for MyView.EE.

Credit to our Sitecore Practice Architect and resident Regex Wizard, Jon Upchurch, for the regular expression to append .EE on to application-relative view paths. Credit to the ASP.NET Core MVC developers for the IsApplicationRelativePath code.

Register the View Engine with MVC

Once you’ve created the view engine, create a pipeline processor to register the view engine in your application like so:

As I stated previously, I chose to implement the ExperienceEditorViewEngine as a decorator so it could wrap other view engines in the application. The RegisterViewEngines method will create  decorated versions of view engines that are already registered in the application and add them to the top of the collection in the same order as the originals.

The order in which view engines exist in the collection is important–the first view engine to find a matching view wins. Since some of our components will have two views, Component.cshtml and Component.EE.cshtml, it’s important that if we’re in the Experience Editor our ExperienceEditorViewEngines run before the other view engines.

For example, consider a scenario where you have two view engines in your view engine collection:

  1. GnarlyViewEngine
  2. TubularViewEngine

After RegisterViewEngines executes, the view engine collection will be as follows:

  1. ExperienceEditorGnarlyViewEngine
  2. ExperienceEditorTubularViewEngine
  3. GnarlyViewEngine
  4. TubularViewEngine

Now when users are in the Experience Editor, experience editor views will be searched in the exact same order that regular views would be searched when not in the experience editor. For components that don’t have an experience-editor view, the normal view engines will still be in the collection to find non-experience-editor views.

Patch the View Engine Initializer into the Sitecore Pipeline

Patch the pipeline processor right after Sitecore.Mvc.Pipelines.Loader.InitializeRoutes:

As long as your view engines are registered in the initialize pipeline, it’s not really a big deal where you patch your processor in. However, Sitecore adds some custom view paths to the out-of-the-box RazorViewEngine in the InitializeRoutes processor, so I like to register my view engines right afterwards.

Demo Time

With the view engines registered, you can test drive the new functionality. As a demo, I’ve created a tile wrapper component that has three placeholders. In the Experience Editor, a dotted border will be drawn around the placeholders to make them easy for content authors to locate; on the live site the borders will not be present (note: I am not advocating this as a valid scenario for separate views, this is just for demonstration purposes).

The controller and action method are simple:

In my Views folder I’ve created the two separate views:

Separate Views for Component

Now when I navigate to a page with my TileWrapper component, a different view is rendered in the Experience Editor and on the live site:

Experience Editor View

Notice how the TileWrapper on the left has a dotted border around the placeholders whereas the border is not present on the right.

Let me know in the comments if this custom View Engine helps you in your projects or how you’ve extended it to fit your own needs. Good luck!

Update (June 5, 2016)

I discovered an issue with the ExperienceEditorViewEngine code above that can cause Experience Editor views to never be rendered in the Experience Editor on content management servers. To avoid this issue, I’ve updated the code to force the ExperienceEditorViewEngine to always call into its underlying view engine with the useCache parameter set to false in the FindPartialView and FindView methods.

When MVC does view look ups, it first has all of the view engines look up views using their view location cache; if no view is found, it then has all of the view engines look up views by searching for the views on disk. This makes perfect sense from a performance standpoint, but was the source of a subtle bug.

Consider what happens when someone accesses a component (Component.cshtml) that has an Experience Editor view (Component.EE.cshtml) from outside the Experience Editor right after application start up. For simplicity’s sake, imagine there’s just one RazorViewEngine, and one ExperienceEditorViewEngine in the application.

  1. The ExperienceEditorViewEngine will be instructed to lookup the view (Component.EE.cshtml) from its cache, but since the application isn’t in Experience Editor mode, it will be skipped.
  2. The RazorViewEngine will be instructed to lookup the view (Component.cshtml) from its cache, but since the application has just started, no view locations are cached, and it will not find the view.
  3. The ExperienceEditorViewEngine will be instructed to lookup the view (Component.EE.cshtml) from disk, but since the application isn’t in Experience Editor mode, it will be skipped.
  4. The RazorViewEngine will be instructed to lookup the view (Component.cshtml) from disk, it will find it, cache the view location, and return the view.

Now, consider what happens when a content author tries to access that component from the Experience Editor:

  1. The ExperienceEditorViewEngine will be instructed to lookup the view (Component.EE.cshtml) from its cache, but since it hasn’t found the view before, it will not find the view.
  2. The RazorViewEngine will be instructed to lookup the view from its cache (Component.cshtml), and since it found the view before, it will return the view.

In this scenario, the ExperienceEditorViewEngine will never get a chance to cache the view location of Component.EE.cshtml because the RazorViewEngine will always find Component.cshtml from its cache first.

Forcing the ExperienceEditorViewEngine to skip caching and always look for the view on disk was a quick fix for this issue. Another approach could be to force the ExperienceEditorViewEngine to look up the view when not in Experience Editor mode, but still return NullViewEngineResult(), thereby forcing the ExperienceEditorViewEngine to cache Experience Editor view locations even when not in Experience Editor mode.

Naturally I’d like for the ExperienceEditorViewEngine to take advantage of the view location cache, but pragmatically I think there are a few things that make the solution above acceptable: first, the ExperienceEditorViewEngine only ever does work in the Experience Editor, so the live site will never be affected by the lack of view location caching in the ExperienceEditorViewEngine; second, none of our application’s pages have that many components, so view location caching isn’t going to improve page load time by an appreciable amount.

Let me know your thoughts on this issue in the comments!

Subscribe to the Microsoft Weekly Digest

* indicates required

Sitecore MVP & Lead Technical Consultant

7 thoughts on “Sitecore MVC – Independent Experience Editor Views

  1. Corey Smith Post author

    Venkata,

    What do you mean by ViewFilter?

    Pragmatism is key here–if you just need to add one small bit of additional markup for your components (e.g., one <span> or one <br/>) in the Experience Editor, creating a separate view just for the Experience Editor is probably overkill. Indeed, I try as much as possible to avoid creating two separate views. However, once you find yourself adding more than one @if (Sitecore.Context.PageMode.IsExperienceEditor) throughout your view, it’s probably time to create a separate view just for the Experience Editor.

  2. Good catch! I guess it makes sense, explicit viewpath not getting modified with implicit behavior. Anyway, I really liked the simplicity of the displaymodes and was just looking for an excuse to use them. Will keep this one in mind.

  3. Corey Smith Post author

    Thanks a lot for the feedback Reinoud. Nice post! I hadn’t thought of extending this to View Renderings as well; I like how you did that.

    I originally explored using a custom DisplayModeProvider before writing the ViewEngine detailed in this post, but I found DisplayModes a bit limiting because they don’t work when you specify an explicit view path (e.g., @Html.Partial(“~/Views/My/ViewPath.cshtml”)). Have you not run into that issue?

  4. Corey Smith Post author

    Yes it does result in having to maintain two separate views but personally I’d rather maintain two separate, clean views than one with a lot of if statements to handle differences in the Experience Editor. My preference is always to handle the differences with CSS as you’ve described, but when CSS isn’t sufficient I’ll just create a separate view.

  5. Doesn’t this just result in having to maintain two separate views though? I agree with the need, what I’ve done in the past is look at the classes the Page Editor or Experience Editor place on components, then add stylings into CSS that are only active per component if those classes are found (one example: scEnabledChrome[sc-part-of]) That allows me to maintain one set of markup but still have the EE specific stylings necessary to help out content authors.

Leave a Reply

Perficient Microsoft Blog

Insights, best practices and technical perspectives to help you leverage your investment in Microsoft technology solutions to power your business growth

Archives