将子域名请求路由到MVC区域
写了个扩展,分享给需要的朋友。
0x01 使用方法
在mvc区域中的{xxxx}AreaRegistration.cs文件中,如ProjectsAreaRegistration.cs
<pre>
<code>
using Dsvisual.Extensions;
namespace Dsvisual.WebApp.Areas.Projects
{
public class ProjectsAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Projects";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapSubdomain("projects", true);
}
}
}
</code>
</pre>
0x02 扩展功能源码
<pre><code>
/// <summary>
/// an extension for map subdomain request to a mvc area
/// </summary>
public static class RouteExtension
{
/// <summary>
/// 创建该区域(Area)的路由并将子域名(subdomain)的请求映射到该区域
/// </summary>
/// <param name="ctx"></param>
/// <param name="subdomain">子域名</param>
/// <param name="allowShortUrl">是否启用短地址(当请求地址位于默认控制器时支持控制器路径省略)</param>
public static void MapSubdomain(this AreaRegistrationContext ctx, string subdomain, bool allowShortUrl)
{
ctx.Routes.MapSubdomain(subdomain, ctx.AreaName, ctx.Namespaces.ToArray(), allowShortUrl);
}
/// <summary>
/// 映射子域名到对应的区域
/// map the subdomain requests to the specific mvc Area
/// </summary>
/// <param name="routes"></param>
/// <param name="subdomain">子域名,支持多级 如admin.info.domain.com 则子域名为admin.info</param>
/// <param name="areaName">MVC Area名称</param>
/// <param name="namespaces">MVC Area所在的命名空间</param>
/// <param name="allowShortUrl">是否启用短地址(当请求地址位于默认控制器时支持控制器路径省略)</param>
private static void MapSubdomain(this RouteCollection routes, string subdomain, string areaName, string[] namespaces, bool allowShortUrl)
{
var prefix = areaName + "_";
var defaults = new { controller = "home", action = "index", id = UrlParameter.Optional };
bool flag = namespaces == null || namespaces.Length == 0;
var dataTokens = new { area = areaName, Namespaces = namespaces, UseNamespaceFallback = flag };
object constraints = null;
//通过配置两个路由来映射短路由和标准路由
//这里URL不带路由前缀目的是避免映射为二级域名后Html.ActionLink等输出路由前缀
//因为MVC区域默认是Projects/{controller}/{action}/{id}这样的格式,
//这样会导致Html.ActionLink等生成project.domain.com/project/home/index这样的路径
//而我们需要的是project.domain.com/home/index这样的格式
if (allowShortUrl)
{
//短路径输出支持路由默认控制器设置和当前调用的控制器相同的情况
//比如默认值设置为{controller="home",action="index"}
//当请求地址为projects.domain.com/details?id=5这样的地址时
//短路径可以将该请求解析到projects区域下HomeController的Details动作上
var shortUrl = new Route("{action}/{id}", new RouteValueDictionary(defaults), new RouteValueDictionary(constraints), new RouteValueDictionary(dataTokens), new MvcRouteHandler());
var shortUrlLocal = new Route(areaName + "/{action}/{id}", new RouteValueDictionary(defaults), new RouteValueDictionary(constraints), new RouteValueDictionary(dataTokens), new MvcRouteHandler());
var shortUrlRoute = new SubdomainRoute(subdomain, areaName, shortUrl, shortUrlLocal);
routes.Add(prefix + "shortUrl", shortUrlRoute);
}
var standard = new Route("{controller}/{action}/{id}", new RouteValueDictionary(defaults), new RouteValueDictionary(constraints), new RouteValueDictionary(dataTokens), new MvcRouteHandler());
var standardLocal = new Route(areaName + "/{controller}/{action}/{id}", new RouteValueDictionary(defaults), new RouteValueDictionary(constraints), new RouteValueDictionary(dataTokens), new MvcRouteHandler());
var standardRoute = new SubdomainRoute(subdomain, areaName, standard, standardLocal);
routes.Add(prefix + "standard", standardRoute);
}
/// <summary>
/// 子域名路由到Area
/// </summary>
internal class SubdomainRoute : Route
{
public Route LocalRoute { get; private set; }
public string LowercasedSubdomain { get; private set; }
public string LowercasedAreaName { get; set; }
public SubdomainRoute(string subdomain, string areaName, Route targetRoute, Route localRoute)
: base(targetRoute.Url, targetRoute.Defaults, targetRoute.Constraints, targetRoute.DataTokens, targetRoute.RouteHandler)
{
this.LocalRoute = localRoute;
this.LowercasedSubdomain = subdomain.ToLowerInvariant();
this.LowercasedAreaName = areaName.ToLowerInvariant();
//在缺失controller的默认值设定中补充一个controller默认值,否则MvcStandardRoute获取不到路由数据
if (!this.Defaults.ContainsKey("controller"))
{
this.Defaults["controller"] = "home";
}
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
//对于通过localhost或127.0.0.1访问的,SubdomainRoute相当于无效状态
if (httpContext.Request.IsLocal && httpContext.Request.Url.IsLoopback)
{
return LocalRoute == null ? null : LocalRoute.GetRouteData(httpContext);
}
var host = httpContext.Request.Url.Host.ToLowerInvariant();
if (host.StartsWith(LowercasedSubdomain))
{
var rest = host.Substring(LowercasedSubdomain.Length, host.Length - LowercasedSubdomain.Length);
if (rest.Count(x => x == '.') == 2)
{
var data = base.GetRouteData(httpContext);
//if (data == null)
//{
// data = new RouteData(this, this.RouteHandler);
// if (Defaults != null)
// {
// foreach (KeyValuePair<string, object> item in Defaults) //controller action index
// {
// if (!data.Values.ContainsKey(item.Key))
// data.Values[item.Key] = item.Value;
// }
// }
// if (DataTokens != null)
// {
// foreach (var item in DataTokens) //Namespaces,area,UseNamespaceFallback
// {
// if (!data.DataTokens.ContainsKey(item.Key))
// data.DataTokens[item.Key] = item.Value;
// }
// }
//}
//data.DataTokens["area"] = this.LowercasedAreaName;
return data;
}
}
return null;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
if (requestContext.HttpContext.Request.IsLocal && requestContext.HttpContext.Request.Url.IsLoopback)
{
return LocalRoute == null ? null : LocalRoute.GetVirtualPath(requestContext, values);
}
var virturalPathData = base.GetVirtualPath(requestContext, values);
return virturalPathData;
}
}
}
</code></pre>