Using Web API with Sitecore ASP.NET Web Forms

Posted 11/19/2013 by vconcepcion

If you're used to building rich web applications in Sitecore, you'll likely come across JavaScript frameworks that plug nicely into RESTful web services.  While we've traditionally setup a WCF service or lightweight ASMX on the back-end to support such interfaces, the ASP.NET Web API seems, in many cases, better suited for such applications as it is built around the HTTP protocol, provides a lightweight mobile-friendly architecture, and is fairly easy to setup.

Without going into a MVC vs. Web Forms debate, the reality is that many of our projects use traditional Web Forms which, although does not come pre-packaged with the Web API, can still take full advantage of it.  The ASP.NET site provides a good article on Using Web API with ASP.NET Web Forms, and so I followed this tutorial to setup an HttpGet action that serves up JSON-serialized data within my Sitecore Web Forms project.

The model and view binding are out of the scope of this post, but my route and controller looked something like the following:

Global.asax:

<%@Application Language='C#' Inherits="Sitecore.Web.Application" %>
<%@ Import Namespace="System.Web.Http" %>
<%@ Import Namespace="System.Web.Routing" %>
  ...
  public void Application_Start() {
    RouteTable.Routes.MapHttpRoute(
      name: "Products API",
      routeTemplate: "api/{controller}/{id}",
      defaults: new { controller = "products" }
    );
    RouteTable.Routes.MapHubs();
  }
  ...

ProductsController.cs:

using System;
using System.Web.Http;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Reflection;
using Sitecore.Diagnostics;
using Sitecore.Web;

namespace NTT.WebAPITest.Controllers
{
    public class ProductsController : ApiController
    {
        [HttpGet]
        public string GetById(Guid id)
        {
            // return some JSON
        }
    }
}

As a test, I opened up a browser and tried going to:

http://localhost/api/products/CB5A3E55-AB7B-45F9-B005-66357A76CB66

And alas got a 404.

Requested Doc Not Found

Clicking "More information" hinted me to add the my "/api" route prefix to the end of the IgnoreUrlPrefixes setting, which essentially aborts the Sitecore httpRequestBegin pipeline early on, allowing ASP.NET to handle the request.  After making this change, I had better results and was able to get back static JSON from the web service, but then noticed that all my Database.GetItem(...) calls were returning null.  Upon debugging, I found that the Sitecore context was in fact unavailable.

As I did some searching online, I came across a helpful blog post by John West which details ways in which Sitecore can process routed requests.  The "Apply a Route" method he describes provides some insight into how Sitecore MVC applies a TransferRoutedRequest processor to abort the httpRequestBegin pipeline after the Sitecore context has been set.  This seemed promising, so after rolling back the IgnoreUrlPrefixes change, and with some help from the .NET Reflector, I added the following processor to the httpRequestBegin pipeline (note that I was unable to copy the Sitecore.Mvc.dll to my bin folder and reference the TransferRoutedRequest type directly, as this would throw an error about a missing Sitecore.Mvc.config).

<httpRequestBegin>
  <processor type="NTT.WebAPITest.Pipelines.HttpRequestBegin.TransferRoutedRequest, NTT.WebAPITest" patch:after="processor[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']" />
</httpRequestBegin>
using System.Web;
using System.Web.Routing;
using Sitecore.Diagnostics;
using Sitecore.Pipelines.HttpRequest;

namespace NTT.WebAPITest.Pipelines.HttpRequestBegin
{
    public class TransferRoutedRequest : HttpRequestProcessor
    {
        public override void Process(HttpRequestArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            HttpContextWrapper httpContext = new HttpContextWrapper(HttpContext.Current);
            RouteData routeData = RouteTable.Routes.GetRouteData(httpContext);
            if (routeData != null)
            {
                RouteValueDictionary dictionary = (routeData.Route as Route).ValueOrDefault(r => r.Defaults);
                if ((dictionary == null) || !dictionary.ContainsKey("scIsFallThrough"))
                {
                    args.AbortPipeline();
                }
            }
        }
    }
}

Tried hitting the API URL in my browser again and voila!

JSON Response

Just another quick note about editing the Global.asax file, I generally try to avoid doing this since it is part of the base Sitecore installation and can make upgrades more challenging.  With some more hints from the Sitecore.Mvc.config, I was able to move this out of Global.asax into an InitializeRoutes pipeline processor as shown below.

<initialize>
  <processor type="NTT.WebAPITest.Pipelines.Initialize.InitializeRoutes, NTT.WebAPITest" />
</initialize>
using System.Linq;
using System.Web.Http;
using System.Web.Http.Dispatcher;
using System.Web.Routing;
using Sitecore.Pipelines;

namespace NTT.WebAPITest.Pipelines.Initialize
{
    public class InitializeRoutes
    {
        public void Process(PipelineArgs args)
        {
            // Register route for Widgets Web API service
            RouteTable.Routes.MapHttpRoute(
                name: "Products API",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { controller = "products" }
            );
        }
    }
}

Share:

Archive

Syndication