Skip to content

Multi Tenancy

Steve Ives edited this page Apr 1, 2020 · 2 revisions

Harmony Core Logo

Multi-tenancy

Multi-tenancy is an architecture that enables a web service to serve multiple customers or multiple customer sites. (I.e., multiple customers or sites are served from the same URL.) The customers or sites may share some or all data files, but the service must be able to distinguish one customer/site's request from another.

Implementing multi-tenancy in Harmony Core is a straightforward process that involves limited project customization if you used the Harmony Core Solution Templates to create your Harmony Core solution.

Use the StartupCustom.dbl file in the Service project to implement partial methods referenced in the generated Startup.dbl file. If your Services project does not have this file, start by creating it. It will need to include a partial Services.Startup class with partial ConfigureServiceCustom, ConfigureCustomBeforeMvc and ConfigureCustom methods. For example:

import Harmony.AspNetCore.Context
import Harmony.Core.Interface
import Harmony.Core
import Harmony.Core.FileIO
import Microsoft.AspNetCore.Builder
import Microsoft.AspNetCore.Hosting
import Microsoft.AspNetCore.Http
import Microsoft.Extensions.Primitives
import Microsoft.Extensions.DependencyInjection
import Microsoft.Extensions.DependencyInjection.Extensions
import System.Linq
namespace Services

	public partial class Startup

		partial method ConfigureServicesCustom, void
			services, @IServiceCollection 
		proc
		endmethod

		partial method ConfigureCustomBeforeMvc, void
			required in app, @IApplicationBuilder
			required in env, @IHostingEnvironment
		proc
			app.UseMultiTenancy(GetTenantId)
		endmethod

		partial method ConfigureCustom, void
			required in app, @IApplicationBuilder
			required in env, @IHostingEnvironment
		proc
		endmethod
	endclass

endnamespace

The ConfigureCustomBeforeMvc method must include a call to the UseMultiTenancy extension method (in the Harmony.AspNetCore.Context namespace), which adds Harmony Core multi-tenancy middleware to the ASP.NET Core processing pipeline:

app.UseMultiTenancy(GetTenantId)

In this call, GetTenantId is the name of a method you implement that determines which customer/site this request is intended for. The following is a simple example that determines this based on a custom HTTP header: x-tenant-id:

private method GetTenantId, @string
	httpCtx, @HttpContext
proc
	data headers, StringValues
	if(httpCtx.Request.Headers.TryGetValue("X-TENANT-ID", headers) && headers.Count == 1) then
	begin
		mreturn headers.ToArray().First()
	end
	else
		mreturn String.Empty
endmethod

This method could instead use a property of the request URL, a cookie, or something stored in the user's authentication information. Whatever the source, if this information is returned from GetTenantId, it will be stored and made available to all running code associated with the async context. I.e., it will be available anywhere in the processing pipeline for the request.

Now that the tenant ID is available to the rest of our code, we need to make sure the EF provider uses the right data file. This is done by replacing the default Harmony Core implementation of Harmony.Core.FileIO.IFileChannelManager. The Harmony.Core.FileIO namespace includes two base classes for this:

  • If you need to support IOHooks, inherit from HookableFileChannelManager.

  • Otherwise, inherit from FileChannelManager.

The following is a FileChannelManager example, but the methods are the same in either case:

import Harmony.Core.FileIO
import Harmony.AspNetCore
namespace Services
	public class CustomFileSpecResolver extends FileChannelManager
		public override method GetChannel, int
			required in fileName, @string
			required in openMode, FileOpenMode
		proc
			data newFileName = fileName
			if(fileName == "DAT:custmas.ism")
				newFileName = MultiTenantProvider.TenantId + ":custmas.ism"

			mreturn parent.GetChannel(newFileName,openMode)
		endmethod
	endclass
endnamespace

The code in this snippet intercepts the filename before it is passed to OPEN. If the file is the one the code is looking for (custmas.ism in this case), its logical is reset to include the tenant ID that was set earlier in the request pipeline. You will almost certainly need to change this implementation to handle your specific environment.

Now that we have a custom implementation for IFileChannelManager, we need to register it for dependency injection. This is done in the ConfigureServicesCustom method we created earlier in StartupCustom.dbl. All that needs to be added is the following line:

services.AddSingleton<IFileChannelManager, CustomFileSpecResolver>()

This replaces the default FileChannelManager with CustomFileSpecResolver, enabling the web service to support multiple sites or customers. If there are security concerns, you should either prevent users from directly specifying their tenant ID, or validate it using authentication/authorization data contained in the HttpContext.

Clone this wiki locally