Building multi-tenant ASP.NET Core Applications with the Orchard Core Framework

As part of a big project we’re working on at RTL Netherlands, we decided to implement a microservice-oriented architecture using ASP.NET Core. One of the requirements were that these microservices should be multi-tenant.

As you’ll come to see, implementing a multi-tenant service could not be simpler with the help of the Orchard Core Framework library.

And that’s precisely what I’m going to show you in this article: I’ll take you step-by-step to implementing a multi-tenant ASP.NET Core application using Orchard Core Framework.

Orchard Core Framework

Orchard Core is an open source application framework that is built on top of ASP.NET Core, and is a complete rewrite of Orchard 1.

Orchard’s original promise was to create shared components for building ASP.NET applications and extensions. Although Orchard version 1 did not quite deliver on that, Orchard Core makes it right, completely and wholly.

One of Orchard’s best design features has always been its modularity & multi-tenancy. However, in order to take advantage of that, your application would have to be Orchard. But in Orchard Core, it is the other way around: you create your own ASP.NET Core application, and reference only those Orchard packages that you actually need.

As it turns out, multi-tenancy is at the heart of Orchard Core and made available as a package, allowing us to write modular, multi-tenant applications without having to take any dependencies on the vast array of other functionality it provides.

Multi Tenancy

A multi tenant application is an application that can serve multiple tenants from a single code base.

There are compelling advantages to implementing a multi-tenant architecture. Because you can serve multiple tenants from a single code base, it allows for high-density hosting and potentially share the same database. Each tenant has its own configuration, such as connection strings, workflows, extensions, domain name, and so forth.

Benefits of a Multi Tenant Architecture

Imagine you have an application implemented as a set of microservices for Customer A. Suddenly there is Customer B. Without a multi-tenant architecture, you would need to deploy the entire application and basically have two copies in the cloud. Perhaps this is fine for two customers. But let’s say things are going really well, and before you know it, you have hundreds or even thousands of customers using your cloud service! Maintenance would be a potential nightmare. And then there’s the number of resources that you must consider (costs) for each instance of your application.

On the other hand, had you implemented the solution using a multi-tenant architecture, not only would deployment be a breeze, think about all the resources you would be saving. In most cases, the opportunity for system reuse between tenants will be close to 100%.

If you’re not yet convinced why multi-tenancy is a must, I encourage you to read Jakob Rodseth’s article on Elegant Multi-Tenancy for Microservices — Part I: Why Bother?

Now that you’re convinced, let’s see what it would take to write a multi-tenant microservice in ASP.NET Core.


Walkthrough — Building a Multi Tenant Application

In this walkthrough, we’re going to see what it takes to create a multi-tenant application in ASP.NET Core.

The application will be simple. First, we will implement a middleware function as a lambda that writes a simple string to the response stream. This string will be tenant-specific. Although the application will be simple, it will be easy to imagine how each tenant could have its own configuration, including database connection strings, API keys, and so forth.

Finally, I will introduce you to a new concept called features. A feature is associated to a piece of functionality. Partitioning your service into small features allow you to now only have tenant-specific configuration, but also tenant-specific functionality.

Let’s have a look!

Installing the .NET Core SDK

First of all, make sure you have downloaded and installed the latest .NET Core SDK (2.1.301 at the time of writing).

Although I’m using Windows, you can develop ASP.NET Core applications on Mac OS and Linux just as well.

If you installed .NET Core SDK before, but you’re unsure which version you installed, open a command prompt and type:

dotnet --version

On my machine, it looks like this:

dotnet --version reveals the installed .NET Core SDK version

Creating the Project

Create a new folder on your hard drive and execute the following command within that folder:

dotnet new web

That will create a new ASP.NET Core project for us using the web template to start with. To see a complete list of available templates, click here.

The folder should now contain the following files and folders:

The files and folders created by the web template

Running the Application

The web template created a Startup class that configures a “Hello World” message when we navigate to the web server. You can try it out by typing the following command:

dotnet run

Notice that a web server was launched listening on ports 5000 (HTTP) and 5001 (HTTPS). Surely enough, when you launch a webbrowser and navigate to https://localhost:5001, you will see the following output:

Now that we’ve seen that the application functions correctly, let’s go ahead and turn it into a multi-tenant application.

Adding the OrchardCore.Application.Targets NuGet Package

In order to have Orchard turn our application into a multi-tenant application, we first need to reference the OrchardCore.Application.Targets package. To do so, open MultiTenantApp.csproj and add the following line:

<PackageReference Include=”OrchardCore.Application.Targets” Version=”1.0.0-beta2–67531" />

Updating Startup.cs

Next, we’ll add OrchardCore to the pipeline. Open Startup.cs and add the following line to the (empty) ConfigureServices method:

app.services.AddOrchardCore().WithTenants();

Replace the lines in the Configure method that adds the Hello World message to the request pipeline with the following line:

app.UseOrchardCore(a => a.Run(async context =>
{
// ShellSettings provide the tenant's configuration.
var shellSettings =
context
.RequestServices
.GetRequiredService<ShellSettings>();
   // Read the tenant-specific custom setting.
var customSetting = shellSettings.Configuration["CustomSetting"];
   // Write the value of the custom setting to the response stream.                  
await context.Response.WriteAsync(customSetting);
}));

That will add the necessary middleware that implements multi-tenancy.

The complete file right now should look like this:

Notice that the middleware function is defined on the IApplicationBuilder a variable within the UseOrchardCore call. This is important, as it ensures our middleware is multi-tenant. The context argument will be within the context of the requested tenant. This means that whenever we resolve a service from that context, it will contain only services scoped to that tenant.

Creating tenants.json

Create a new file in the root called tenants.json file. The tenants file has a simple structure that contains an array of tenant configurations. The following example shows two tenant configurations:

Notice that each tenant has its own URL prefix, which enables you to invoke a URL specific to a tenant. You can also provide an actual domain name per tenant. This information is used during the HTTP request to determine to which tenant the HTTP request applies.

Also notice that I included a custom setting called "CustomSetting". When you run the project now and navigate to https://localhost:5001/customer-a, you should get the following output:

Accessing the CustomerA tenant

And as expected, when you navigate to https://localhost:5001/customer-b, you will see:

And there you have it — a multi-tenant application!


Features

The ability to have tenant-specific configuration is all you need in many scenarios, but it doesn’t have to stop there. There may be occasions where you need to enable additional, or perhaps even modified behavior to a given tenant.

For example, your application could provide a service called DefaultService that implements ISomeService and is used across tenants. But for some tenants, you might need different behavior. In other words, you may want to provide a different implementation of ISomeService for those tenants.

That is where Features come into play.

In order to associate functionality with a tenant, we can configure a tenant with a set of so-called “features”.

A feature is declared using an assembly-level attribute called Feature. This feature is then associated with a class that derives fromStartupBase.

You then configure per tenant which features should be enabled.

Implementing Features

To see how this works, let’s implement a service contract and two implementations. We’ll then associate each of them with a feature so that we can configure each tenant what features are enabled for them.

The service contract will be an interface called IMessageProvider, and looks like this:

We’ll update the middleware lambda in the Startup class to resolve all registered implementations of IMessageProvider, invoke their GetMessageAsync method, and concatenate the results into a single response sent to the browser:

Feature 1: TimeOfDay

The first feature is called the TimeOfDay feature, and is a simple middleware that renders the time of day.

Here’s its Startup class:

Notice the use of the assembly attribute on line 4 to declare a feature called “TimeOfDay”. Orchard uses this to collect all declared features.

Notice also the use of the Feature attribute that annotates the Startup class. This is how features are associated to Startup classes.

The Startup class then registers TimeOfDayMessageProvider, which looks like this:

IClock is a service provided by Orchard Core that abstracts away direct usage of System.DateTime, which is useful when writing unit tests.

Feature 2: RemoteIp

The second feature is called RemoteIp, and is another simple piece of middleware that simply echos the client’s IP address.

The Startup class is very similar to the TimeOfDay version:

The RemoteIpMessageProvider class is implemented as follows:

Updating Tenants.json

Now that we have the two features in place, it’s time to update tenants.json to configure each tenant with a feature:

Notice that we enabled the “RemoteIp” feature for both tenants, but only enabled “TimeOfDay” for one of them.

Trying it out

With all that in place, let’s see what happens when we invoke http://localhost:5001/customer-a:

And surely enough when we tryhttp://localhost:5001/customer-b:

As expected, Customer B displays the current time, since we enabled that additional feature for that tenant.

Conclusion

Multi-tenant applications are a powerful concept that does not only simplify deployment, but can save a lot of costs due to high density hosting.

Orchard Core greatly simplifies building multi-tenant services by providing a robust, easy to use programming model to implement features that can then be configured on a tenant-by tenant basis.

The complete code accompanying this article can be found here: https://github.com/rtl-nl/MultiTenantApp

More Orchard Core examples can be found here: https://github.com/OrchardCMS/OrchardCore.Samples