Validate tenant with middleware / global filter, ASP.NET Core 2

Tenant is a term that describes the country the application runs in at Just Eat. For example, UK, IE (Ireland) are two different tenants. I’ve created an api that accepts a scheduled event. The endpoint is like this

POST api/v1/uk/events

At the moment, we’ll only support 7 different tenants. The api should return BadRequest if the country code doesn’t fall on those 7. It fits well for ASP.NET Core Middleware, middleware gets executed on every request.

What’s middleware? Middleware is software that’s assembled into an application pipeline to handle requests and responses

For implementation, I need to do

  1. Create an extension method, app.UseRequireTenantValidation() to hook up the middleware
  2. Write the middleware
  3. Use the middleware in Startup class

1. Extension method

public static class ValidateTenantOnRequestExtensions
{
    public static IApplicationBuilder UseValidateTenantOnRequest(
      this IApplicationBuilder builder)
    {
        return builder.UseMiddleware();
    }
}

2. middleware

public class ValidateTenantOnRequestMiddleware
{
    private readonly RequestDelegate _next;

    public ValidateTenantOnRequestMiddleware(
      RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        if (context.GetRouteData().Values.ContainsKey("tenant"))
        {
            var tenantStr = context.GetRouteValue("tenant") 
              as string;
            Tenant _;
            var success = Enum.TryParse(tenantStr, true, out _);
            if (!success)
            {
                context.Response.StatusCode = 
                  (int) HttpStatusCode.BadRequest;
                await context.Response.WriteAsync(
                  new Error(new UnsupportedTenantException(tenantStr)).Message);
            }
        }

        await _next(context);
    }
}

However, it turned out that global filter has better support in terms of routing. app.AddMVC() populate route data and it happens almost at the latest on the pipeline. So, GetRouteData() at the middleware level returns null. Ok. I learn while I go. Let’s change the code to filter.

TenantFilter

public class TenantRequiredFilterAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        if (context.RouteData.Values.ContainsKey("tenant"))
        {
            var tenantStr = context.RouteData.Values["tenant"] 
              as string;
            Tenant _;
            var success = Enum.TryParse(tenantStr, true, out _);
            if (!success)
            {
                context.Result = new ObjectResult( 
                  new Error(new UnsupportedTenantException(tenantStr)))
                {
                    StatusCode = 400
                };
            }
        }
    }

    public void OnResourceExecuted(ResourceExecutedContext context)
    {
    }
}

The code is almost the same, except that now the clas inherits IResourceFilter.
You can add the filter to the pipeline in Startup.

services.AddMvc(options => { 
    options.Filters.Add(); });

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s