【问题标题】:Is it possible to make an ASP.NET MVC route based on a subdomain?是否可以基于子域创建 ASP.NET MVC 路由?
【发布时间】:2013-12-27 19:51:08
【问题描述】:

是否可以有一个使用子域信息来确定其路由的 ASP.NET MVC 路由?例如:

  • user1.domain.com 转到一个地方
  • user2.domain.com 转到另一个?

或者,我可以让它们都使用username 参数进入同一个控制器/动作吗?

【问题讨论】:

  • 我为多租户应用程序实现了类似的东西,但使用抽象基础控制器而不是自定义路由类。我的博文是here
  • 一定要考虑这种方法:http://blog.tonywilliams.me.uk/asp-net-mvc-2-routing-subdomains-to-areas 我发现在我的应用程序中引入多租户比其他答案更好,因为 MVC 区域是引入特定于租户的控制器和视图的好方法以有组织的方式。
  • @trebormf - 我认为您应该将其添加为答案,这就是我最终用作解决方案的基础。
  • @Shagglez - 谢谢。这是一个答案,但版主出于我无法理解的原因将其转换为评论。
  • 托尼的喜欢坏了。这是对我有用的一个:blog.tonywilliams.me.uk/…

标签: asp.net-mvc routing asp.net-mvc-routing


【解决方案1】:

几个月前,我开发了一个将方法或控制器限制在特定域中的属性。

它很容易使用:

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

您也可以直接将其应用到控制器上。

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

限制: 您可能无法在具有不同过滤器的不同方法上拥有两条相同的路线 我的意思是以下可能会引发重复路由的异常:

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

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

