Domain Routing and resolving current tenant with ASP.NET MVC 6 / ASP.NET 5

Edit on GitHub

So you’re building a multi-tenant application. And just like many multi-tenant applications out there, the application will use a single (sub)domain per tenant and the application will use that to select the correct database connection, render the correct stylesheet and so on. Great! But how to do this with ASP.NET MVC 6?

A few years back, I wrote about ASP.NET MVC Domain Routing. It seems that post was more popular than I thought, as people have been asking me how to do this with the new ASP.NET MVC 6. In this blog post, I’ll do exactly that, as well as provide an alternative way of resolving the current tenant based on the current request URL.

Disclaimer: ASP.NET MVC 6 still evolves, and a big chance exists that this blog post is outdated when you are reading it. I’ve used the following dependencies to develop this against:

You’re on your own if you are using other dependencies.

Domain routing – what do we want to do?

The premise for domain routing is simple. Ideally, we want to be able to register a route like this:

The route would match any request that uses a hostname similar to *.localtest.me, where "*" is recognized as the current tenant and provided to controllers a a route value. And of course we can define the path route template as well, so we can recognize which controller and action to route into.

Domain routing – let’s do it!

Just like in my old post on ASP.NET MVC Domain Routing, we will be using the existing magic of ASP.NET routing and extend it a bit with what we need. In ASP.NET MVC 6, this means we’ll be creating a new IRouter implementation that encapsulates a TemplateRoute. Let’s call ours DomainTemplateRoute.

The DomainTemplateRoute has a similar constructor to MVC’s TemplateRoute, with one exception which is that we also want a domainTemplate parameter in which we can define the template that the host name should match. We will create a new TemplateRoute that’s held in a private field, so we can easily match the request path against that if the domain matches. This means we only need some logic to match the incoming domain, something which we need a TemplateMatcher for. This guy will parse {tenant}.localtest.me into a dictionary that contains the actual value of the tenant placeholder. Not deal, as the TemplateMatcher usually does its thing on a path, but since it treats a dot (.) as a separator we should be good there.

Having that infrastructure in place, we will need to build out the Task RouteAsync(RouteContext context) method that handles the routing. Simplified, it would look like this:

We match the hostname against our domain emplate. If it does not match, then the route does not match. If it does match, we call the inner TemplateRoute’s RouteAsync method and let that one handle the path template matching, constraints processing and so on. Lazy, but convenient!

We’re not there yet. We also want to be able to build URLs using the various HtmlHelpers that are around. If we pass it route data that is only needed for the domain part of the route, we want to strip it off the virtual path context so we don’t end up with URLs like /Home/About?tenant=tenant1 but instead with a normal /Home/About. Here’s a gist:

Fitting it all together, here’s the full DomainTemplateRoute class: https://gist.github.com/maartenba/77ca6f9cfef50efa96ec#file-domaintemplateroute-cs – The helpers for registering these routes are at https://gist.github.com/maartenba/77ca6f9cfef50efa96ec#file-domaintemplateroutebuilderextensions-cs

But there’s another approach!

One might say the only reason we would want domain routing is to know the current tenant in a multi-tenant application. This will often be the case, and there’s probably a more convenient method of doing this: by building a middleware. Ideally in our application startup, we want to add app.UseTenantResolver(); and that should ensure we always know the desired tenant for the current request. Let’s do this!

OWIN learned us that we can simply create our own request pipeline and decide which steps the current request is routed through. So if we create such step, a middleware, that sets the current tenant on the current request context, we’re good. And that’s exactly what this middleware does:

We check the current request, based on the request or one of its properties we create a Tenant instance and a TenantFeature instance and set it as a feature on the current HttpContext. And in our controllers we can now get the tenant by using that feature: Context.GetFeature<ITenantFeature>().

There you go, two ways of detecting tenants based on the incoming URL. The full source for both solutions is at https://gist.github.com/maartenba/77ca6f9cfef50efa96ec (requires some assembling).

This is an imported post. It was imported from my old blog using an automated tool and may contain formatting errors and/or broken images.

Leave a Comment

avatar

