Sitecore and WebApi Living in Harmony

How cute is that?! by Ahqib Hussain, on FlickrWe all seem to love WebApi. On the Microsoft technology stack, it is probably the most well-conceived method of building a REST-like web service. If you are building a Sitecore website, though, you may have a problem. Sitecore takes over most of the ASP.NET request cycle, which poses a problem for other ASP.NET technologies, including WebApi. That is not the end of the story, though!

If you would rather just skip all of the boring explanation stuff, then go install this Nuget package in your web project and get off my back!

Problem: Routing

The first problem that needs to be addressed, is that Sitecore handles all of the request routing. It needs to do this in order to serve up all of the Sitecore goodness, but that means you don’t get to create your own routes like you would expect in a normal web project.

Sitecore pipeline handlers to the rescue!

Luckily, Sitecore lets you hook into their request pipeline, and even abort it if you need to! Check this out! We can check the route table to see if there are any registered WebApi routes to match the current request, and then swap out the default route handler for the Sitecore one! It’s like a magical bait and switch!

public class AbortSitecoreForKnownRoutes : HttpRequestProcessor
{
    public override void Process(HttpRequestArgs args)
    {
        /* This assumes all registered routes are external to Sitecore. If you have other routes registered, you may want
           to add an additional check that the requested url is actually an /api/ route, to avoid accidentally 
           remapping these other routes and aborting the pipeline. (Thanks Jason N.)
         */
        var routeCollection = RouteTable.Routes;
        var routeData = routeCollection.GetRouteData(new HttpContextWrapper(args.Context));
 
        if (routeData == null) return;
 
        args.Context.RemapHandler(routeData.RouteHandler.GetHttpHandler(args.Context.Request.RequestContext));
        args.AbortPipeline();
    }
}

We hook this into Sitecore by adding it to the httpRequestBegin pipeline in the web.config:

<configuration>
  <sitecore>
    <pipelines>
      <httpRequestBegin>
        <processor type="{namespace}.AbortSitecoreForKnownRoutes, {assembly}"/>
      </httpRequestBegin>
    </pipelines>
  </sitecore>
</configuration>

Sweet! Now we can handle routes as we expect, but where are my routes at?

Problem: Defining Routes

Since we are using the static RouteTable, you could register new routes in the global.asax, but that solution is not very portable, and it requires you to modify a file that is shipped in the Sitecore install package (namely, global.asax), which is never a good idea if it can be avoided.

Once again, Sitecore pipeline handlers to the rescue!

Just like we can hook in to the http request pipeline, we can hook into the application initialize pipeline. This lets us keep our route definitions in a separate class.

Here we are registering the default WebApi route.

public class RegisterWebApiRoute
{
    public void Process(PipelineArgs args)
    {
        var config = GlobalConfiguration.Configuration;
        config.Routes.MapHttpRoute("DefaultApiRoute",
                                 "api/{controller}/{id}",
                                 new {id = RouteParameter.Optional});
    }
}

We can easily hook it into the initialize pipeline in the web.config:

<configuration>
  <sitecore>
    <pipelines>
      <initialize>
        <processor type="{namespace}.RegisterWebApiRoute, {assembly}"/>
      </initialize>
    </pipelines>
  </sitecore>
</configuration>

What does this mean for my projects?

Good question! I’m glad you asked!

What this means is that you can build WebApi controllers/services into a Sitecore-based website, and even access the Sitecore database and APIs for returning data!

To make all of this even easier, I have packaged up this solution for you to re-use. You can find it on github, if you have ideas to improve it, and you can find it on Nuget to quickly/easily add it to your Sitecore projects!

