是否可以基于子域进行ASP.NET MVC路由?


235

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

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

或者,是否可以使它们都通过username参数进入相同的控制器/动作?


我为多租户应用程序实现了类似的操作,但是使用了抽象的基础Controller而不是自定义的Route类。我的博客文章在这里
路加·桑普森

6
请务必考虑使用这种方法:http : //blog.tonywilliams.me.uk/asp-net-mvc-2-routing-subdomains-to-areas我发现将多租户引入我的应用比其他答案更好,因为MVC区域是一种以有组织的方式引入特定于租户的控制器和视图的好方法。
trebormf 2011年

2
@trebormf-我认为您应该添加它作为答案,这就是我最终用作解决方案的基础。
Shagglez 2012年

@Shagglez-谢谢。这是一个答案,但主持人出于无法理解的原因将其转换为评论。
trebormf 2012年

5
托尼的样子被打破了。这是对我有用的网站
罗尼·奥弗比

Answers:


168

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

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

1
感谢您提供详细的示例,但我不关注如何从Global.asax执行.Add。
justSteve 2012年

4
我将路由称为SubdomainRoute,并将其添加为第一个路由,如下所示:routes.Add(new SubdomainRoute());
杰夫·汉德利

6
这种方法是否需要对可能的子域列表进行硬编码?
Maxim V. Pavlov 2012年

2
不,您可以添加一个名为“子域”之类的数据库字段,您将期望该子域适用于特定用户或其他用户,然后在子域上进行查找。
瑞安·海斯

1
有人可以推荐这个的Webforms版本吗?
MatthewT 2014年

52

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

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

查询参数还支持使用Visual Studio 2013进行本地调试,而无需配置netsh或以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);
    }
}

为了方便起见,请MapSubdomainRoute像平常一样从您的RegisterRoutes方法中调用以下方法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"]; }
}

1
我更新了代码,以使子域始终可用作路由值。这简化了对子域的访问。
爱德华·布雷

我喜欢这个。非常简单,对于我的项目来说绰绰有余。
不久2014年

这是一个很好的答案。有没有办法使用路由属性?我正在尝试使此工作适用于“ subdomain.domain.com/portal/register”之类的路径,并且使用属性会使此操作变得更容易。
perfect_element

@perfect_element-属性路由不能像基于约定的路由那样扩展。这样做的唯一方法是建立自己的属性路由系统。
NightOwl888 '17

23

这不是我的工作,但我必须在此答案上添加它。

这是解决这个问题的好方法。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 
))

;


5
该解决方案存在问题。假设您想将子域作为不同的用户来处理:route.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);
Gorkem Pacaci 2010年

我不知道为什么我不能运行此代码...我只是收到SERVER NOT FOUND错误...表示代码对我不起作用...您是否在设置其他配置或其他内容?
TJ博士2014年

我已经创建了此gist
IDisposable

1
@IDisposable什么是MvcApplication.DnsSuffix?
HaBo 2014年

我们只是在web.config中公开基本的DNS域...典型值是.example.org
IDisposable

4

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

public string Get(string id, string subdomain)

这种方法使调试方便,因为您可以在使用localhost而不是实际主机名时手动指定查询参数(有关详细信息,请参见标准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()));

有人在路由数据未出现在Web API控制器上并且检查控制器内部的Request.GetRouteData时是否遇到任何问题?
艾伦·麦克唐纳

3

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

通常,该路由不知道该域,因为该应用程序可以部署到任何域,并且该路由将不在乎一种或另一种方式。但是在您的情况下,您希望基于控制器并在域之外执行操作,因此您将必须创建一个了解域的自定义路由。


3

为子域路由创建了,您可以创建此类路由。目前,它适用于.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)Controllers / HomeController.cs

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

3)该lib也将允许您生成URL和表单。码:

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

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


2

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

1

在定义了一个新的Route处理程序以查看通过URL传递的主机之后,您可以采用一个基本Controller的想法,该Controller知道正在访问该Site的站点。看起来像这样:

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

我推荐你去 卢克·桑普森博客


1

如果要为每个租户提供具有不同域/子域的项目的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 用aspnet-mvc-6-aspnet-5.html路由和解决当前租户

但是,这些要点并未得到维护,需要进行调整才能与最新版本的ASP.NET Core一起使用。

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


不是在寻找多租户-谢谢您的提示!
丹·埃斯帕萨

0

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

它很容易使用:

[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(){}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.