Is it possible to make an ASP.NET MVC route based on a subdomain?

Viewed 78581

Is it possible to have an ASP.NET MVC route that uses subdomain information to determine its route? For example:

  • user1.domain.example goes to one place
  • user2.domain.example goes to another?

Or, can I make it so both of these go to the same controller/action with a username parameter?

10 Answers

You can do it by creating a new route and adding it to the routes collection in RegisterRoutes in your global.asax. Below is a very simple example of a custom Route:

public class ExampleRoute : RouteBase
{

    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        var url = httpContext.Request.Headers["HOST"];
        var index = url.IndexOf(".");

        if (index < 0)
            return null;

        var subDomain = url.Substring(0, index);

        if (subDomain == "user1")
        {
            var routeData = new RouteData(this, new MvcRouteHandler());
            routeData.Values.Add("controller", "User1"); //Goes to the User1Controller class
            routeData.Values.Add("action", "Index"); //Goes to the Index action on the User1Controller

            return routeData;
        }

        if (subDomain == "user2")
        {
            var routeData = new RouteData(this, new MvcRouteHandler());
            routeData.Values.Add("controller", "User2"); //Goes to the User2Controller class
            routeData.Values.Add("action", "Index"); //Goes to the Index action on the User2Controller

            return routeData;
        }

        return null;
    }

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
        //Implement your formating Url formating here
        return null;
    }
}

Yes but you have to create your own route handler.

Typically the route is not aware of the domain because the application could be deployed to any domain and the route would not care one way or another. But in your case you want to base the controller and action off the domain, so you will have to create a custom route that is aware of the domain.

I created library for subdomain routing which you can create such a route. It is working currently for a .NET Core 1.1 and .NET Framework 4.6.1 but will be updated in near future. This is how is it working:
1) Map subdomain route in Startup.cs

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    var hostnames = new[] { "localhost:54575" };

    app.UseMvc(routes =>
    {
        routes.MapSubdomainRoute(
            hostnames,
            "SubdomainRoute",
            "{username}",
            "{controller}/{action}",
            new { controller = "Home", action = "Index" });
    )};

2) Controllers/HomeController.cs

public IActionResult Index(string username)
{
    //code
}

3) That lib will also allow you to generate URLs and forms. Code:

@Html.ActionLink("User home", "Index", "Home" new { username = "user1" }, null)

Will generate <a href="http://user1.localhost:54575/Home/Index">User home</a> Generated URL will also depend on current host location and schema.
You can also use html helpers for BeginForm and UrlHelper. If you like you can also use new feature called tag helpers (FormTagHelper, AnchorTagHelper)
That lib does not have any documentation yet, but there are some tests and samples project so feel free to explore it.

In ASP.NET Core, the host is available via Request.Host.Host. If you want to allow overriding the host via a query parameter, first check Request.Query.

To cause a host query parameter to propagate into to new route-based URLs, add this code to the app.UseMvc route configuration:

routes.Routes.Add(new HostPropagationRouter(routes.DefaultHandler));

And define HostPropagationRouter like this:

/// <summary>
/// A router that propagates the request's "host" query parameter to the response.
/// </summary>
class HostPropagationRouter : IRouter
{
    readonly IRouter router;

    public HostPropagationRouter(IRouter router)
    {
        this.router = router;
    }

    public VirtualPathData GetVirtualPath(VirtualPathContext context)
    {
        if (context.HttpContext.Request.Query.TryGetValue("host", out var host))
            context.Values["host"] = host;
        return router.GetVirtualPath(context);
    }

    public Task RouteAsync(RouteContext context) => router.RouteAsync(context);
}

If you are looking at giving MultiTenancy capabilities to your project with different domains/subdomains for each tenant, you should have a look at SaasKit:

https://github.com/saaskit/saaskit

Code examples can be seen here: http://benfoster.io/blog/saaskit-multi-tenancy-made-easy

Some examples using ASP.NET core: http://andrewlock.net/forking-the-pipeline-adding-tenant-specific-files-with-saaskit-in-asp-net-core/

EDIT: If you do no want to use SaasKit in your ASP.NET core project you can have a look at Maarten's implementation of domain routing for MVC6: https://blog.maartenballiauw.be/post/2015/02/17/domain-routing-and-resolving-current-tenant-with-aspnet-mvc-6-aspnet-5.html

However those Gists are not maintained and need to be tweaked to work with the latest release of ASP.NET core.

Direct link to the code: https://gist.github.com/maartenba/77ca6f9cfef50efa96ec#file-domaintemplateroutebuilderextensions-cs

Few month ago I have developed an attribute that restricts methods or controllers to specific domains.

It is quite easy to use:

[IsDomain("localhost","example.com","www.example.com","*.t1.example.com")]
[HttpGet("RestrictedByHost")]
public IActionResult Test(){}

You can also apply it directly on a controller.

public class IsDomainAttribute : Attribute, Microsoft.AspNetCore.Mvc.Filters.IAuthorizationFilter
{

    public IsDomainAttribute(params string[]  domains)
    {
        Domains = domains;
    }

    public string[] Domains { get; }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var host = context.HttpContext.Request.Host.Host;
        if (Domains.Contains(host))
            return;
        if (Domains.Any(d => d.EndsWith("*"))
                && Domains.Any(d => host.StartsWith(d.Substring(0, d.Length - 1))))
            return;
        if (Domains.Any(d => d.StartsWith("*"))
                && Domains.Any(d => host.EndsWith(d.Substring(1))))
            return;

        context.Result = new Microsoft.AspNetCore.Mvc.NotFoundResult();//.ChallengeResult
    }
}

Restriction: you may not be able to have two same routes on different methods with different filters I mean the following may throw an exception for duplicate route:

[IsDomain("test1.example.com")]
[HttpGet("/Test")]
public IActionResult Test1(){}

[IsDomain("test2.example.com")]
[HttpGet("/Test")]
public IActionResult Test2(){}
Related