asp.net mvc 路由绑定域名

原文地址:https://gist.github.com/IDisposable/77f11c6f7693f9d181bb

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Http;
using System.Web.Http.Routing;
using System.Web.Mvc;
using System.Web.Routing;

namespace Alerts.Helpers
{
    internal static class DomainRegexCache
    {
        // since we're often going to have the same pattern used in multiple routes, it's best to build just one regex per pattern
        private static ConcurrentDictionary<string, Regex> _domainRegexes = new ConcurrentDictionary<string, Regex>();

        internal static Regex CreateDomainRegex(string domain)
        {
            return _domainRegexes.GetOrAdd(domain, (d) =>
            {
                d = d.Replace("/", @"\/")
                     .Replace(".", @"\.")
                     .Replace("-", @"\-")
                     .Replace("{", @"(?<")
                     .Replace("}", @">(?:[a-zA-Z0-9_-]+))");

                return new Regex("^" + d + "$", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture);
            });
        }
    }

    public class DomainRoute : Route
    {
        private const string DomainRouteMatchKey = "DomainRoute.Match";
        private const string DomainRouteInsertionsKey = "DomainRoute.Insertions";

        private Regex _domainRegex;
        public string Domain { get; private set; }

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

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

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

        public DomainRoute(string domain, string url, RouteValueDictionary defaults, IRouteHandler routeHandler)
            : base(url, defaults, routeHandler)
        {
            Domain = domain;
            _domainRegex = DomainRegexCache.CreateDomainRegex(Domain);
        }

        public override RouteData GetRouteData(HttpContextBase httpContext)
        {
            var requestDomain = httpContext.Request.Url.Host;

            var domainMatch = _domainRegex.Match(requestDomain);
            if (!domainMatch.Success)
                return null;

            var existingMatch = httpContext.Items[DomainRouteMatchKey] as string;

            if (existingMatch == null)
                httpContext.Items[DomainRouteMatchKey] = Domain;
            else if (existingMatch != Domain)
                return null;

            var data = base.GetRouteData(httpContext);
            if (data == null)
                return null;

            var myInsertions = new HashSet<string>();

            for (var i = 1; i < domainMatch.Groups.Count; i++)
            {
                var group = domainMatch.Groups[i];
                if (group.Success)
                {
                    var key = _domainRegex.GroupNameFromNumber(i);
                    if (!String.IsNullOrEmpty(key) && !String.IsNullOrEmpty(group.Value))
                    {
                        // could throw here if data.Values.ContainsKey(key) if we wanted to prevent multiple matches
                        data.Values[key] = group.Value;
                        myInsertions.Add(key);
                    }
                }
            }

            httpContext.Items[DomainRouteInsertionsKey] = myInsertions;
            return data;
        }

        public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
        {
            return base.GetVirtualPath(requestContext, RemoveDomainTokens(requestContext, values));
        }

        private RouteValueDictionary RemoveDomainTokens(RequestContext requestContext, RouteValueDictionary values)
        {
            var myInsertions = requestContext.HttpContext.Items[DomainRouteInsertionsKey] as HashSet<string>;

            if (myInsertions != null)
            {
                foreach (var key in myInsertions)
                {
                    if (values.ContainsKey(key))
                        values.Remove(key);
                }
            }

            return values;
        }
    }

    // For MVC routes
    public class DomainRouteCollection
    {
        private string Domain { get; set; }
        private RouteCollection Routes { get; set; }

        public DomainRouteCollection(string domain, RouteCollection routes)
        {
            Domain = domain;
            Routes = routes;
        }

        public Route MapRoute(string name, string url)
        {
            return MapRoute(name, url, null, null, null);
        }

        public Route MapRoute(string name, string url, object defaults)
        {
            return MapRoute(name, url, defaults, null, null);
        }

        public Route MapRoute(string name, string url, string[] namespaces)
        {
            return MapRoute(name, url, null, null, namespaces);
        }

        public Route MapRoute(string name, string url, object defaults, object constraints)
        {
            return MapRoute(name, url, defaults, constraints, null);
        }

        public Route MapRoute(string name, string url, object defaults, string[] namespaces)
        {
            return MapRoute(name, url, defaults, null, namespaces);
        }

