Thoughts from the Wet Coast

The musings of an ASP.NET Developer from Canada's We(s)t Coast

Comments

Articles

 
     

Naif.Blog: 6. Using Middleware to Implement Multi-tenancy

Category: ASP.NET Core
Aug 01 2016

Last weekend was another long weekend in Canada - at least in the part of Canada where I live, British Columbia, so I had some time to return to working on my bog application - Naif.Blog.  The final piece of the puzzle - how can we make our simple blog application support multiple different blogs from the same code-base.  This feature can save hosting dollars as it allows a single hosted site to support many different blogs, and is usually termed - multi-tenancy.

As usual there are many ways to skin a cat, but the approach I decided to take was to use Middleware to determine which blog is making the request.

Application Context

In order to determine which blog context we are executing in, I have created an interface IApplicationContext and a matching object ApplicationContext.  We will use this object to manage our context as a service using Dependency Injection.   ASP.NET Core’s DI component supports a number of lifecycle types, and one of these is Scoped which creates a new instance for each request, so in Startup.cs we add the single statement

services.AddScoped<IApplicationContext, ApplicationContext>();

Now at the beginning of the request a new instance of ApplicationContext will be created and any object that needs the context can retrieve it from the DI container.

The application context is a simple object, as shown below.

public interface IApplicationContext
{
    Guid Id { get; }

    IEnumerable<Models.Blog> Blogs { get; set; }

    Models.Blog CurrentBlog { get; set; }
}

It has three properties - an Id, a collection of all the blogs and the current blog.  The Blog model is not really a new class.  In previous iterations of Naif.Blog there was a BlogOptions class and this class has been renamed Blog and extended with a couple of additional properties.

public class Blog
{
    public string ByLine { get; set; }

    public string Disclaimer { get; set; }

    public string Id { get; set; }

    public Post Post { get; set; }

    public IEnumerable<Post> Posts { get; set; }

    public string Theme { get; set; }

    public string Title { get; set; }

    public string Url { get; set; }
}

The BlogOptions object was loaded once at start up (from the appsettings.json file) as these options were static.  But now we need to load the relevant Blog detail based on the hostname in the request, and this is where ASP.NET Core Middleware comes in.

ASP.NET Core Middleware

The ASP.NET Core pipeline is very flexible and allows developers to add components to the pipeline, as Middleware.  We have already used middleware components without realizing what they are in Startup.cs.  For example app.UseStaticFiles() invokes the static files middleware and app.UseMvc invokes the MVC middleware.

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Home/Error");
}

app.UseStaticFiles();

app.UseMvc(routes =>
    {
        ...
    });          

ASP.NET Core Middleware is a simple class with a single method - Invoke.  The constructor takes a RequestDelegate which represents the next middleware component to invoke, and the Invoke method takes an HttpContext object as its first parameter - both methods support the injection of additional objects through Dependency Injection.

The constructor for our ApplicationContextMiddleware class is as follows

public ApplicationContextMiddleware(RequestDelegate next,
                                    IHostingEnvironment env,
                                    IMemoryCache memoryCache,
                                    ILoggerFactory loggerFactory)
{
    _memoryCache = memoryCache;
    _logger = loggerFactory.CreateLogger<ApplicationContextMiddleware>();
    _next = next;
    _blogsFile = env.WebRootPath + "/blogs.json";
}

and the Invoke method is

public async Task Invoke(HttpContext context, IApplicationContext appContext)
{
    appContext.Blogs = GetBlogs();
    appContext.CurrentBlog = appContext.Blogs.SingleOrDefault(b => b.Url == context.Request.Host.Value);
    await _next.Invoke(context);
} 

The Invoke method first retrieves all the blogs from the blogs.json file.  It then retrieves the blog whose Url property matches the host url for the request, and sets the CurrentBlog property of the ApplicationContext object.  Finally the next Middleware component in the pipeline is called.

Our custom middleware can now be added to the request pipeline by adding a call to the UseMiddleware method of the IApplicationBuilder interface.  I have implmented this using an extension method/

public static IApplicationBuilder UseApplicationContext(this IApplicationBuilder app)
{
    return app.UseMiddleware<ApplicationContextMiddleware>();
}

which can then be invoked in the Startup.cs class.

app.UseStaticFiles();

app.UseApplicationContext();

Now our Middleware will execute on each request and build the ApplicationContext object.  We can then use that object by injecting it into our Controllers, ViewComponents and Views just as we would any other service.

Source

The source for Naif.Blog can be found at https://github.com/cnurse/Naif.Blog.  If you want to find the state of the repository used in this post then you can find that at the tagged release v0.0.5 (https://github.com/cnurse/Naif.Blog/releases/tag/v0.0.5)

Categories

Tags