DEV Community

Cover image for Kentico CMS Quick Tip: Minimal JSON Web APIs with IHttpHandler and .ashx Files
Sean G. Wright for WiredViews

Posted on • Updated on

Kentico CMS Quick Tip: Minimal JSON Web APIs with IHttpHandler and .ashx Files

Photo by Benjamin Nwaneampeh on Unsplash

This post was inspired by a discussion I had recently with Chris Bass, an inspiring member of the Kentico developer community. Check out his blog for more Kentico content.

Kentico Portal Engine CMS + API

When we need to make calls to our Kentico CMS Portal Engine web application from the browser over XHR or from another web service, we need an API to communicate with.

There are several ways to accomplish this, each with pros and cons, depending on our requirements 🤔.

Here's another great blog post about this topic from Kristian Bortnik.


Web API 2 - The Sports Car 🏎

Kentico's documentation explains the steps for integrating Web API 2 into the CMS.

The approach is great if you need a large and robust custom API surface standing in front of the CMS - and it's an approach I've used many, many times 👍.

However, the setup isn't trivial and the solution effectively runs the Web API OWIN-based application within the CMS - which leads to some sharp edges 🔪.


Kentico REST API - Public Transportation 🚍

Kentico has a REST API built-in, which can be used to query, and modify all kinds of data within the application 🧐.

It does provide security through a Basic Authentication HTTP header and but authenticates against the normal User accounts created within Kentico.

The REST API exposes the Kentico Page data and *Info objects directly, effectively creating a projection of the database over HTTP.

Given the above, the caveat of this built-in solution is that it's verbose, not customizable, and a leaky abstraction 😞.


IHttpHandler - The Commuter Car 🚗

For those simple scenarios where we only need a handful of endpoints, exposing a limited set of data, curated and filtered for us, we would like a way to build an API... without all the API.

A nice solution to this problem is the ASP.NET IHttpHandler, which can be exposed through an .ashx file in our CMS project.

You can read more about how IHttpHandler works in Microsoft's documentation.

IHttpHandler gives us extremely low-level to an incoming HTTP Request and the outgoing HTTP Response.

There's no WebForms code here, just the raw request and response 😮.

This is perfect for our use-case since we don't want to render HTML through a complex web of page lifecycle events and user controls 👏.

Let's take a look at some code for a concrete example of how all of this works.

I see the IHttpHandler solution as ideal for small client-side integrations and also JSON-based server-to-server integrations with existing, in-production, Kentico CMS sites that can't risk big architectural changes.

I've done both, but we'll look at the client-side solution today.


Example: An E-Commerce Store with Dynamic Prices

Imagine we have a Business-to-Business (B2B) e-commerce application where the prices and inventory need to be pulled live from a back-end warehousing or ERP system (not Kentico).

We don't want to delay the loading of a product details page each time a visitor requests it because we need to fetch the pricing - that would hurt SEO and the User Experience ☹!

Instead, we want to cache the product details page and then, via JavaScript, request the price independently 😁.

So, we need a simple API endpoint that can forward this request on to the back-end system.


Creating the .ashx File

Let's open our Kentico CMS solution, expand the project, and then the CMSPages folder.

Right-click on the CMSPages folder and select "Add" -> "Generic Handler".

Visual Studio Project UI context menu for adding a Generic Handler

We're going to name this handler ProductApi and Visual Studio will add the .ashx extension for us.

IIS already knows how to process requests for .ashx files, so even if you haven't worked with this file type before, there's no extra work necessary to use it in your application 🤓.

What we end up with is a class named ProductApi that looks like the following:

public class ProductApi : IHttpHandler
{

