MVC Custom Routing Subdomain

狂风中的少年 提交于 2019-12-29 09:12:18

问题


I'm trying to build a "Tenant" Subdomain route that attaches to a MVC Area. In this case I have an Area called "Tenant" which has two controllers; Public and Admin. My custom Route is used to grab the Subdomain if it matches then route them to the proper Controller-Action-Area.

The base of this project came from the following http://www.matrichard.com/post/asp.net-mvc-5-routing-with-subdomain

The problem I'm having is in the custom Subdomain Route. When I hit the Public/Index Route, the routeData is returning null and I see the following error. Although if the route is /admin it returns the correct routeData.

Server Error in '/' Application.

The matched route does not include a 'controller' route value, which is required.

It also seems to be always matching using RouteDebugger tool, is this a clue to my problem?

Examples Routes:

controller=Public action=Index, area=Tenant

http://tenant1.mydomain.com:8080/

http://tenant1.mydomain.com:8080/logon

controller=Admin action=Index, area=Tenant

http://tenant1.mydomain.com:8080/admin

http://tenant1.mydomain.com:8080/admin/edit

--

SubdomainRouteP.cs

public class SubdomainRouteP : Route
{
    public string Domain { get; set; }

    public SubdomainRouteP(string domain, string url, RouteValueDictionary defaults): this(domain, url, defaults, new MvcRouteHandler())
    {
    }

    public SubdomainRouteP(string domain, string url, object defaults): this(domain, url, new RouteValueDictionary(defaults), new MvcRouteHandler())
    {
    }

    public SubdomainRouteP(string domain, string url, object defaults, IRouteHandler routeHandler): this(domain, url, new RouteValueDictionary(defaults), routeHandler)
    {
    }

    public SubdomainRouteP(string domain, string url, RouteValueDictionary defaults, IRouteHandler routeHandler): base(url, defaults, routeHandler)
    {
        this.Domain = domain;
    }

    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        // 
        // routeData object returns null in some cases 
        // 
        var routeData = base.GetRouteData(httpContext);

        var subdomain = httpContext.Request.Url.Host.Split('.').First();

        string[] blacklist = { "www", "mydomain", "localhost" };

        // This will ignore anything that is not a client tenant prefix
        if (blacklist.Contains(subdomain))
        {
            return null; // Continue to the next route
        }

        // Why is this NULL?
        if (routeData == null)
        {

            routeData = new RouteData(this, new MvcRouteHandler());

        }

        routeData.DataTokens["Area"] = "Tenant";
        routeData.DataTokens["UseNamespaceFallback"] = bool.FalseString;
        routeData.Values.Add("subdomain", subdomain);

        // IMPORTANT: Always return null if there is no match.
        // This tells .NET routing to check the next route that is registered.
        return routeData;
    }

}

RouteConfig.cs

        routes.Add("Admin_Subdomain", new SubdomainRouteP(
            "{client}.mydomain.com", //of course this should represent the real intent…like I said throwaway demo project in local IIS
            "admin/{action}/{id}",
            new { controller = "Admin", action = "Index", id = UrlParameter.Optional }));

        routes.Add("Public_Subdomain", new SubdomainRouteP(
            "{client}.mydomain.com", //of course this should represent the real intent…like I said throwaway demo project in local IIS
            "{controller}/{action}/{id}",
            new { controller = "Public", action = "Index", id = UrlParameter.Optional }));

        // This is the MVC default Route
        routes.MapRoute(
            "Default",
            "{controller}/{action}/{id}",
            new { controller = "Home", action = "Index", id = UrlParameter.Optional });

The Url below gives me the following results from RouteDebugger. During test 1 and 2 the route still matches /admin.

Failed Test 1: http://tenant.mydomain.com/

Failed Test 2: http://tenant.mydomain.com/logon

Successful 3: http://tenant.mydomain.com/admin

Matches Url Defaults

True admin/{action}/{id} controller = Admin, action = Index

True {controller}/{action}/{id} controller = Public, action = Index


回答1:


The post that you linked to has a bug: When a constraint or the URL does not match, the base.GetRouteData method will return null. In this case, adding the subdomain name to the route dictionary will obviously throw an exception. There should be a null guard clause before that line.

public override RouteData GetRouteData(HttpContextBase httpContext)
{
    var routeData = base.GetRouteData(httpContext);
    if (routeData != null)
    {
        routeData.Values.Add("client", httpContext.Request.Url.Host.Split('.').First());
    }
    return routeData;
}

As should be the case with your route. You need to ensure you return null in the case where the base class returns null (which indicates either the URL or a constraint did not match, and we need to skip processing this route).

Also, I am not sure if it makes any difference than adding the data directly to the DataTokens, but the MVC framework has an IRouteWithArea that can be implemented to configure the Area the route applies to.

public class SubdomainRouteP : Route, IRouteWithArea
{
    public string Area { get; private set; }

    public SubdomainRouteP(string area, string url, RouteValueDictionary defaults): this(area, url, defaults, new MvcRouteHandler())
    {
    }

    public SubdomainRouteP(string area, string url, object defaults): this(area, url, new RouteValueDictionary(defaults), new MvcRouteHandler())
    {
    }

    public SubdomainRouteP(string area, string url, object defaults, IRouteHandler routeHandler): this(area, url, new RouteValueDictionary(defaults), routeHandler)
    {
    }

    public SubdomainRouteP(string area, string url, RouteValueDictionary defaults, IRouteHandler routeHandler): base(url, defaults, routeHandler)
    {
        this.Area = area;
    }

    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        var routeData = base.GetRouteData(httpContext);

        // This will ignore anything where the URL or a constraint doesn't match
        // in the call to base.GetRouteData().
        if (routeData != null)
        {
            var subdomain = httpContext.Request.Url.Host.Split('.').First();

            string[] blacklist = { "www", "mydomain", "localhost" };

            // This will ignore anything that is not a client tenant prefix
            if (blacklist.Contains(subdomain))
            {
                return null; // Continue to the next route
            }

            routeData.DataTokens["UseNamespaceFallback"] = bool.FalseString;
            routeData.Values.Add("subdomain", subdomain);
        }

        // IMPORTANT: Always return null if there is no match.
        // This tells .NET routing to check the next route that is registered.
        return routeData;
    }

}

I can't figure out what you are trying to do with the domain parameter. The URL will most likely return something for domain. So, it seems like you should have a constraint in the first "{controller}/{action}/{id}" route or you will never have a case that will pass through to the default route. Or, you could use an explicit segment in the URL so you can differentiate it (the same way you did with your admin route).

routes.Add("Admin_Subdomain", new SubdomainRouteP(
    "Tenant",
    "admin/{action}/{id}",
    new { controller = "Admin", action = "Index", id = UrlParameter.Optional }));

routes.Add("Public_Subdomain", new SubdomainRouteP(
    "Tenant",
    "public/{action}/{id}",
    new { controller = "Public", action = "Index", id = UrlParameter.Optional }));

// This is the MVC default Route
routes.MapRoute(
    "Default",
    "{controller}/{action}/{id}",
    new { controller = "Home", action = "Index", id = UrlParameter.Optional });

Another option would be to add another constructor parameter to pass in an explicit list of valid domains to check against.



来源:https://stackoverflow.com/questions/35441583/mvc-custom-routing-subdomain

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!