WebApi
in Sitefinity

Dec 18

Introduction

Out-of-the-box Sitefinity offers built-in REST and OData data services for you to consume. It's officially a fully fledge Headless CMS now. But sometimes, you just need to build your own API, customised to get and format the data you want in the way you want it, and the first choice for doing that is often using an MVC WebApi solution.

After a few WebApi solutions, you fall into a pattern about how to go about creating these. Here I give you my default approach, which isn't necessarily the best of them all and won't solve every requirement, but it's going pretty well to date. :-)

The implementation starts with the one provided by Sitefinity and uses Ninject as an IoC container. You can read that first, but I give the full implementation here, along with my own changes\preferences.

This walkthrough implements the API within the main Sitefinity project, but it's much the same as implementing it in its own class library.

NuGet Packages

Let's first add all the NuGet packages we need.

As of Sitefinity 14.3, it comes with the following:
Ninject 3.2.2
Ninject.Web.Common 3.2.3

We don't want to upgrade these as they may prove to be incompatible with the rest of the Sitefinity ecosystem and any supporting packages we want to look to keep/match the same versions for compatibility. For us, we want to add:
Ninject.Extensions.Conventions 3.2.0
Ninject.Web.WebApi 3.2.4
Microsoft.AspNet.WebApi.Extensions.Compression.Server 2.0.1

Initial Supporting Classes

Our first class (taken straight from Sitefinity documentation) is the NinjectControllerFactory. Technically not really required. It is more for Widgets, but I add it for completeness, and Sitefinity tells me to.

public class NinjectControllerFactory : FrontendControllerFactory
{
    private readonly IKernel ninjectKernel;
 
    public NinjectControllerFactory()
    {
        ninjectKernel = Global.NinjectKernel;
    }
 
    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        if (controllerType == null)
            return null;
 
        var resolvedController = this.ninjectKernel.Get(controllerType);
        IController controller = resolvedController as IController;
 
        return controller;
    }
}

As it is, the WebApi will not send errors to Sitefinity, and so the CustomHttpExceptionLogger class will do this for us and help keep everything consistent.

public class CustomHttpExceptionLogger : ExceptionLogger
{
    public override void Log(ExceptionLoggerContext context)
    {
        Exceptions.HandleException(context.Exception, ExceptionPolicyName.UnhandledExceptions);
    }
}

Finally, the InterfaceMappings class. This a simple piece of code that will take care of looking up interfaces and bindings within the assembly and auto-register them for us.

public class InterfaceMappings : NinjectModule
{
    public override void Load()
    {
        this.Bind(x => x.FromThisAssembly()
            .SelectAllClasses().BindDefaultInterface());
    }
}

All of these classes I tend to put in a folder called Extensions, but you can put them where ever suits you.

Ninject

First, let's instigate Ninject. In your Global.ascx.cs file, we want to change it from inheriting the HttpApplication to the Ninject version, along with replacing the Application start event and adding some registration lines.

public class Global : NinjectHttpApplication
{
    protected override void OnApplicationStarted()
    {
        base.OnApplicationStarted();
        Telerik.Sitefinity.Abstractions.Bootstrapper.Bootstrapped += this.Bootstrapper_Bootstrapped;
    }
 
    protected void Bootstrapper_Bootstrapped(object sender, EventArgs e)
    {
        ObjectFactory.Container.RegisterType<ISitefinityControllerFactory, NinjectControllerFactory>(new ContainerControlledLifetimeManager());
        GlobalConfiguration.Configuration.DependencyResolver = new NinjectDependencyResolver(NinjectKernel);
        this.RegisterWebApiRoute();
    }
 
