有可能制作一个ASP。NET MVC路由基于子域?

有没有可能有一个ASP。NET MVC路由,使用子域信息来确定它的路由?例如:

  • < >强user1 < / >强.domain.example去一个地方
  • < >强user2 < / >强.domain.example去另一个?

或者,我是否可以让它们都使用username参数指向相同的控制器/动作?

78978 次浏览

是的,但是你必须创建你自己的路由处理器。

通常情况下,路由不知道域,因为应用程序可以部署到任何域,路由不会关心这种或那种方式。但在你的情况下,你想要基于域的控制器和动作,所以你必须创建一个自定义路由,它可以感知域。

你可以通过创建一个新路由并将其添加到global.asax中的RegisterRoutes中的routes集合来实现。下面是一个自定义路由的简单例子:

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

这不是我的工作,但我必须把它加到这个答案上。

这里有一个解决这个问题的好办法。martin Balliauw编写了创建DomainRoute类的代码,该类可以与正常的路由使用非常相似。

< a href = " http://blog.maartenballiauw.be/post/2009/05/20/ASPNET-MVC-Domain-Routing。aspx noreferrer“rel = > 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
))

对于捕获子域,同时保留标准MVC5路由特性,使用以下派生自RouteSubdomainRoute类。

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

查询参数还允许使用Visual Studio 2013进行本地调试,而无需使用netsh配置或以管理员身份运行。默认情况下,IIS Express仅在未提升时绑定到本地主机;它不会绑定到同义主机名,如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"]; }
}

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

public string Get(string id, string subdomain)

这种方法使调试变得方便,因为当使用本地主机而不是实际的主机名时,可以手动指定查询参数(详细信息请参阅标准MVC5路由应答)。这是动作选择器的代码:

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

在定义一个新的Route处理程序后,它将查看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博客

如果你正在考虑为你的项目提供多租户功能,为每个租户提供不同的域/子域,你应该看看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/

< p >编辑: 如果你不想在你的ASP中使用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的最新版本一起工作。净的核心。

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

ASP。网络核心中,主机通过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);
}
我创建了用于子域路由的库,你可以创建这样的路由。它目前正在为。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 helper。如果你愿意,你也可以使用称为标签助手的新特性(FormTagHelperAnchorTagHelper)
该库还没有任何文档,但有一些测试和示例项目,所以请随意探索

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

它很容易使用:

[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
}
}
< p >限制: 你可能不能在不同的过滤器的不同方法上有两个相同的路由 我的意思是下面可能会抛出重复路由的异常:

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


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