Sitecore Item Editor Tricks and ASP.NET MVC

Wed Dec 08 2010

In a recent Sitecore project we did at Hanson Dodge Creative, we had a need for custom Item Editors to allow users to search and select items from a legacy CMS database. The basic idea was to select content from the legacy CMS, store content IDs in Sitecore fields, and render the content within Sitecore sublayouts. The approach worked great, but we ran into some challenges (and their solutions) along the way with our Item Editors, which I wanted to share with the Sitecore community. We also made some good use of ASP.NET MVC to build the Item Editors. I’m a big fan of using MVC with Sitecore, so I thought I’d also take an opportunity here to show one great use of it.

Lots of good info here on creating Item Editors in Sitecore, so read on.

Saving Data from a Custom Item Editor

There is information out there already on the basics of setting up an item editor in the core database, and adding it to the Standard Values of your data template. This is nothing new. Some guides to the basics of Item Editors:

As part of our implementation, we wanted to support the existing Save button in the Ribbon. A separate button would have been too confusing for our users. Mark's post outlines an approach for capturing the javascript Save event in the Custom Item Editor, and then uses a jQuery AJAX call to save the value. However we encountered two issues with this implementation:
  • Our save was not working consistently. We confirmed via Firebug that the values from the Content tab were also being saved, overriding the values from the Item Editor.
  • It is confusing to the end user where they should edit the value -- in the Content tab, or in the Item Editor.
The technical hurdle here is obviously the bigger one. If the Content tab is constantly overriding our save, allowing editing of values in the Item Editor is pointless.

I ultimately found a solution using IFrame fields in my template field definition. IFrame fields provide a simple way of creating a custom field editor, and John West had discovered and shared a means of saving the IFrame field value as part of the Save event. My thinking was thus: if an IFrame field can save a value for a field, can I get the IFrame field to read its value from the Item Editor? With a little Javascript hackery, I got this to work. Since that StackOverflow post though, we cleaned this up, made it reusable, and created a test harness.
 
 
sitecore.html -- Simulates the Content Editor. Contains the "Item Editor" frame and two "IFrame Field" frames.
customItemEditor.html -- Simulates the custom item editor, with a form and input fields.
iframeField1.html and iframeField2.html -- Simulates the IFrame fields, which "delegate" to the item editor to get their field values.
crossIframeMessenger.js -- The meat and potatoes, should be included on the Item Editor page.
 
The basic idea: the Item Editor page registers a function on the parent document, which the IFrame fields look for and call when Sitecore requests their values. Note that because of browser security you may need to run this test harness on an HTTP server.
 
What's great about this solution is that it solves our usability issue too -- a little messaging and styling in the IFrame field directs the user on how to edit the data in question:
 

Custom IFrame Field

What's nice too is that you can leave fields in the Content Editor tab that don't require the Item Editor. You only need to "delegate" the fields necessary to make your Item Editor work.
 

ASP.NET MVC for your Item Editor

With a framework in place, we got our hands dirty creating our Item Editors. Outside our Sitecore work, we've been utilizing MVC in almost all of our new ASP.NET development. We've found that the code is cleaner, we are more efficient, and we are saving money by having fewer workstations thrown out the window due to Webforms event model frustrations. You'll see later that using MVC had some nice advantages specifically for our Item Editors as well.
 
There are some steps necessary to getting MVC running on a Sitecore project that I won't go through here. Technically, it is only on Sitecore 6.4 that MVC has become "supported," though we have been using it since 6.0. John West has blogged about getting MVC running on Sitecore 6.4. In addition to standard MVC web.config entries, the most important step is creating an httpRequestBegin pipeline processor that prevents the Sitecore layout engine from attempting to handle MVC routes. Place such a processor after the ItemResolver but before the LayoutResolver.
/// <summary>
/// Aborts the Sitecore pipeline if there is a registered System.Web.Routing route.
/// If placed after sitecore resolvers,
/// allows us to use valuable Sitecore context items (e.g. Sitecore.Context.Database).
/// </summary>
public class SystemWebRoutingResolver : Sitecore.Pipelines.HttpRequest. HttpRequestProcessor
{
public override void Process(Sitecore.Pipelines.HttpRequest. HttpRequestArgs args)
{
    RouteData routeData = RouteTable .Routes.GetRouteData( new HttpContextWrapper (args.Context));
    if (routeData != null )
    {
        args.AbortPipeline();
    }
}
}