24 responses

  1. Avatar for khalidabuhakmeh
    khalidabuhakmeh February 17th, 2015

    Pretty awesome feature.

  2. Avatar for Jerrie Pelser
    Jerrie Pelser February 21st, 2015

    I like the middleware approach Maarten! I have implemented something similar for a client, but using a custom authentication attribute. Your middleware approach seem cleaner to me - time to go do some refactoring :)

  3. Avatar for ASPHostPortal.com Windows Host
    ASPHostPortal.com Windows Host March 14th, 2015

    Nice tutorial... :)

  4. Avatar for Brazil2
    Brazil2 March 31st, 2015

    Show your best love and respect to your mother on this Mother’s Day in very unique and exclusive way. Send Mother’s Day Flowers to Brazil through online shopping store and win the heart of your mom. Order online for Mother’s Day Gift Delivery in Brazil and appreciate
    your mother for the special feelings that she has brought to your life.

  5. Avatar for Koreaflower
    Koreaflower March 31st, 2015

    Special Gift for your mother on Mother’s Day these cheerful arrangements of Flowers and Gift items. Send Mother's Day Flowers to South Korea and incorporate happiness into your mother’s life. Mother’s Day Gift Delivery in South Korea through online shopping websites is the best option to make your Mother surprise and feel very special.

  6. Avatar for rabahBell
    rabahBell April 1st, 2015

    If we use the middleware
    approach, do we have to change the existing default MVC routing?
    My understanding is that the second approach will only
    resolve the issue of getting the name of the subdomain.

    But how about handling
    routing for multitenant application. My understanding is tha MVC does not know
    how to route subdomains( is this a correct statement). Also what about the HtmlHelper extension
    methods that you have suggested in your 2009 post. Do we still need that?
    Please note I am new to MVC.

  7. Avatar for Homory
    Homory April 17th, 2015

    currently "https://gist.github.com/maa... " 's unable to open, would you mind sending me a copy to "[email protected]"?

  8. Avatar for Homory
    Homory April 18th, 2015

    I'd like to know if we could do as the following:

    public async Task Invoke(HttpContext context)
    {
    var tenant = new Tenant
    {
    Id = context.Request.Host
    };
    if (context.Request.Host.Value.IndexOf("我的家") < 0)
    {
    context.Response.Redirect("http://我的家.中国/");
    return;
    }
    var tenantFeature = new TenantFeature(tenant);
    context.SetFeature<itenantfeature>(tenantFeature);
    await _requestDelegate(context);
    }

  9. Avatar for ranjanbd71
    ranjanbd71 April 23rd, 2015

    thanx for shearing.

    ranjanbd

  10. Avatar for Georgette Lee- Magin
    Georgette Lee- Magin April 30th, 2015

    Thanks for sharing it in a more simpler, understandable way.

    Magin Web Design
    Birmingham Website Design

  11. Avatar for Kyle
    Kyle May 22nd, 2015

    Will either of these approaches work with full (i.e. not sub) domains? I want to use one MVC 5 application to run several smaller websites that all have their own domains.

  12. Avatar for Durga Prasad
    Durga Prasad June 13th, 2015

    Good post. Keep posting this kind of approaches known. Thanks.http://www.coetl.com/asp-net-mvc-t...

  13. Avatar for Shimmy
    Shimmy July 21st, 2015

    Thanks for sharing!
    Much nicer to have it all so simple and minified!

  14. Avatar for hbul
    hbul October 20th, 2015

    Nice post Maarten, I enjoy your work! I assume this is written entirely for the latest editions of ASP.NET MVC and ASP.NET. Is there a way to use this clean way of resolving tenants in earlier editions of MVC (5) you reckon?

  15. Avatar for Maarten Balliauw
    Maarten Balliauw October 20th, 2015
  16. Avatar for hbul
    hbul October 20th, 2015

    That's what I already use (albeit modified), but I am still not entirely pleased of the way it looks. The code in this post just looks so much more mature and integrated into the framework, and I was wondering if there is such a thing as middleware in MVC5? I am not sure where to incorporate it into.

  17. Avatar for Maarten Balliauw
    Maarten Balliauw October 20th, 2015

    You should be able to plug that in using Owin Middleware - here's an example - http://benfoster.io/blog/sa...

  18. Avatar for tiagoskaneta
    tiagoskaneta March 30th, 2016

    Thank you for sharing. I'd just like to point out that the first approach no longer works in RC1. This is due to a change in the TemplateMatcher class, which now expects a PathString to be passed to the Match method (instead of a string in a couple of versions ago). As a result of this change, I believe we're no longer able to use the default TemplateMatcher in this kind of application.

  19. Avatar for Jalpesh Vadgama
    Jalpesh Vadgama April 7th, 2016

    HI Maarten,

    Great blog post. Instead of gist could you please post whole project on github. Me and one of my friend want to learn and understand it better.

    Thanks for great work as always!

  20. Avatar for Alec
    Alec April 12th, 2016

    Hi Maarten, I am having difficulty getting your code to work, as I believe some methods you're using no longer exist, or have otherwise changed. Do you have an updated version of this concept?

    In the middleware example, 'context.SetFeature' (line 31) doesn't want tor resolve for me. In the first example, '_matcher.Match(requestHost);' (line 12) doesn't work because the method now expects a PathString, which does not include (and therefore cannot match) the host.

  21. Avatar for Andrew Sheppard
    Andrew Sheppard February 23rd, 2017

    Solving a tenancy problem now at work and was already thinking along these lines. Quite relieved to see your post on this as it really clears things up. Great work!

  22. Avatar for Anil
    Anil March 17th, 2017

    When working with AspNetCore i find issue... TemplateRoute is not found...
    Please suggest me how to fix??

  23. Avatar for Paul Cook
    Paul Cook June 20th, 2017

    Hey, I was wondering on how to resolve a tenancy issue and I just landed on you blog. Thanks a lot for sharing this information in a very simple way. Some awesome features explained in this ASP.NET MVC 6 post. http://ow.ly/lDTS30cJWMs

  24. Avatar for Sandeep D
    Sandeep D November 22nd, 2017

    MVC5 Dynamic domain should create with query string parameter pls help me