    public void ProcessRequest(HttpContext context)
    {
        context.Response.ContentType = "text/plain";
        context.Response.Write("Hello World");
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Handling the Request

Now we need to handle the incoming request from the browser.

We have the raw HttpContext to work with here. This is good, because we don't want the typical Web Forms infrastructure to get in our way, but it also means a bit more work for us 😑.

The ProcessRequest method is where we will do all of our work with the HTTP Request.

Let's assume that the browser is going to send an XHR request, with a skuid specified as a query string parameter, and it will expect a JSON response in return.

Here's ProcessRequest with some validation and error handling:

public void ProcessRequest(HttpContext context)
{
    // Set this first
    context.Response.ContentType = "application/json";

    string method = context.Request.HttpMethod;

    if (!string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase))
    {
        context.Response.StatusCode = 400;

        return;
    }

    string skuIdParam = context.Request.QueryString.Get("skuid");

    int skuId = ValidationHelper.GetInteger(skuIdParam, 0);

    if (skuId == 0)
    {
        context.Response.StatusCode = 400;

        return;
    }

    SKUInfo sku = SKUInfoProvider.GetSKUInfo(skuId);

    if (sku is null)
    {
        context.Response.StatusCode = 404;

        return;
    }

    // continue processing ...

Enter fullscreen mode Exit fullscreen mode

Creating the Response

Now that we have handled all the potential issues, we can get our back-end system identifier out of the sku object and request the most up-to-date values.

For our example we will pretend the response comes back in the following shape:

public class ProductStats
{
    public decimal Price { get; set; }
    public int Inventory { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Let's get hand-wavy 👋 here and assume we were successful in getting values back from our back-end system and we now want to send them back to the browser.

These last few steps are pretty simple:

  1. Get the ProductStats response from the back-end system
  2. Use Newtonsoft.Json to serialize the C# object into a JSON string
  3. Write the JSON string as the HTTP response
    // continue processing ...

    ProductStats response = // response from our back-end system

    string responseText = JsonConvert.SerializeObject(
        response, 
        serializationSettings);

    context.Response.Write(responseText);
}
Enter fullscreen mode Exit fullscreen mode

You might have noticed the serializationSettings parameter above. That can be customized to your preferences and use-case, but it allows you to define how Newtonsoft.Json produces JSON from your C#.

I typically store this in a static readonly field in my IHttpHandler, and these are the settings I tend to use 😎:

private static readonly JsonSerializerSettings serializationSettings = 
    new JsonSerializerSettings
    {
        Formatting = Formatting.None,
        ContractResolver = new CamelCasePropertyNamesContractResolver(),

        // UTC Date serialization configuration
        DateFormatHandling = DateFormatHandling.IsoDateFormat,
        DateParseHandling = DateParseHandling.DateTimeOffset,
        DateTimeZoneHandling = DateTimeZoneHandling.Utc,
        DateFormatString = "yyyy-MM-ddTHH:mm:ss.fffK",
    };
Enter fullscreen mode Exit fullscreen mode

Using Our API

So what does using this "API" look like?

Well, we can request the "API" in the browser like so:

Browser displaying fake data JSON response from HttpHandler

But what about from JavaScript? Well, that's just as easy 😀!

I'm going to assume we're supporting modern browsers, so legacy fallbacks are up to the reader 😏:

(async () => {
    const params = new URLSearchParams({ skuid: 10 });

    const response = await fetch(`/CMSPages/ProductApi.ashx?${params}`);

    const { price, inventory } = await response.json();

    console.log('Price', price);
    console.log('Inventory', inventory);
})()
Enter fullscreen mode Exit fullscreen mode

Who would have thought we could create a completely custom JSON based integration API in just a couple of minutes 🤗!?


Bonus: All the Context

I would also like to note that since the HTTP request is going to the same domain that the JavaScript loaded under, there's no annoying cookie or CORS restrictions 🧐.

All cookies for the current domain are sent back to the server with every HTTP request, even XHR requests to our .ashx file.

This means that the normal Kentico *Context classes that give us access to the ambient request data, like the current authenticated user (MembershipContext) and the current shopping cart (ShoppingCartContext) are all still available in our ProductApi class ⚡.

If we want to respond with additional discounts for Customers in different groups, or send the current user's ID with the SKU Id to our back-end system to get product recommendations, we can do that too 😄!

How about displaying a shipping time estimate based on information gathered from the Browser Geolocation API and the items in the shopping cart? Yup, we could do that 😃.

Wrap Up

While Kentico's Web API 2 integration approach and the built-in REST API provide a lot of functionality, they don't quite fit the requirements for a small, custom, minimalist endpoint exposed by the CMS for XHR requests from the browser.

Fortunately, IHttpHandlers and .ashx files give us a quick and dirty way to stand up an endpoint using low-level ASP.NET features, without losing out on the functionality of the CMS 👍.

If you try this approach out, let me know what you think!

Thanks for reading 🙏!


If you are looking for additional Kentico content, checkout the Kentico tag here on DEV:

#kentico

Or my Kentico blog series:

Oldest comments (0)