Serving up Orchard Content Using Web API

Written by | Orchard

Web Services seem to be all the rage right now, and for good reason – they are easy to work with if architected correctly and can interface with a huge range of platforms, which is very important nowadays.  Web API is Microsoft’s most recent effort at a light weight service platform, at least for HTTP based services, and I really enjoy working with it.

Orchard has historically done a great job of keeping up with the latest and greatest, and now supports full Web API integration.  This is actually a really powerful feature of that platform that I don’t see a lot of people using for whatever reason.  You can serve up literally any piece of content from Orchard as JSON through the Web API, so let’s take a look at how this works.  There are other examples out there on this topic, but I have found most of them to be very trivial in nature and don’t provide any kind of useful example.

This isn’t an in depth tutorial on Web API, it’s assumed you have at least a basic understanding of how it works but I’ll try to cover the main points to get you up and running.

In this scenario we are going to serve up the navigation of our website as JSON.  This can be a surprisingly useful feature – say you are supporting multiple websites on different domains and platforms but want them to share the same primary navigation.  This was a real scenario I had to deal with.  The parent site was Orchard, and using Web API I was able to distribute the site’s navigation across two other sites so that it could be managed from one spot.

Below is the full code – it’s actually not that complicated if you’re familiar with Orchard, and it’s actually shorter than it looks because there is some minor code duplication.  Basically all of the logic for this whole feature is seen below, so I didn’t feel it was enough duplication to warrant adding complexity through abstraction.

Go ahead and create a basic Orchard module using the command line tool and then read on when you’re ready.

public class NavServiceController : ApiController
    {
        private readonly IContentManager _contentManager;
        private readonly INavigationManager _navManager;
        private readonly INavigationProvider _navProvider;
        private readonly IMenuProvider _menuProvider;
        private readonly IMenuService _menuService;
 
        public NavServiceController(IContentManager contentManager, INavigationManager navManager, INavigationProvider navProvider, IMenuProvider menuProvider, IMenuService menuService)
        {
            _menuService = menuService;
            _menuProvider = menuProvider;
            _navProvider = navProvider;
            _navManager = navManager;
            _contentManager = contentManager;
            T = NullLocalizer.Instance;
        }
 
        public IOrchardServices Services { getprivate set; }
        public Localizer T { getset; }
 
        [AllowCrossSiteJsonAttribute]
        public List<NavItem> GetMainNav()
        {
            var content = _contentManager.Query("Page").List();
            foreach (ContentItem item in content)
            {
                var test = item.As<TitlePart>();
                var test2 = test;
            }
 
            var request = HttpContext.Current.Request;
            var url = string.Format("{0}://{1}", request.Url.Scheme, request.Url.Authority);
 
            IContent menu = _menuService.GetMenu("Main Menu");
            IEnumerable<MenuItem> menuItems = _navManager.BuildMenu(menu).ToList();
            List<NavItem> serialMenu = new List<NavItem>();
            foreach (var item in menuItems)
            {
                //checks for absolute urls entered in the Orchard admin so that it doesn't include the domain prefix
                if (item.Href.Contains("http"))
                {
                    NavItem myItem = new NavItem() { ItemName = item.Text.ToString(), ItemUrl = item.Href };
                    myItem.Items = new List<NavItem>();
                    foreach (var subItem in item.Items)
                    {
                        if (item.Href.Contains("http"))
                        {
                            myItem.Items.Add(new NavItem() { ItemName = subItem.Text.ToString(), ItemUrl = subItem.Href });
                        }
                        else
                        {
                            myItem.Items.Add(new NavItem() { ItemName = subItem.Text.ToString(), ItemUrl = url + subItem.Href });
                        }
                    }
                    serialMenu.Add(myItem);
                }
                else
                {
                    NavItem myItem = new NavItem() { ItemName = item.Text.ToString(), ItemUrl = url + item.Href };
                    myItem.Items = new List<NavItem>();
                    foreach (var subItem in item.Items)
                    {
                        if (subItem.Href.Contains("http"))
                        {
                            myItem.Items.Add(new NavItem() { ItemName = subItem.Text.ToString(), ItemUrl = subItem.Href });
                        }
                        else
                        {
                            myItem.Items.Add(new NavItem() { ItemName = subItem.Text.ToString(), ItemUrl = url + subItem.Href });
                        }
 
                    }
                    serialMenu.Add(myItem);
                }
 
                
 
            }
            return serialMenu;
        }
    }