With MVC working side by side with Sitecore, we created Actions and Views for our Item Editors and IFrame fields. With MVC it's easy enough to read the "id" and "db" parameters passed into the Item Editor URL. You can then construct your View Model and return the appropriate view for your Item Editor. In our case, we needed to retrieve values from the legacy CMS, based on the Sitecore item, and place those in the View Model.

public ActionResult Index( string id, string db)
{
    Sitecore.Data. Database database = Sitecore.Data. Database .GetDatabase(db);
    Item item = database.GetItem(id);
    //do some stuff with Item
    //construct viewModel
    return View(viewModel);
}

We also created a reusable shared view for our IFrame fields, so that we could easily add new fields and delegate them to the item editor. The view is essentially the markup from the test harness, with some of the values coming from a view model instead.

public ActionResult Field( string name)
{
    return View( new IFrameField () { FieldName = Server.UrlDecode(name), Editor = "Choose Articles" });
} 

After setting up routes in our Global.asax, we configured these MVC actions as the URLs for our Item Editors and IFrame fields. Note that we configured the routes under the /sitecore/shell URL path. More on why this is important later.

routes.MapRoute(
    "SitecoreShellDefault" ,
    "sitecore/shell/mvc/{controller}/{action}" ,
    new { controller = "ItemEditor" , action = "Index" }
); 

Item Editor with MVC path

IFrame Field with MVC path

Improving the Solution: Item Locking

We implemented 3 custom Item Editors with this approach, with good results. However when we started testing with non-Administrative users, we found we had forgotten one important aspect of the Content Editor interface.... item locking! Even if the Content tab indicated that an item was locked, or needed to be locked, our custom tab still acted as if everything was groovy. However with a few lines of code in our Controllers, a couple more shared Views, and a bit of styling, we were able to replicate the behavior of the Content tab.

if (!Sitecore. Context .User.IsAdministrator && item.Locking.IsLocked() && item.Locking.GetOwner() != Sitecore. Context .User.Name)
{
    ViewData[ "lockUser" ] = item.Locking.GetOwner();
    ViewData[ "currentUser" ] = Sitecore. Context .User.Name;
    return View( "Locked" );
}

if (!Sitecore. Context .User.IsAdministrator && !item.Locking.IsLocked())
{
    return View( "GetLock" );
} 

Custom Locking Message for a Locked Item

Custom Locking Message for an Unlocked Item

When we first tested this solution, we noticed something strange... after Previewing a page, users would get a message that they cannot edit the item, because their own user has it locked. Debugging revealed that the Context user was coming back as extranet\Anonymous. Why? Because our routes were not yet mapped under /sitecore/shell -- so they were not running in the context of the shell site, or the shell user. That's why above our routes are mapped to that path. Makes sense though -- your Item Editor should run under the shell context.

Other Advice

We picked some other helpful notes on Item Editors and MVC along the way:

  • If you're getting erroneous "save changes" warnings from the content editor, check for Javascript errors in your Item Editor.
  • With the IFrame approach outlined here for saving Item Editor values, keep in mind that turning on Raw Values will disable the IFrame field, and your values from the Item Editor will not be saved.
  • The Velir Custom Item Generator is a great complement to MVC-based Item Editors -- the generated Custom Items make great View Models.

 

That's it! Thanks to my dev team who helped prove out this architecture, including @ALittleTooQuiet, @ddysart, and @veryfancy. Hopefully there are some bits in our experience to help you with your Item Editor implementation, or you have a better understanding of how ASP.NET MVC can be used with Sitecore. Please leave any feedback below, or hit me up on Twitter if you have questions.

- Nick Wesselman / techphoria414

Loading...
Nick Wesselman

Nick Wesselman started working professionally in software development just a few days after everyone realized we dodged a bullet with the Y2k bug. He’s worked as a Sitecore solution partner, Technology partner, and now for Sitecore itself. He lives with his wife, son, and daughter in Asheville, NC.