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

Sipke Schoorstra
Jun 26, 2018 · 8 min read
Image for post
Image for post

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’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

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

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

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

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:

Image for post
Image for post
dotnet --version reveals the installed .NET Core SDK version

Creating the Project

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:

Image for post
Image for post
The files and folders created by the web template

Running the Application

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:

Image for post
Image for post

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

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

Updating Startup.cs

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

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:

Image for post
Image for post
Accessing the CustomerA tenant

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

Image for post
Image for post

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

Features

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

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

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 Startup class is very similar to the TimeOfDay version:

The RemoteIpMessageProvider class is implemented as follows:

Updating Tenants.json

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

Trying it out

Image for post
Image for post

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

Image for post
Image for post

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

Conclusion

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

RTL Tech

RTL Technology Blog brings you stories from inside the RTL…

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store