I also included a View Model class which will we touch on in a minute:

public class NavItem
    {
        
        public string ItemName { getset; }
        public string ItemUrl { getset; }
        public List<NavItem> Items { getset; }
    }

First we create a controller that inherits from APIController and add it to the Controllers folder. I also added a data attribute to the controller that will modify the response headeres to allow cross domain requests – this is crucial if you’re working across platforms. Next we use Orchard’s dependency injection to setup all of the Navigation related services we might want to use in the constructor.

We then define the method “GetMainNav()” which will create and return our JSON. Remember, in Web API, incoming HTTP requests are mapped to methods on controllers that inherit from APIController based on how their names match the HTTP methods of GET, POST, PUT, and DELETE. So basically since GetMainNav has the word “Get” in it, Get requests will be mapped to this method. The parameters for the method can further narrow down how the request gets mapped. You can also use data attributes like [HTTPGet] attached to the methods and then you can name them whatever you want. Remember this isn’t an in depth tutorial on Web API, however, so I’ll stop there.

var request = HttpContext.Current.Request;
var url = string.Format("{0}://{1}", request.Url.Scheme, request.Url.Authority);

These two lines just get the root domain path of our site so we can prepend it to our relative urls the method generates – this is so the links that are served up are absolute paths and won’t go to different pages based on the domain.  If this isn’t the functionalithy you want obviously you can remove this piece.

IContent menu = _menuService.GetMenu("Main Menu");
IEnumerable<MenuItem> menuItems = _navManager.BuildMenu(menu).ToList();
List<NavItem> serialMenu = new List<NavItem>();
foreach (var item in menuItems)
{ .......

The first line here gets our menu by name – I am using “Main Menu” because it’s included in a default Orchard setup.  Next we can use the Navigation Manager service to build our menu and return it as a list.  After that we instantiate an empty list of type NavItem, which is the custom View Model class we created.  The reason for this View Model is cleanliness, Orchard models tend to contain an excessive amount of data and meta data and we don’t want that returned in our JSON.

Inside the foreach we iterate through all the menu items and just assign hte relative data to our View Model and add the View Model to our list.  This will assemble a list of our menu items in a clean way. In the example I am supporting two levels of navigation (basically a top nav with drop downs) but you could add more or even make it recursive.

The If statement inside the foreach is basically just there to handle one of the Orchard nav item types – in the Orchard admin area you can enter nav items that are absolute URLs to external pages. We don’t want to prepend our own domain onto these, so we check to see if its an absolute URL and if its not we run the same code without prepending our domain. The code inside the If and Else blocks is otherwise the same.

At the end we return our content – Web API will automatically return the format requested, which is usually JSON or maybe XML.

With just two classes and one method we created a whole navigation service!

The hardest part of this for me was just figuring out how to get Orchard to build the type of content items I was looking for. The navigation system is one of the more complicated types to query, though, so this example is actually simpler with something like a content type of page. An example below:

var content = _contentManager.Query("Page").List();
foreach (ContentItem item in content)
{
    var test = item.As<TitlePart>();
}

This is just a simple code example with a few tips.  The easiest way to query content types in Orchard is by using the Query method of the Content Manager service.  Just pass in the name of your content type and cast it to a list and you’ll be good to go.  I’ve also included a foreach that shows an easier way to access data if you only need data from a specific part of the content type.  All Content Types can be casted to one of their parts, which willl return a model that only has the properties you want.  In the example above we cast a page to a TitlePart – this would allow to easily collect the titles of all of our pages and return them as JSON.  A simple example, but you can imagine the potential.  You could build a mobile app that has it’s content drive by your Orchard site, and obviously much more.

Querying content from Orchard through code confused me for a long while but it’s not too bad once you get the hang of it, I’ll try to put together a post regarding this topic.

Hopefully this is a helpful overview of using Orchard to serve up content through the Web API.  It’s crazy powerful…have fun with it!

Last modified: July 24, 2014