ASP.NET Core 2.0 中基于域的路由

Domain-based routing in ASP.NET Core 2.0

我有一个 ASP.NET Core 2.0 应用托管在 Azure 应用服务上。

此应用程序绑定到 domainA.com。我的应用程序中有一条路线,例如 domainA.com/route.

现在,我想引入另一个域,但让它只响应不同的路由——例如,domainB.com

最好的方法是什么?

实现此目的的一种方法是制作一个 custom route constraint 来指定每个域名的路由功能。

域约束

    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);
        }
    }

用法

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?}");
});

请注意,如果您在 Visual Studio 中启动它,它将无法使用标准配置。为了在不更改配置的情况下轻松调试,您可以指定 URL 并将域作为查询字符串参数:

/route?domain=domaina.com

这只是为了让您不必重新配置 IIS 和本地主机文件即可进行调试(但如果您愿意,您仍然可以这样做)。在 Release 构建期间,此功能已被删除,因此它仅适用于生产中的 实际 域名。

由于默认情况下路由响应所有域名,因此只有在域之间共享大量功能时才有意义。如果没有,最好为每个域设置单独的区域:

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") }
);

对于 .net Core MVC,您可以创建一个新的 IRouteConstraint 和一个 RouteAttribute

=> IRouteConstraint.cs

using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace Microsoft.AspNetCore.Routing
{
    public class ConstraintHost : IRouteConstraint
    {

        public string _value { get; private set; }
        public ConstraintHost(string value)
        {
            _value = value;
        }

        public bool Match(HttpContext httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
        {
            string hostURL = httpContext.Request.Host.ToString();
            if (hostURL == _value)
            {
                return true;
            }
            //}
            return false;
            //return hostURL.IndexOf(_value, StringComparison.OrdinalIgnoreCase) >= 0;
        }

        public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
        {
            throw new NotImplementedException();
        }
    }
    public class ConstraintHostRouteAttribute : RouteAttribute
    {
        public ConstraintHostRouteAttribute(string template, string sitePermitted)
            : base(template)
        {
            SitePermitted = sitePermitted;
        }

        public RouteValueDictionary Constraints
        {
            get
            {
                var constraints = new RouteValueDictionary();
                constraints.Add("host", new ConstraintHost(SitePermitted));
                return constraints;
            }
        }

        public string SitePermitted
        {
            get;
            private set;
        }
    }
} 

并且在您的控制器中可以这样使用它:

    [ConstraintHostRoute("myroute1/xxx", "domaina.com")]
    [ConstraintHostRoute("myroute2/yyy", "domainb.com")]
    public async Task<ActionResult> MyController()
    {
      return View();
    }

Built-in方法

从 ASP.NET Core 3 开始——并且在 ASP.NET Core 5 和 6 中继续支持——你可以通过使用 RequireHost() extension method, as discussed in Allow routing to areas by hostname. (与问题标题相反,这不特定于区域。)

例子

因此,为了适应 @nightowl888's example in the accepted answer,您现在可以实现相同的结果而无需定义自定义 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?}"
  );

});

属性路由

或者,如果您更喜欢属性路由,as used in @yanga's approach, you can now use the new (but poorly documented) HostAttribute (source code):

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

显然,这并没有解决 ASP.NET 核心 2 的原始问题。但是,由于这是一个未(未)记录的功能,所以我想把它留在这里,以供尝试的人使用为 ASP.NET Core 3+.

解决这个问题