19 Responses to “Sitecore and WebApi Living in Harmony”

  1. Thomas Christensen

    Hi Patrick

    I have follwed your guide, and I have some issues.

    I have created and added the 2 pipeline steps.

    I have created a small test controller:
    public class HelloController : ApiController
    {
    public IHttpActionResult Get()
    {
    return this.Ok(“Hello world”);
    }
    }

    When I try to request the web api I get the following error:

    No HTTP resource was found that matches the request URI ‘http://domain/api/hello/’.

    No action was found on the controller ‘Hello’ that matches the name ‘Index’.

    It looks like it is thinking that the controller is a normal MVC controller instead of a API controller, looking for the “index” action.

    Any ideas on how to fix that?

    Reply
    • Patrick

      That sounds like normal routing behavior.

      /api/hello/ will look for an Index method on the HelloController.
      /api/hello/123 will look for an action on the HelloController that takes a single argument for “id”

      If you want to call the Get method, then you’ll need to manually add a special route. Maybe something like this:

      config.Routes.MapHttpRoute(“HelloDefaultRoute”,
      “api/hello”,
      new {action = “Get”, controller = “Hello”});

      On a side note, if you are using the newer versions of Sitecore, you may want to check out Kam Figy’s post about WebAPI 2 attribute routing in Sitecore: http://kamsar.net/index.php/2014/05/using-web-api-2-attribute-routing-with-sitecore/

      Reply
      • Thomas Christensen

        What I was trying to accomplish was to create a rest service using th webapi.

        (i am using SC7.2 btw)

        If I create a clean web api 2 project in VS2013 it automaticaly routes request to the api controller using the http method as the action to execute on the controller.

        The controller above is copy/pasted directly from such a project, where it returns the expected result:
        [string]Hello world[/string]

        (using [] instead og angle brackets)

        Reply
        • Patrick

          Obviously something like this is difficult to troubleshoot over blog comments, but it still looks like it is related to the route configuration.

          Maybe compare the route configuration from the Global.asax on both projects (the clean WebAPI2 project, and your Sitecore project).

          You may also want to double-check your library versions.
          Sitecore 7.2 (Update-3) ships with Microsoft MVC 5.1, but if you have used the Nuget package in your Sitecore project, it is likely referencing MVC 5.2. I’m not sure that would be related to this issue, but I have run into other problems because of this.

          Reply
          • Thomas Christensen

            I fixed it. I copied the route dfinition from the clean webapi project and added the
            config.EnsureInitialized();
            line. Then made sure that the processor is the last in the pipeline.

            Thanks a ton the the hints :)

            public class RegisterWebApiRoute
            {
            public void Process(PipelineArgs args)
            {
            HttpConfiguration config = GlobalConfiguration.Configuration;
            config.MapHttpAttributeRoutes();
            config.Routes.MapHttpRoute(name: “DefaultApi”, routeTemplate: “api/{controller}/{id}”, defaults: new { id = RouteParameter.Optional });
            config.EnsureInitialized();
            }
            }

  2. Happy Customer

    The system.webServer section in your include file (from the NuGet) was causing Sitecore config load to fail for some reason (and when trying to log the error, there was a NullRef). Whatup?

    Reply
    • Patrick

      I don’t normally approve anonymous comments, but I wanted to thank you for bringing it to my attention. The nuget package is updated with that section commented-out. On some Sitecore installs, users will need to copy that section into their web.config file.

      Reply
  3. wdh2207

    Hi Patrick,

    Nice post, I’ve used it to get this up and running on my development machine and it works a treat. However, when I deploy it to our servers I get the below error message. My local machine will have .net 4.5 installed, however the deployment machine does not. My site targets .net 4 so I didnt think this would be an issue. Do you have any ideas?

    NullReferenceException: Object reference not set to an instance of an object.]
    System.Web.Http.Dispatcher.HttpRoutingDispatcher.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) +298
    System.Net.Http.DelegatingHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) +38
    System.Web.Http.HttpServer.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) +310
    System.Net.Http.HttpMessageInvoker.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) +99
    System.Web.Http.WebHost.HttpControllerHandler.BeginProcessRequest(HttpContextBase httpContextBase, AsyncCallback callback, Object state) +203
    System.Web.Http.WebHost.HttpControllerHandler.System.Web.IHttpAsyncHandler.BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, Object state) +48
    System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +9048532
    System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +184

    Thanks

    Will

    Reply
    • Patrick

      I have not seen that before. What version of Sitecore? If it is Sitecore 7+, it requires .NET Framework 4.5 on the server.

      Did you add the nuget package for WebAPI and System.Net before or after the project was set to target 4.0 framework? — Just need to clarify that the correct webapi binaries are included in the project.

      It may also be something with the controller. Can you share your controller code? Definition and attributes should be enough. Method contents can be omitted if you like.

      Reply
  4. Viraj Deshpande

    Hello Patrick,

    Can you tell me how can I have a setup where I have a separate asp.net (f/w v 4.5) web api project (outside of sitecore install) and then on post build event of this proj I am simply copying my DLLs, Views folder and Content folder over to Sitecore.

    What I want to have is complete isolation between asp.net web api and sitecore. This setup isn’t working for me.

    Any pointers?

    Reply
    • Patrick

      Thanks for the message, Viraj.

      I haven’t tried that precise setup, so my answer will probably not be complete, but I’ll try and point you in the right direction…

      The first thing you should probably consider is why it needs to be completely isolated like that in a separate solution, but deployed to the same IIS website. Maybe you could consider deploying the WebApi stuff to a separate website on the same server and using URL Rewrite to reflect it on the url(s) you want.

      Another option might be to deploy your WebApi project to a Virtual folder under the main Sitecore site.

      If you are totally set on deploying it to same actual site and folder as the Sitecore install, here are some things you need to watch out for:

      * Your WebApi project will need to target the same framework as the sitecore install
      * Be careful of conflicting versions of the same libraries or dlls
      * Don’t let the web.config from your WebApi project overwrite the sitecore web.config — you will need to make any necessary changes directly to the web.config. You can get around some of this with “configSource”, but that doesn’t work for everything, and still requires changes to the web.config
      * You will need some way to register your webapi routes so that Sitecore doesn’t try to handle all of the requests. Maybe WebActivator… but that will require changes to the Global.asax

      I don’t know… the more I think about it, I’m not sure it can be done the way you are describing (assuming I understand your question correctly).

      I’d love to hear more ideas, and let me know what solution you end up using!

      Reply
      • Viraj Deshpande

        Hello Patrick, thanks much for the response. So this is what I am trying to. I have Client Only install on my machine for sitecore. This will have only Data and Website folder on my machine. I am connecting to the Database hosted on different server by changing the connection string configuration. Now we are creating our own web api layer (which will be sitting underneath sitecore) that will read the sitecore content use Sitecore Item Api. I understand Sitecore itself provide Web Api for item but we do not want to use that for various reasons.

        Can you tell me few things?

        1. Where do you create these processors mentioned above? Inside the sitecore or in different project?

        2. Can you tell me how have you configured your setup?

        3. Does the sequence of processor entries in the config. matters? meaning having this custom processor executed before certain other pipeline processor?

        Reply
        • Patrick

          To answer your questions…

          1. the processors can be in any DLL that you place into the website’s bin folder. They are not referenced from within the Sitecore content tree, just the config files, so they can be in your WebApi project without any problems. Note that they inherit from “HttpRequestProcessor”, which is a class in the Sitecore.Kernel.dll, so you will need to reference that from your project.

          2. This is a very vague question, and I’m not completely clear on what you are asking, so I will try to answer the best that I can. Generally, I do not install Sitecore into the same folder that my project source code is. I usually have sitecore installed to a separate folder, and then I will publish from my source code into the installed folder for testing. It is difficult to describe in a comment like this, but I am working on a blog post that should clarify it quite a bit. I hope to publish that soon.

          3. Yes, the order of the processors does matter. Sitecore will execute them in the order they are registered. If you take a look at the Nuget package I have created for this blog post, you can see that I have inserted the WebApi processor immediately after “Sitecore.Pipelines.HttpRequest.CustomHandlers”, which is very early in the pipeline (to avoid unnecessary request processing), but late enough to have access to the full sitecore context.

          Nuget package: https://www.nuget.org/packages/DEG.Shared.SC.EnableWebApi/

          Source code for nuget package: https://github.com/degdigital/DEG.Shared.SC.EnableWebApi

          Reply
    • Anand T

      Hi Viraj,

      I too got same situation, i have a sitecore web forms project, and a web api project, but i need to access sitecore context of sitecore project in web api project, did that work for you… i got “could not read sitecore configurations” exception while trying to access sitecore.

      any help is really appreciated.

      Thanks in advance.
      T Anand

      Reply
  5. Jason Nesbitt

    This was very helpful, so thank you very much.

    My project already had some custom routes mapped for other reasons, so aborting the Sitecore pipeline at the presence of route data caused problems for me. Instead I used the following check:

    var request = HttpContext.Current.Request;
    if (!request.Path.StartsWith(“/api/”))
    return;

    Reply
    • Patrick

      Thanks for the note, Jason!

      Now that you mention it, you are correct. This solution assumes that any custom registered route will not be using Sitecore, and should abort the pipeline.

      I’ll add some comments in the code to make this more clear.

      Reply
  6. Patrick

    I had somebody ask why I was aborting the Sitecore pipeline. Apparently they had tried it without aborting the pipeline, and it worked just fine.

    The answer is really just for performance. After the request handler has been re-mapped, there is nothing more that Sitecore needs to do, so it can safely abort the pipeline and let MVC/WebAPI finish handling the request.

    Reply

Leave a Reply