Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Domain-based routing in ASP.NET Core 2.0

I have an ASP.NET Core 2.0 app hosted on an Azure App Service.

This application is bound to domainA.com. I have one route in my app—for example, domainA.com/route.

Now, I want to introduce another domain, but have it respond only to a different route—for example, domainB.com.

What is the best way to do this?

like image 586
Norbert Pisz Avatar asked Mar 20 '18 07:03

Norbert Pisz


People also ask

What is domain based routing?

Domain-based routing enables for calls to be routed on the outbound dialpeer based on the domain name or IP address provided in the request Uniform Resource Identifier (URI) of incoming Session IP message.

What is routing and how can you define routes in ASP.NET Core?

Routing is responsible for matching incoming HTTP requests and dispatching those requests to the app's executable endpoints. Endpoints are the app's units of executable request-handling code. Endpoints are defined in the app and configured when the app starts.

What is SpaProxy?

The SpaProxy package contains a target file that generates a spa. proxy. json file based on the settings in the project file during the build. An additional environment variable gets set in the launchSettings.


2 Answers

One way to accomplish this is to make a custom route constraint to specify which routes function for each domain name.

DomainConstraint

    public class DomainConstraint : IRouteConstraint
    {
        private readonly string[] domains;

        public DomainConstraint(params string[] domains)
        {
            this.domains = domains ?? throw new ArgumentNullException(nameof(domains));
        }

        public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
        {
            string domain =
#if DEBUG
                // A domain specified as a query parameter takes precedence 
                // over the hostname (in debug compile only).
                // This allows for testing without configuring IIS with a 
                // static IP or editing the local hosts file.
                httpContext.Request.Query["domain"];
#else
                null;
#endif
            if (string.IsNullOrEmpty(domain))
                domain = httpContext.Request.Host.Host;

            return domains.Contains(domain);
        }
    }

Usage

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "DomainA",
        template: "route",
        defaults: new { controller = "DomainA", action = "Route" },
        constraints: new { _ = new DomainConstraint("domaina.com") });

    routes.MapRoute(
        name: "DomainB",
        template: "route",
        defaults: new { controller = "DomainB", action = "Route" },
        constraints: new { _ = new DomainConstraint("domainb.com") });

    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");
});

Note that if you fire this up in Visual Studio it won't work with the standard configuration. To allow for easy debugging without changing the configuration, you can specify the URL with the domain as a query string parameter:

/route?domain=domaina.com

This is just so you don't have to reconfigure IIS and your local hosts file to debug (although you still can if you prefer that way). During a Release build this feature is removed so it will only work with the actual domain name in production.

Since routes respond to all domain names by default, it only makes sense to do it this way if you have a large amount of functionality that is shared between domains. If not, it is better to setup separate areas for each domain:

routes.MapRoute(
    name: "DomainA",
    template: "{controller=Home}/{action=Index}/{id?}",
    defaults: new { area = "DomainA" },
    constraints: new { _ = new DomainConstraint("domaina.com") }
);

routes.MapRoute(
    name: "DomainA",
    template: "{controller=Home}/{action=Index}/{id?}",
    defaults: new { area = "DomainB" },
    constraints: new { _ = new DomainConstraint("domainb.com") }
);
like image 130
NightOwl888 Avatar answered Oct 05 '22 20:10

NightOwl888


Built-in Approach

Since ASP.NET Core 3—and with continued support in ASP.NET Core 5 and 6—you can restrict individual route definitions to specific hostnames by using the RequireHost() extension method, as discussed in Allow routing to areas by hostname. (Contrary to the issue title, this isn't specific to areas.)

Example

So, to adapt @nightowl888's example in the accepted answer, you can now accomplish the same result without having to define a custom IRouteConstraint:

app.UseMvc(routes =>
{

  routes.MapRoute(
    name: "DomainA",
    template: "route",
    defaults: new { controller = "DomainA", action = "Route" }
  ).RequireHost("domaina.com");

  routes.MapRoute(
    name: "DomainB",
    template: "route",
    defaults: new { controller = "DomainB", action = "Route" }
  ).RequireHost("domainb.com");

  routes.MapRoute(
    name: "default",
    template: "{controller=Home}/{action=Index}/{id?}"
  );

});

Attribute Routing

Alternatively, if you prefer attribute routing, as used in @yanga's approach, you can now use the new (but poorly documented) HostAttribute (source code):

[Host("domainb.com")]
public DomainController: Controller
{
  … 
}

Obviously, this doesn't address the original problem, which was for ASP.NET Core 2. As this is an un(der)documented feature, however, I wanted to leave it here for people trying to solve this problem for ASP.NET Core 3+.

like image 28
Jeremy Caney Avatar answered Oct 05 '22 20:10

Jeremy Caney