    private void RegisterWebApiRoute()
    {
        GlobalConfiguration.Configuration.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html "));
        GlobalConfiguration.Configuration.MapHttpAttributeRoutes();
        GlobalConfiguration.Configuration.EnableCors(new EnableCorsAttribute(ConfigurationManager.AppSettings["corsOrigins
        "], "* ""* "));
        GlobalConfiguration.Configuration.Services.Replace(typeof(IExceptionLogger), new CustomHttpExceptionLogger());
        GlobalConfiguration.Configuration.EnsureInitialized();
 
        GlobalConfiguration.Configuration.Routes.MapHttpRoute(
            "DefaultApi ",
            "webapi/{controller}/{id} ",
            new { id = RouteParameter.Optional },
            constraints: null,
            handler: new ServerCompressionHandler(new GZipCompressor(), new DeflateCompressor()) { InnerHandler = new HttpControllerDispatcher(GlobalConfiguration.Configuration) });
    }
 
    protected override IKernel CreateKernel()
    {
        IKernel kernel = new StandardKernel();
        NinjectKernel = kernel;
        return kernel;
    }
 
    public static IKernel NinjectKernel { getprivate set; }
}

The GlobalConfiguration.Configuration.Formatters.JsonFormatter enables us to return JSON-declared responses. Without this, the return will be interpreted as text. Still valid, just not always recognised as JSON by programs or frameworks.

You can add your CORs configuration here as well. I store my accepted values in the web.config.

The CustomHttpExceptionLogger is described above.

I also register my WebApi route as webapi, (you can choose anything you like). I do this as Sitefinity already uses /api/, and although unlikely, by using a custom one, I ensure that I will never have to worry about a conflict now or in the future with the Sitefinity methods and my own.

For performance, we want to compress our responses for the best speed, which is what the ServerCompressionHandler does for us.

The WebApi

We can now start on the actual API with our plumbing code completed. Create a folder to hold your WebApi code. I created a folder named WebApi, and inside it, my first controller. I decorate this with a RoutePrefix to avoid retyping it and to keep everything in its own route space.

[RoutePrefix("webapi/stuff ")]
public class StuffController : ApiController
{
 
}

We don't want to do much, if any, logic in our controllers. It's considered a bad practice and hard to test. So I create a service class to do all the 'work'. First, create an interface, so we can utilise a testing framework and IoC much easier.

public interface IStuffService
{
    List<Stuff> GetStuff(String stuffTopic);
}

And a concrete class with the actual implementation.

public class StuffService : IStuffService
{
    public List<Stuff> GetStuff(String stuffTopic)
    {
        List<Stuff> results = new List<Stuff>();
        results.Add(new Stuff()
        {
            Title = "Some Stuff "
        });
 
        return results;
    }
}

We then wire this up into our controller using Ninject. Well, behind the scenes, Ninject will wire this all up for us by using constructor injection.

private readonly IStuffService stuffService;
public StuffController(IStuffService _stuffService)
{
    stuffService = _stuffService;
}

And finally, the first method which will return a JSON response.

[HttpGet]
[Route("get-stuff/{topic} ")]
public IHttpActionResult GetSomeStuff(String topic)
{
    var response = stuffService.GetStuff(topic);
 
    return this.Json(response);
}

Caching

I always look to cache my results, and I encourage you to also to do the same. The performance improvement can not be underestimated.

First, I add the Sitefinity CacheManager and then follow this pattern to cache results.

You will need to determine how long to cache your own results. You will also need to consider events where you must clear the cache ( CacheManager[cacheKey] = null ). You may even prefer putting your cache logic at a different layer within your code. All something for you to think over.
private ICacheManager CacheManager
{
    get { return SystemManager.GetCacheManager(CacheManagerInstance.Global); }
}

Then, within my method, I wrap it and check the cache for a result first.

public IHttpActionResult GetSomeStuff(String topic)
{
    String cacheKey = "Stuff- " + topic;
    var response = (List<Stuff>)CacheManager[cacheKey];
    if (response == null || response.Count() < 1)
    {
        response = new List<Stuff>();
        response = stuffService.GetStuff(topic);
        CacheManager.Add(cacheKey, response, CacheItemPriority.Normal, nullnew AbsoluteTime(TimeSpan.FromHours(1)));
    }
 
    return this.Json(response);
}

And there you go, a basic WebApi implemented in Sitefinity.

Yet we could do more. But I think post has gone on long enough. I think it's time for a break and to look at creating a second post to continue the conversation.


Darrin Robertson - Sitefinity Developer

Thanks for reading and feel free to comment - Darrin Robertson

If I was really helpful and you would buy me a coffee if you could, yay! You can.


Leave a comment
Load more comments

Make a Comment

recapcha code