LibrarySites.Banner

Use JSON and MVC to Retrieve Item Data with the Sitecore ASP.NET CMS

This blog post describes how you can implement an MVC controller and action to return information about items in the Sitecore ASP.NET web Content Management System (CMS) using JavaScript Object Notation (JSON). For more information about using MVC with Sitecore, see Posts about Using MVC with the Sitecore ASP.NET CMS.

ASP.NET MVC makes it very easy to implement action methods that generate JSON. To do so, simply create an action in a controller and use the Json() method to return data. The following code provides an example intended to service requests for URLs such as /JsonItem/GetChildItemData/ID, where ID represents the ID of an item in the Sitecore database associated with the site triggered by that request, by returning a JSON representation of some properties of the children of that item. For more information about the Sitecore context, see the blog post The Sitecore Context.

To create the controller, in the /Controllers subdirectory of your project, create a class that derives from the System.Web.Mvc.Controller abstract base class.

namespace Sitecore.Sharedsource.Mvc.Controllers
{
  using System;
  using System.Collections.Generic;
  using System.Web.Mvc;
  
  using SC = Sitecore;
  
  public class JsonItemController : Controller
  {
    public JsonResult GetChildItemData()
    {
      SC.Diagnostics.Assert.IsTrue(
        this.RouteData.Values.ContainsKey("id"),
        "RouteData.Values.ContainsKey(id)");
      SC.Diagnostics.Assert.IsFalse(
        String.IsNullOrEmpty(this.RouteData.Values["id"].ToString()),
        "RouteData.Values[id].ToString()");
      SC.Diagnostics.Assert.IsNotNull(
        SC.Mvc.Presentation.PageContext.Current.Database,
        "SC.Mvc.Presentation.PageContext.Current.Database");
      SC.Data.Items.Item item = SC.Mvc.Presentation.PageContext.Current.Database.GetItem(
        this.RouteData.Values["id"].ToString());
      SC.Diagnostics.Assert.IsNotNull(item, "item");
      return Json(this.GetChildren(item), JsonRequestBehavior.AllowGet);
    }
  
    protected IEnumerable<object> GetChildren(SC.Data.Items.Item item)
    {
      foreach (SC.Data.Items.Item child in item.Children)
      {
        yield return new
        {
          Name = child.Name, 
          Url = SC.Links.LinkManager.GetItemUrl(child)
        };
      }
    }
  }
}

Note the use of JsonRequestBehavior.AllowGet. For security, ASP.NET MVC disallows HTTP GET requests for JSON resources, but you can use this enumeration value to allow HTTP GET. In other words, you should not use this action method for secure data.

If you, like me, learned ASP.NET more than 10 years ago, you might not be familiar with the yield keyword or anonymous types as used with the yield keyword in this example. For a little bit of information about the yield keyword, see the blog post The C# Yield Keyword, or note that the yield return construct makes it easier for a method to return a collection. For information about anonymous types, see New "Orcas" Language Feature: Anonymous Types (wow, are anonymous types that old?), or just note that the compiler automatically creates a single type definition for all of the anonymous types that use this type of syntax to define the same property names. Of course you can use named types and avoid the yield keyword if you prefer.

In order to use this action, you need to add a route that triggers this controller and action. A typical MVC application would use global.asax to register routes, but I object to the lack of separation of concerns provided by global.asax. Therefore, I recommend that you add a processor to the initialize pipeline. For more information about pipelines, see the blog post All About Pipelines in the Sitecore ASP.NET CMS.

namespace Sitecore.Sharedsource.Mvc.Pipelines.Loader
{
  using System;
  using System.Web.Mvc;
  using System.Web.Routing;
  
  using SC = Sitecore;
  
  public class RegisterMvcRoutes 
  {
    public virtual void Process(SC.Pipelines.PipelineArgs args)
    {
      this.RegisterRoutes(RouteTable.Routes);
    }
  
    public bool MvcIgnoreHomePage { get; set; }
  
    protected virtual void RegisterRoutes(RouteCollection routes)
    {
      if (this.MvcIgnoreHomePage)
      {
        routes.IgnoreRoute(String.Empty);
      }
  
      routes.MapRoute(
        "Default", // Route name
        "{controller}/{action}/{id}" // URL with parameters
//        new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
      );
    }
  }
}

As described in the blog post Using Web Forms and MVC in a Single Solution with the Sitecore ASP.NET CMS, if you use MVC for the home page, you may need to tell MVC to ignore requests for the home page, as the default route would otherwise apply. Thus the MvcIgnoreHomePage property of this processor; if you use Web Forms for the home page, set this parameter to true. I haven’t yet determined what happens when item names, MVC controller and action names, and languages in the URL path collide, especially considering wildcards. For more information about wildcards, see the blog post Use Wildcards to Expose User Profiles with the Sitecore ASP.NET CMS.

Note also the commented default values for the controller and action name, and made the id parameter mandatory.

