Tutorial: Building Modular Multi-tenant ASP.NET Core Applications with Dotnettency

Part 2

This tutorial is part 2 in this series. The sample code to go with this post can be found here.

Published on 07 August 2017

In the previous part of the this tutorial we saw how we could do some very basic Tenant resolution. We were identifying who the current tenant is based on values available in the URL such as port number etc. In this post, we will see how we can also use values not available in the URL such as a cookie value (or anything else), to identify the current tenant also.

Tenant Identification

When a request is received, dotnettency creates an identifier that indicates the current tenant. By default, the identifier is based on the request url. However we can override this.

First, derive from HttpContextTenantDistinguisherFactory<TTenant> and override GetTenantDistinguisher:


    public class CookieTenantDistinguisherFactory : HttpContextTenantDistinguisherFactory<Tenant>
    {
        public CookieTenantDistinguisherFactory(IHttpContextAccessor httpContextAccessor) : base(httpContextAccessor)
        {
        }

        protected override TenantDistinguisher GetTenantDistinguisher(HttpContext context)
        {
            // We will return the request uri by default to identify the tenant,
            // unless a tenant cookie is available, in which case we will return a URI with a custom scheme.
            var uri = context.Request.GetUri();

            var cookie = context.Request.Cookies["tenant"];
            if (!string.IsNullOrWhiteSpace(cookie))
            {
                switch (cookie)
                {
                    case "Moogle":
                        return new TenantDistinguisher(new Uri("tenant://Moogle"));

                    case "Gicrosoft":
                        return new TenantDistinguisher(new Uri("tenant://Gicrosoft"));
                }
            }

            return uri;
        }
    }

Notice how, when a cookie is present, we return a URI with our own custom scheme - i.e tenant:// and with a hostname that indicates the tenant specified by the cookie.

Next, get dotnettency use our new implementation, in startup.cs:


            services.AddMultiTenancy<Tenant>((multiTenancyOptions) =>
            {
                multiTenancyOptions
                    .DistinguishTenantsWith<CookieTenantDistinguisherFactory>()
                    .InitialiseTenant<TenantShellFactory>();
            });

Now lastly, we need to adjust our TenantShellFactory to handle our custom URI's:


    public class TenantShellFactory : ITenantShellFactory<Tenant>
    {
        public Task<TenantShell<Tenant>> Get(TenantDistinguisher distinguisher)
        {
            // If cookie was present, then scheme will be our own custom one.         
            if (distinguisher.Uri.Scheme == "tenant")
            {
                var tenant = distinguisher.Uri.Host.ToLowerInvariant();
                switch (tenant)
                {
                    case "moogle":
                        return CreateMoogleTenant();
                    case "gicrosoft":
                        return CreateGicrosoftTenant();
                }

            }

            // Otherwise, just pick tenant based on port as before.
            if (distinguisher.Uri.Port == 5000 || distinguisher.Uri.Port == 5001)
            {
                return CreateMoogleTenant();
            }

            if (distinguisher.Uri.Port == 5002)
            {
                return CreateGicrosoftTenant();
            }


            throw new NotImplementedException("Please make request on ports 5000 - 5003 to see various behaviour.");

        }

        private Task<TenantShell<Tenant>> CreateGicrosoftTenant()
        {
            Guid tenantId = Guid.Parse("b17fcd22-0db1-47c0-9fef-1aa1cb09605e");
            var tenant = new Tenant(tenantId, "Gicrosoft");
            var result = new TenantShell<Tenant>(tenant);
            return Task.FromResult(result);
        }

        private Task<TenantShell<Tenant>> CreateMoogleTenant()
        {
            Guid tenantId = Guid.Parse("049c8cc4-3660-41c7-92f0-85430452be22");
            var tenant = new Tenant(tenantId, "Moogle");
            // Also adding any additional Uri's that should be mapped to this same tenant.
            var result = new TenantShell<Tenant>(tenant, new Uri("http://localhost:5000"),
                                                         new Uri("http://localhost:5001"));
            return Task.FromResult(result);
        }
    }

Seeing it in action

Now to see this in action, install your favourite cookie editing tool in your browser. I personally use EditThisCookie

Start the application running and browse on localhost:5000 and you will see:

dotnettency-helloworld-moogle.PNG

Now, add a cookie named "tenant" with a value "Gicrosoft". Refresh the page:

dotnettency-cookie-gicrosoft.PNG

Hurray! It works! (If it doesn't, then you can compare against the sample code here)

What's next?

Stay tuned for:

  1. Per Tenant Container / Services
  2. Per Tenant Middleware
  3. Per Tenant IHostingEnvironment (for sandboxing file access)
  4. Modules (Shared and Routed)

Don't forget to leave a comment if you find this stuff useful!

This is part of a series. You can find the other parts here

You can find the sample code for this post here

comments powered by Disqus