在 Area 中使用RouteAttribute 定义路由, 并支持多语言
业务上的一个需求, 同一页面, 两种不同的使用方法, 为了区分这两种需求, 需要加一个参数到 URL 中,
不改路由的话, 是这样:
http://localhost:16269/en-US/Forwarder/Bargain/Create/G20150911000009?from=FAK
虽然不是处女座的, 但是我想把地址变成这样:
http://localhost:16269/en-US/Forwarder/Bargain/FAK/Create/G20150911000009
改路由表分分钟的事, 但是每个特殊的业务都去改一下路由表, 那也太蛋疼了.
MVC 中有 RouteAttribute , 在不分 Area 的系统中使用过, 很简单.
具体可参考:
今天头一次在 Area 中添加这个东西, 搞的有些错乱.
1, RouteAttribute 配合 RouteAreaAttribute 不起作用
这是因为 MapMvcAttributeRoutes() 方法放在 RegisterAllAreas 后面调用了, 两个调整一下顺序就可以了, 不需在要每个 Area 下面的 AreaRegistration 中调用, 总的调用一次就可行了.
正确的写法是在RouteConfig 中这样:
public class RouteConfig {
public static void RegisterRoutes(RouteCollection routes) {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
RouteTable.Routes.MapMvcAttributeRoutes();
AreaRegistration.RegisterAllAreas();
...
...
2, 正确的路由数据顺序
这个地址:
http://localhost:16269/en-US/Forwarder/Bargain/FAK/Create/G20150911000009
的路由的 Template 应该是这样的:
{lang}/{area}/{controller}/{from}/{action}/{id}
一开始没有把握要领, 搞成这样:
http://localhost:16269/Forwarder/en-US/Bargain/FAK/Create/G20150911000009 http://localhost:16269/Forwarder/en-US/Forwarder/Bargain/FAK/Create/G20150911000009
过程就是一点点的试试, 不在赘述,
正确的写法应该是这样:
[RouteArea("Forwarder", AreaPrefix = "{lang=zh-CN}/Forwarder")]
[RoutePrefix("Bargain")]
public class BargainController : BaseController {
...
...
[Route("{from=FAK}/Create/{id}")]
public ActionResult Create([Required]string id, string from = "FAK") {
...
3, 多语言不起作用
先来看一眼怎么处理多语言的:
public class MutiLangRouteHandler : MvcRouteHandler {
protected override System.Web.IHttpHandler GetHttpHandler(RequestContext requestContext) {
var handler = base.GetHttpHandler(requestContext);
string lang = requestContext.RouteData.Values["lang"].ToString();
try {
var culture = CultureInfo.GetCultureInfo(lang);
Thread.CurrentThread.CurrentUICulture = culture;
} catch {
}
return handler;
}
}
在这个 Handler 中, 会取路由中的 lang , 然后尝试设置 UICulture 为指定语言.
在注册路由的时候, 要指定 Route 的 RouteHandler 为这个 MutiLangRouteHandler
routes.Add(new Route("{lang}/{controller}/{action}/{id}",
new RouteValueDictionary(new {
lang = "zh-CN",
controller = "Home",
action = "Index",
id = UrlParameter.Optional
}),
new RouteValueDictionary(new {
lang = "(zh-CN)|(en-US)"
}),
new MutiLangRouteHandler()));
Area 中的路由要这样:
var r2 = context.MapRoute(
"Forwarder_default1",
"Forwarder/{controller}/{action}/{id}",
new {
lang = "zh-CN",
action = "Index",
id = UrlParameter.Optional
},
new {
lang = "(zh-CN)|(en-US)"
}
);
var handler = new MutiLangRouteHandler();
r1.RouteHandler = handler;
r2.RouteHandler = handler;
上面虽然用 RouteAttribute 配置好了路由, 但是那只是表面上的, 根本就不会执行到自定义的 多语言处理器 (MutiLangRouteHandler)
原因很简单啊, RouteTable.Routes.MapMvcAttributeRoutes() 生成的路由使有的是 默认的 MvcRouteHandler, 它里面肯定不会处理多语言啦 .
RouteTable.Routes.MapMvcAttributeRoutes()之后, 在调用工具中可以看到 路由表中已经添加由 RouteAttribute 生成的路由, 但是报歉, 包装它的是一个 internal 的类, 所以无法获取这些路由, 更无法给这些路由设置 RouteHandler.

怎么办呢? 扩展 RouteAttribute ? 但是这个类是 sealed 的, 无法扩展.
看一下源码, 它实现了 IDirectRouteFactory 接口, 这个接口有一个 CreateRoute 方法, 返回 RouteEntity
RouteEntry IDirectRouteFactory.CreateRoute(DirectRouteFactoryContext context)
{
Contract.Assert(context != null);
IDirectRouteBuilder builder = context.CreateBuilder(Template);
Contract.Assert(builder != null);
builder.Name = Name;
builder.Order = Order;
return builder.Build();
}
RouteEntity 中有个 Route, 也就是说, 只要获取到这个 RouteEntity , 就可以给它生成的路由加 RouteHandler 了.
于是我这样定义:
/// <summary>
///
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class RouteWithHandlerAttribute : Attribute, IDirectRouteFactory, IRouteInfoProvider {
...
...
public Type HandlerType {
get;
set;
}
/// <summary>
///
/// </summary>
/// <param name="handlerType"></param>
/// <param name="template"></param>
public RouteWithHandlerAttribute(Type handlerType, string template = "") {
if (handlerType == null)
throw new ArgumentNullException("handlerType");
if (!(handlerType.GetInterfaces().Contains(typeof(IRouteHandler))
&& handlerType.GetConstructor(Type.EmptyTypes) != null)
)
throw new ArgumentException("handerType 必须是 IRouteHandler 的子类, 必须有无参构造函数");
if (template == null)//可以为空字符串
throw new ArgumentNullException("template");
this.Template = template;
this.HandlerType = handlerType;
}
/// <summary>
///
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
RouteEntry IDirectRouteFactory.CreateRoute(DirectRouteFactoryContext context) {
if (context == null)
throw new ArgumentNullException("context");
var handler = (IRouteHandler)Activator.CreateInstance(this.HandlerType);
...
...
IDirectRouteBuilder builder = context.CreateBuilder(Template);
...
...
var entry = builder.Build();
entry.Route.RouteHandler = handler;
return entry;
}
}
即在返回之前给 entry.Route.RouteHandler 赋值.
看似很完美, 结果运行就报错:
[InvalidOperationException: 直接路由不支持按路由路由处理程序。] System.Web.Mvc.Routing.DirectRouteBuilder.ValidateRouteEntry(RouteEntry entry) +245
直接路不支持路由处理程序啊...功夫白搭了
看来在 RouteAttribute 上下功夫是没用了, 换其它方法吧.
我在系统中写了一堆 ActionFilter, 用于判断权限啦 , Action 参数完整性啦, 错误处理等等, 对这个东西还是有过深入了解的.
ActionFilterAttribute 提供了 OnActionExecuting, 会在 Action 执行前执行, 我可以借助这个方法来处理多语言:
public class MutiLangAttribute : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext) {
//不能直接调用 Handler, 会报以下错误:
// 只能在引发“HttpApplication.AcquireRequestState”之前调用“HttpContext.SetSessionStateBehavior”。
//IRouteHandler handler = new MutiLangRouteHandler();
//handler.GetHttpHandler(filterContext.RequestContext).ProcessRequest(HttpContext.Current);
string lang = filterContext.RequestContext.RouteData.Values["lang"].ToString();
try {
var culture = CultureInfo.GetCultureInfo(lang);
Thread.CurrentThread.CurrentUICulture = culture;
} catch {
}
}
}
OK, Action 上这样写:
[Route("{from=FAK}/Create/{id}"), MutiLang]
public ActionResult Create([Required]string id, string from = "FAK") {
即加一个 RouteAttribute, 另外在加上 MutiLangAttribute
OK, 多语言的问题基本上处理完毕了, 除了一个例外: DisplayModel
4, DisplayMode 中取不出 RouteData 中的 lang
DisplayMode 是 MVC4 中增加的功能, 我用它来做这样的事:
en-Us 的时候, 显示英文字段, zh-CN 的时候, 显示中文字段. 当然,它可以处理的事多去了, 只要脑洞够大.
public class DisplayModelSetting {
public static void Config() {
DisplayModeProvider.Instance.Modes.Insert(, new DefaultDisplayMode("en-US") {
ContextCondition = ctx => {
var data = RouteTable.Routes.GetRouteData(ctx);
if (data != null) {
var lang = (string)data.Values.Get("lang", "");
return string.Equals(lang, "en-US", StringComparison.OrdinalIgnoreCase);
}
return false;
}
});
var config = ConfigurationHelper.GetSection<CustomDomainsConfig>();
if (config != null && config.Domains != null) {
//匹配自定义域名, 以达到不同公司, 显示不同界面的功能.
//自定义域名在 Configs/CustomDomains.config 中定义
config.Domains.Cast<CustomDomainItem>().ToList().ForEach(c => {
DisplayModeProvider.Instance.Modes.Insert(, new DefaultDisplayMode(c.View) {
ContextCondition = ctx => {
return string.Equals(ctx.Request.Url.Host, c.Domain, StringComparison.OrdinalIgnoreCase);
}
});
});
}
}
}
结果运行发现, 无论是何种语言, (string)data.Values.Get("lang", "") 返回的一直是空字符串.
在调试器中发现:

RouteData 中还包含一个 RouteData 集合, lang 是在这个集合中, 所以上面的代码无法直接取出.
修改成这样:
DisplayModeProvider.Instance.Modes.Insert(, new DefaultDisplayMode("en-US") {
ContextCondition = ctx => {
var data = RouteTable.Routes.GetRouteData(ctx);
if (data != null) {
RouteValueDictionary rdic = data.Values;
//适用于 RouteAttribute
var routeData = data.Values.Get("MS_DirectRouteMatches", null);
if (routeData != null) {
rdic = ((IEnumerable<System.Web.Routing.RouteData>)routeData).First().Values;
}
var lang = (string)rdic.Get("lang", "");
return string.Equals(lang, "en-US", StringComparison.OrdinalIgnoreCase);
}
return false;
}
});
OK , DisplayMode 的问题也解决了!
-----------------------
总结: 挺曲折的, 其它没有.
完.
在 Area 中使用RouteAttribute 定义路由, 并支持多语言的更多相关文章
- ASP.NET MVC 设置Area中 Controller 的方法 默认启动页
MVC中通常分区域编程,互不干扰,如果需要设置某个区域下面的某个控制器下面的某个方法为默认启动页的话,直接修改项目的路由如下: public static void RegisterRoutes(Ro ...
- 如何将 Area 中的 Controller 放到独立的程序集?
目录 背景如何将 Area 中的 Controller 放到独立的程序集?备注 背景返回目录 本文假设您已经熟悉了 ASP.NET MVC 的常规开发方式.执行模型和关键扩展点,这里主要说一下如何使用 ...
- ASP.NET Web API中的Routing(路由)
[译]Routing in ASP.NET Web API 单击此处查看原文 本文阐述了ASP.NET Web API是如何将HTTP requests路由到controllers的. 如果你对ASP ...
- 处理ASP.NET Core中的HTML5客户端路由回退
在使用由Angular,React,Vue等应用程序框架构建的客户端应用程序时,您总是会处理HTML5客户端路由,它将完全在浏览器中处理到页面和组件的客户端路由.几乎完全在浏览器中... HTML5客 ...
- Python框架学习之Flask中的视图及路由
在前面一讲中我们学习如何创建一个简单的Flask项目,并做了一些简单的分析.接下来在这一节中就主要来讲讲Flask中最核心的内容之一:Werkzeug工具箱.Werkzeug是一个遵循WSGI协议的P ...
- 07:vue定义路由
1.1 定义路由 1.说明 1. 路由是单页面应用程序(SPA)的关键,Vue提供过来路由插件,使用这个路由就要安装这个插件 2. 安装: npm install vue-router 3. 依赖于v ...
- 转:在ASP.NET MVC中通过URL路由实现对多语言的支持
对于一个需要支持多语言的Web应用,一个很常见的使用方式就是通过请求地址来控制界面呈现所基于的语言文化,比如我们在表示请求地址的URL中将上语言文化代码(比如en或者en-US)来指导服务器应该采用怎 ...
- ASP.NET MVC:看 MVC 源码,学习:如何将 Area 中的 Controller 放到独立的程序集?
背景 本文假设您已经熟悉了 ASP.NET MVC 的常规开发方式.执行模型和关键扩展点,这里主要说一下如何使用 ASP.NET MVC 的源代码解决一些问题. 如何将 Area 中的 Control ...
- sails route(1) -用户定义路由
sails支持两种类型的路由: custom(or "explicit") andautomatic(or "implicit"). 先来看一下custom 即 ...
随机推荐
- Java读写txt文件
1.Java读取txt文件 1.1.使用FileInputStream: public static String readFile(File file, String charset){ //设置默 ...
- Android程序入口以及项目文件夹的含义和使用总结—入门
新接触一门程序或者开发框架,我一般都要先弄清楚程序的入口在哪里,程序怎么运行的:建立一个项目后,各个文件夹有什么作用以及如何使用等等.理清楚这些东西对以后开发是很有好处的,古话说得好,工欲善其事,必先 ...
- HTTP Content-type
文件扩展名 Content-Type 文件扩展名 Content-Type .* application/octet-stream .tif image/tiff .001 applicat ...
- NetBIOS
NetBIOS是Network Basic Input/Output System的缩写,严格来说它不是一个网络协议,而是一套API,为局域网内应用程序通信提供会话层(OSI七层参考模型)的支持. N ...
- [转]Worksheet.Change Event (Excel)
本文转自:https://msdn.microsoft.com/en-us/library/office/ff839775.aspx#AboutContributor Example The fo ...
- openPOWERLINK官方安装版例程(v2.3.0)附带mnobd.cdc文件断句
demo_mn_qt.exe启动所需载入的mnobd.cdc文件断句(备忘) //// Project: Demo_3CN //// NodeCount: 3 //// 0000003A //// N ...
- UIView.frame的骗局
如果你刚刚开始接触IOS编程, 刚刚接触UIKit, 肯定会被 frame, bounds, center, layer.anchorPoint, layer.position 这些乱七八糟得属性折腾 ...
- UESTC 1014 Shot
这题刚开始没想通,结果发现要解方程,代码如下: #include<iostream> #include<cstdio> #include<cmath> #inclu ...
- NYOJ----1124数量
数量 时间限制:200 ms | 内存限制:65535 KB 难度:0 描述 HJS大牛想去街上吃饭,街道旁边拴着很多狗,他想我堂堂......(省略n个字)岂会被狗咬,所以他很牛的从狗的面前经过 ...
- bootstrap和jquery mobile的对比
最近一直在研究bootstrap这东西,确实是个好的框架,但是诸多优势背后也隐藏着一些不好的地方,对此,我把它和另一套响应式框架jquery mobile做了一下对比,我的总结如下: 1.boo ...