As noted in the blog post MVC Updates to Existing Pipelines in the Sitecore ASP.NET CMS, the InitializeRoutes processor added to the initialize pipeline by the /App_Config/Include/Sitecore.Mvc.config Web.config include file can modify defined routes and adds the Sitecore default route to the end of the route table. Therefore, this processor should come before that one. To avoid the need to reference other processors in the pipeline by type, the easiest place to add it is as the first processor in the pipeline. The following Web.config include file adds the route. For more information about Web.config include files, see the blog post All About web config Include Files with the Sitecore ASP.NET CMS.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <initialize>
        <processor
          type="Sitecore.Sharedsource.Mvc.Pipelines.Loader.RegisterMvcRoutes,assembly"
          patch:before="processor[1]">
          <mvcIgnoreHomePage>false</mvcIgnoreHomePage>
        </processor>
      </initialize>
    </pipelines>
  </sitecore>
</configuration>

Now, a URL such as /JsonItem/GetChildItemData/%7B110D559F-DEA5-42EA-9C1C-8A5DF7E70EF9%7D returns JSON such as the following (for testing I created some basic items under the /sitecore/content/Home item that corresponds to that ID):

[{"Name":"First Child","Url":"/First Child.aspx"},{"Name":"Second Sample Item","Url":"/Second Sample Item.aspx"}]

Note that some browsers (such as Chrome) render the text; others (such as IE) prompt you to download it. If time permits, I'll try to write a post that describes how to use jQuery on the client to access such a JSON resources in a view, but that's a little more client-side than I usually approach.

Oh yeah, I'm not sure, but I think it's a bad idea to pass a Sitecore.Data.Items.Item object to the Json() method. I think it starts walking the object tree and ends up with too much data, which would probably result in a StackOverflowException. But I didn't investigate that closely.

  • Hi John,  If I add the route from this example, I get the "An illegal route has been detected: '{controller}/{action}/{id}'. If you wish to keep this route, please remove it from the Mvc.IllegalRoutes setting" error.  In the default Sitecore MVC config this route is added to the IllegalRoutes config setting. When you use "JsonItem/{action}/{id} " as the route, it works.   Does this mean you will have to add every controller to the route table ?

  • @Michael: The early builds I used didn't have that setting. The problem is that the default route matches too many URLs - anything that has three or fewer segments. So ASP.NET tries to use your route for everything, overriding Sitecore's route for handling item URLs, and you get controller/action not found for any URL of a Sitecore item that does not correspond to a controller name.   www.sitecore.net/.../Prevent-the-Sitecore-ASPNET-CMS-from-Applying-MVC-Routes.aspx describes a hacky solution that can get around this if you use .aspx in the URLs of your Sitecore items (both Web Forms and MVC, which actually uses .cshtml files). But I don't really like that solution.   I prefer to change Mvc.IllegalRoutes to something ike "|NOT_LIKELY_TO_OCCUR|". Then I override the TransferRoutedRequest processor in the httpRequestBegin pipeline to only call the default implementation if a route matching the requested URL exists. But I also have to override the SitecoreControllerFactory to perform a similar check, and the InitializeControllerFactory processor in the initialize pipeline to use my factory instead of the default. I was going to blog about this, but I have not really tested it completely, and I think Sitecore engineering has to address this issue. I can see potential problems (if the URL doesn't specify a controller, could/should this code check for the existence of the a controller, what if an item exists with the same name as a controller, etc.). Also, I'm not sure the way it checks for the existence of a controller is most efficient, and it may be worthwhile to cache the results of those checks. Still, I would be happy to share the code if you want to see it.

  • Can someone tell me how to access sitecore items in Java script  file ?  for example I have the following code   $scope.GetPreferredLanguages = @Html.Sitecore().Field("JsonData", Sitecore.Context.Database.GetItem("/sitecore/content/Global Content/DataFiles/PreferredLanguages"))

  • Hariharan,  Is JsonData a custom field that you created? (see option 1) Or instead are you trying to serialize the PreferredLanguages item or child items? (see option 2)  Option 1. You could use something like the following.  var preferredLanguages = @Html.Raw(item["JsonData"]) I might write the json to a string first and use a javascript json decoder instead of writing the JSON directly. var preferredLanguages = $.parseJSON("@Html.Raw(item['JsonData'])")   Option 2:  Use the Item Web API service provided by sitecore to serialize the item to JSON. sdn.sitecore.net/.../SitecoreItemWebApi10.aspx  

  • My Jason data I have it stored in my Sitecore as Jason data in data files  My issue is I am not able to use this code in my Javascript    @Html.Sitecore().Field("JsonData", Sitecore.Context.Database.GetItem("/sitecore/content/Global Content/DataFiles/PreferredLanguages"))

  • Once I keep the following code in my .CsHtml file everything works ok. I am trying to move all my script code to a seperate java script file and I am not able to use the @Html tag in my script file it throws error

  • Hi David,  All I am trying is to use the @Html tag in either my controller or java script which is not working is there any way I can use ?  

  • It looks like in Sitecore 8 there is a nicer way to do this so you don't have to register routes:

    hookedon.digital/.../

    Going to try it out...