        public Route MapRoute(string name, string url, object defaults, object constraints, string[] namespaces)
        {
            if (name == null)
                throw new ArgumentNullException("name");
            if (url == null)
                throw new ArgumentNullException("url");

            var route = new DomainRoute(Domain, url, defaults, new MvcRouteHandler())
            {
                Constraints = new RouteValueDictionary(constraints),
                DataTokens = new RouteValueDictionary()
            };

            if (namespaces != null && namespaces.Length > 0)
                route.DataTokens["Namespaces"] = namespaces;

            Routes.Add(name, route);

            return route;
        }
    }

    // For Areas routes
    public class DomainAreaRegistrationContext
    {
        private AreaRegistrationContext Context { get; set; }
        private DomainRouteCollection Routes { get; set; }

        public DomainAreaRegistrationContext(string domain, AreaRegistrationContext context)
        {
            Context = context;
            Routes = new DomainRouteCollection(domain, Context.Routes);
        }

        public Route MapRoute(string name, string url)
        {
            return MapRoute(name, url, null, null, null);
        }

        public Route MapRoute(string name, string url, object defaults)
        {
            return MapRoute(name, url, defaults, null, null);
        }

        public Route MapRoute(string name, string url, string[] namespaces)
        {
            return MapRoute(name, url, null, null, namespaces);
        }

        public Route MapRoute(string name, string url, object defaults, object constraints)
        {
            return MapRoute(name, url, defaults, constraints, null);
        }

        public Route MapRoute(string name, string url, object defaults, string[] namespaces)
        {
            return MapRoute(name, url, defaults, null, namespaces);
        }

        public Route MapRoute(string name, string url, object defaults, object constraints, string[] namespaces)
        {
            if (namespaces == null && Context.Namespaces != null)
                namespaces = Context.Namespaces.ToArray();

            var route = Routes.MapRoute(name, url, defaults, constraints, namespaces);
            route.DataTokens["area"] = Context.AreaName;

            // disabling the namespace lookup fallback mechanism keeps this area from accidentally picking up
            // controllers belonging to other areas
            bool useNamespaceFallback = (namespaces == null || namespaces.Length == 0);
            route.DataTokens["UseNamespaceFallback"] = useNamespaceFallback;

            return route;
        }
    }

    // WebApi Routes
    public class DomainHttpRoute : HttpRoute
    {
        private const string DomainRouteMatchKey = "DomainHttpRoute.Match";
        private const string DomainRouteInsertionsKey = "DomainHttpRoute.Insertions";

        private Regex _domainRegex;
        public string Domain { get; private set; }

        public DomainHttpRoute(string domain)
            : this(domain, (string)null, (HttpRouteValueDictionary)null, (HttpRouteValueDictionary)null, (HttpRouteValueDictionary)null, (HttpMessageHandler)null)
        {
        }

        public DomainHttpRoute(string domain, string routeTemplate)
            : this(domain, routeTemplate, (HttpRouteValueDictionary)null, (HttpRouteValueDictionary)null, (HttpRouteValueDictionary)null, (HttpMessageHandler)null)
        {
        }

        public DomainHttpRoute(string domain, string routeTemplate, HttpRouteValueDictionary defaults)
            : this(domain, routeTemplate, defaults, (HttpRouteValueDictionary)null, (HttpRouteValueDictionary)null, (HttpMessageHandler)null)
        {
        }

        public DomainHttpRoute(string domain, string routeTemplate, HttpRouteValueDictionary defaults, HttpRouteValueDictionary constraints)
            : this(domain, routeTemplate, defaults, constraints, (HttpRouteValueDictionary)null, (HttpMessageHandler)null)
        {
        }

        public DomainHttpRoute(string domain, string routeTemplate, HttpRouteValueDictionary defaults, HttpRouteValueDictionary constraints, HttpRouteValueDictionary dataTokens)
            : this(domain, routeTemplate, defaults, constraints, dataTokens, (HttpMessageHandler)null)
        {
        }

        public DomainHttpRoute(string domain, string routeTemplate, HttpRouteValueDictionary defaults, HttpRouteValueDictionary constraints, HttpRouteValueDictionary dataTokens, HttpMessageHandler handler)
            : base(routeTemplate, defaults, constraints, dataTokens, handler)
        {
            Domain = domain;
            _domainRegex = DomainRegexCache.CreateDomainRegex(Domain);
        }