【讨论】:

    【解决方案2】:

    我创建了library for subdomain routing,您可以创建这样的路线。它目前适用于 .NET Core 1.1 和 .NET Framework 4.6.1,但将在不久的将来进行更新。它是这样工作的:
    1) 在 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) 控制器/HomeController.cs

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

    3) 该库还允许您生成 URL 和表单。代码:

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

    将生成<a href="http://user1.localhost:54575/Home/Index">User home</a> 生成的 URL 还将取决于当前主机位置和架构。
    您还可以为BeginFormUrlHelper 使用html 助手。如果您愿意,还可以使用称为标签助手的新功能(FormTagHelperAnchorTagHelper
    该库还没有任何文档,但有一些测试和示例项目,因此请随意探索。

    【讨论】:

      【解决方案3】:

      ASP.NET Core 中,主机可通过 Request.Host.Host 获得。如果你想允许通过查询参数覆盖主机,首先检查Request.Query

      要使主机查询参数传播到新的基于路由的 URL,请将此代码添加到 app.UseMvc 路由配置:

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

      并像这样定义HostPropagationRouter

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

      【讨论】:

        【解决方案4】:

        如果您正在考虑为您的项目提供 MultiTenancy 功能,并为每个租户使用不同的域/子域,您应该看看 SaasKit:

        https://github.com/saaskit/saaskit

        代码示例可以看这里:http://benfoster.io/blog/saaskit-multi-tenancy-made-easy

        使用 ASP.NET 核心的一些示例:http://andrewlock.net/forking-the-pipeline-adding-tenant-specific-files-with-saaskit-in-asp-net-core/

        编辑: 如果您不想在 ASP.NET 核心项目中使用 SaasKit,您可以查看 Maarten 为 MVC6 实现的域路由:https://blog.maartenballiauw.be/post/2015/02/17/domain-routing-and-resolving-current-tenant-with-aspnet-mvc-6-aspnet-5.html

        但是,这些 Gist 没有得到维护,需要进行调整才能与最新版本的 ASP.NET 核心一起使用。

        直接链接代码:https://gist.github.com/maartenba/77ca6f9cfef50efa96ec#file-domaintemplateroutebuilderextensions-cs

        【讨论】:

        • 不寻找多租户——但感谢您的提示!
        【解决方案5】:

        在定义一个新的路由处理程序来查看在 URL 中传递的主机之后,您可以采用一个基本控制器的想法,该控制器知道它正在被访问的站点。它看起来像这样:

        public abstract class SiteController : Controller {
            ISiteProvider _siteProvider;
        
            public SiteController() {
                _siteProvider = new SiteProvider();
            }
        
            public SiteController(ISiteProvider siteProvider) {
                _siteProvider = siteProvider;
            }
        
            protected override void Initialize(RequestContext requestContext) {
                string[] host = requestContext.HttpContext.Request.Headers["Host"].Split(':');
        
                _siteProvider.Initialise(host[0]);
        
                base.Initialize(requestContext);
            }
        
            protected override void OnActionExecuting(ActionExecutingContext filterContext) {
                ViewData["Site"] = Site;
        
                base.OnActionExecuting(filterContext);
            }
        
            public Site Site {
                get {
                    return _siteProvider.GetCurrentSite();
                }
            }
        
        }
        

        ISiteProvider 是一个简单的接口:

        public interface ISiteProvider {
            void Initialise(string host);
            Site GetCurrentSite();
        }
        

        我推荐你去Luke Sampson Blog

        【讨论】:

          【解决方案6】:

          要在使用 Web API 时捕获子域,请覆盖操作选择器以注入 subdomain 查询参数。然后在控制器的操作中使用子域查询参数,如下所示:

          public string Get(string id, string subdomain)
          

          这种方法使调试方便,因为您可以在使用 localhost 时手动指定查询参数而不是实际的主机名(有关详细信息,请参阅standard MVC5 routing answer)。这是动作选择器的代码:

          class SubdomainActionSelector : IHttpActionSelector
          {
              private readonly IHttpActionSelector defaultSelector;
          
              public SubdomainActionSelector(IHttpActionSelector defaultSelector)
              {
                  this.defaultSelector = defaultSelector;
              }
          
              public ILookup<string, HttpActionDescriptor> GetActionMapping(HttpControllerDescriptor controllerDescriptor)
              {
                  return defaultSelector.GetActionMapping(controllerDescriptor);
              }
          
              public HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
              {
                  var routeValues = controllerContext.Request.GetRouteData().Values;
                  if (!routeValues.ContainsKey("subdomain")) {
                      string host = controllerContext.Request.Headers.Host;
                      int index = host.IndexOf('.');
                      if (index >= 0)
                          controllerContext.Request.GetRouteData().Values.Add("subdomain", host.Substring(0, index));
                  }
                  return defaultSelector.SelectAction(controllerContext);
              }
          }
          

          通过将其添加到WebApiConfig.Register 来替换默认的操作选择器:

          config.Services.Replace(typeof(IHttpActionSelector), new SubdomainActionSelector(config.Services.GetActionSelector()));
          

          【讨论】:

          • 是否有人遇到路由数据未显示在 Web API 控制器上的问题并检查控制器内的 Request.GetRouteData 未显示任何值?
          【解决方案7】:

          在保留标准 MVC5 路由功能的同时捕获子域,请使用从 Route 派生的以下 SubdomainRoute 类。

          此外,SubdomainRoute 允许选择将子域指定为 查询参数,使 sub.example.com/foo/barexample.com/foo/bar?subdomain=sub 等效。这允许您在配置 DNS 子域之前进行测试。查询参数(使用时)通过Url.Action等生成的新链接传播。

          查询参数还可以使用 Visual Studio 2013 进行本地调试,而无需 configure with netsh or run as Administrator。默认情况下,IIS Express 仅在非提升时绑定到 localhost;它不会绑定到像 sub.localtest.me 这样的同义主机名。

          class SubdomainRoute : Route
          {
              public SubdomainRoute(string url) : base(url, new MvcRouteHandler()) {}
          
              public override RouteData GetRouteData(HttpContextBase httpContext)
              {
                  var routeData = base.GetRouteData(httpContext);
                  if (routeData == null) return null; // Only look at the subdomain if this route matches in the first place.
                  string subdomain = httpContext.Request.Params["subdomain"]; // A subdomain specified as a query parameter takes precedence over the hostname.
                  if (subdomain == null) {
                      string host = httpContext.Request.Headers["Host"];
                      int index = host.IndexOf('.');
                      if (index >= 0)
                          subdomain = host.Substring(0, index);
                  }
                  if (subdomain != null)
                      routeData.Values["subdomain"] = subdomain;
                  return routeData;
              }
          
              public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
              {
                  object subdomainParam = requestContext.HttpContext.Request.Params["subdomain"];
                  if (subdomainParam != null)
                      values["subdomain"] = subdomainParam;
                  return base.GetVirtualPath(requestContext, values);
              }
          }
          

          为方便起见,从您的 RegisterRoutes 方法调用以下 MapSubdomainRoute 方法,就像您使用旧的 MapRoute 一样:

          static void MapSubdomainRoute(this RouteCollection routes, string name, string url, object defaults = null, object constraints = null)
          {
              routes.Add(name, new SubdomainRoute(url) {
                  Defaults = new RouteValueDictionary(defaults),
                  Constraints = new RouteValueDictionary(constraints),
                  DataTokens = new RouteValueDictionary()
              });
          }
          

          最后,为了方便地访问子域(从真正的子域或查询参数),创建一个具有 Subdomain 属性的 Controller 基类会很有帮助:

          protected string Subdomain
          {
              get { return (string)Request.RequestContext.RouteData.Values["subdomain"]; }
          }
          

          【讨论】:

          • 我更新了代码以使子域始终可用作路由值。这简化了对子域的访问。
          • 我喜欢这个。非常简单,对我的项目来说绰绰有余。
          • 这是一个很好的答案。有没有办法使用路由属性?我正在尝试使这项工作适用于“subdomain.domain.com/portal/register”之类的路径,并且使用属性会使这更容易。
          • @perfect_element - 属性路由不像基于约定的路由那样可扩展。这样做的唯一方法是构建自己的属性路由系统。
          【解决方案8】:

          这不是我的工作,但我必须将其添加到此答案中。

          这是解决此问题的好方法。 Maartin Balliauw 编写了创建 DomainRoute 类的代码,该类的使用与普通路由非常相似。

          http://blog.maartenballiauw.be/post/2009/05/20/ASPNET-MVC-Domain-Routing.aspx

          示例使用是这样的......

          routes.Add("DomainRoute", new DomainRoute( 
              "{customer}.example.com", // Domain with parameters 
              "{action}/{id}",    // URL with parameters 
              new { controller = "Home", action = "Index", id = "" }  // Parameter defaults 
          ))
          

          ;

          【讨论】:

          • 这个解决方案有问题。假设您想将子域作为不同的用户处理: routes.Add("SD", new DomainRoute("user}.localhost", "", new { controller = "Home", action = "IndexForUser", user="u1 " } ));它也缓存主页。这是因为生成的正则表达式。为了解决这个问题,您可以复制 DomainRoute.cs 中的 CreateRegex 方法,将其命名为 CreateDomainRegex,将此行上的 * 更改为 +: source = source.Replace("}", @">([a- zA-Z0-9_]*))");并在 GetRouteData 方法中对域 regx 使用此新方法: domainRegex = CreateDomainRegex(Domain);
          • 我不知道为什么我无法运行此代码...我只收到SERVER NOT FOUND 错误...表示该代码不适合我...您是否设置了任何其他配置什么的?!
          • 我已经创建了我的这个gist.github.com/IDisposable/77f11c6f7693f9d181bb版本的要点
          • @IDisposable 什么是 MvcApplication.DnsSuffix?
          • 我们只是在 web.config 中公开基本 DNS 域...典型值是 .example.org
          【解决方案9】:

          您可以通过创建新路由并将其添加到 global.asax 中 RegisterRoutes 中的路由集合来实现。下面是一个非常简单的自定义 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;
              }
          }
          

          【讨论】:

          • 感谢您提供详细的示例,但我没有关注如何从 Global.asax 执行 .Add。
          • 我调用了路由 SubdomainRoute 并将其添加为第一条路由,如下所示:routes.Add(new SubdomainRoute());
          • 这种方法是否需要对可能的子域列表进行硬编码?
          • 不,您可以添加一个名为“子域”之类的数据库字段,您将是您期望子域为特定用户或其他任何内容的数据库字段,然后只需查找子域。
          • 有人可以推荐一个网络表单版本吗?
          【解决方案10】:

          是的,但您必须创建自己的路由处理程序。

          通常路由不知道域,因为应用程序可以部署到任何域,并且路由不会关心任何一种方式。但在您的情况下,您希望控制器和操作基于域,因此您必须创建一个知道域的自定义路由。

          【讨论】:

            猜你喜欢
            • 2011-06-19
            • 2013-01-15
            • 2016-12-01
            • 1970-01-01
            • 1970-01-01
            • 2015-01-16
            • 1970-01-01
            相关资源
            最近更新 更多