        public override IHttpRouteData GetRouteData(string virtualPathRoot, HttpRequestMessage request)
        {
            var requestDomain = request.RequestUri.Host;

            var domainMatch = _domainRegex.Match(requestDomain);
            if (!domainMatch.Success)
                return null;

            object existingMatch;
            if (!request.Properties.TryGetValue(DomainRouteMatchKey, out existingMatch))
                request.Properties[DomainRouteMatchKey] = Domain;
            else if (Domain != existingMatch as string)
                return null;

            var data = base.GetRouteData(virtualPathRoot, request);
            if (data == null)
                return null;

            var myInsertions = new HashSet<string>();

            for (var i = 1; i < domainMatch.Groups.Count; i++)
            {
                var group = domainMatch.Groups[i];
                if (group.Success)
                {
                    var key = _domainRegex.GroupNameFromNumber(i);
                    if (!String.IsNullOrEmpty(key) && !String.IsNullOrEmpty(group.Value))
                    {
                        // could throw here if data.Values.ContainsKey(key) if we wanted to prevent multiple matches
                        data.Values[key] = group.Value;
                        myInsertions.Add(key);
                    }
                }
            }

            request.Properties[DomainRouteInsertionsKey] = myInsertions;
            return data;
        }

        public override IHttpVirtualPathData GetVirtualPath(HttpRequestMessage request, IDictionary<string, object> values)
        {
            return base.GetVirtualPath(request, RemoveDomainTokens(request, values));
        }

        private IDictionary<string, object> RemoveDomainTokens(HttpRequestMessage request, IDictionary<string, object> values)
        {
            var myInsertions = request.Properties[DomainRouteInsertionsKey] as HashSet<string>;

            if (myInsertions != null)
            {
                foreach (var key in myInsertions)
                {
                    if (values.ContainsKey(key))
                        values.Remove(key);
                }
            }

            return values;
        }
    }

    // for WebApi routes
    public class DomainHttpRouteCollection
    {
        private string Domain { get; set; }
        private HttpRouteCollection Routes { get; set; }

        public DomainHttpRouteCollection(string domain, HttpRouteCollection routes)
        {
            Domain = domain;
            Routes = routes;
        }

        public IHttpRoute MapDomainHttpRoute(string name, string routeTemplate)
        {
            return MapDomainHttpRoute(name, routeTemplate, null, null, null);
        }

        public IHttpRoute MapDomainHttpRoute(string name, string routeTemplate, object defaults)
        {
            return MapDomainHttpRoute(name, routeTemplate, defaults, null, null);
        }

        public IHttpRoute MapDomainHttpRoute(string name, string routeTemplate, object defaults, object constraints)
        {
            return MapDomainHttpRoute(name, routeTemplate, defaults, constraints, null);
        }

        public IHttpRoute MapDomainHttpRoute(string name, string routeTemplate, object defaults, object constraints, HttpMessageHandler handler)
        {
            if (name == null)
                throw new ArgumentNullException("name");
            if (routeTemplate == null)
                throw new ArgumentNullException("routeTemplate");

            var route = new DomainHttpRoute(Domain, routeTemplate, new HttpRouteValueDictionary(defaults), new HttpRouteValueDictionary(constraints));

            Routes.Add(name, route);

            return route;
        }
    }
}

使用方法:

using System.Web.Mvc;
using YourApplication;

namespace YourApplication.Areas.Login
{
    public class LoginAreaRegistration : AreaRegistration
    {
        public override string AreaName
        {
            get
            {
                return "Login";
            }
        }

        public override void RegisterArea(AreaRegistrationContext context)
        {
            var loginAreaContext = new DomainAreaRegistrationContext("login" + MvcApplication.DnsSuffix, context);

            loginAreaContext.MapRoute(
                name: "Login",
                url: "",
                defaults: new { controller = "Login", action = "Login", id = UrlParameter.Optional }
            );
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace YourApplication
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            var clientRoutes = new DomainRouteCollection("{clientHost}" + MvcApplication.DnsSuffix, routes);

            RegisterRootRoutes(clientRoutes);
        }
        
        private static void RegisterRootRoutes(DomainRouteCollection routes)
        {
            routes.MapRoute(
                name: "Root/Index",
                url: "",
                defaults: new { controller = "Root", action = "Index" }
            );
        }
    }
}
using System.Web.Http;
using System.Web.Http.ExceptionHandling;
using System.Web.Http.ModelBinding;
using YourApi;

namespace YourApi
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            var clientApiRoutes = new DomainHttpRouteCollection("{clientHost}" + MvcApplication.ClientSuffix, config.Routes);

            clientApiRoutes.MapDomainHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/v1/{controller}/{action}"
            );
        }
    }
}