业务上的一个需求, 同一页面, 两种不同的使用方法, 为了区分这两种需求, 需要加一个参数到 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 的系统中使用过, 很简单.
具体可参考:

http://blogs.msdn.com/b/webdev/archive/2013/10/17/attribute-routing-in-asp-net-mvc-5.aspx#route-areas

今天头一次在 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 定义路由, 并支持多语言的更多相关文章

  1. ASP.NET MVC 设置Area中 Controller 的方法 默认启动页

    MVC中通常分区域编程,互不干扰,如果需要设置某个区域下面的某个控制器下面的某个方法为默认启动页的话,直接修改项目的路由如下: public static void RegisterRoutes(Ro ...

  2. 如何将 Area 中的 Controller 放到独立的程序集?

    目录 背景如何将 Area 中的 Controller 放到独立的程序集?备注 背景返回目录 本文假设您已经熟悉了 ASP.NET MVC 的常规开发方式.执行模型和关键扩展点,这里主要说一下如何使用 ...

  3. ASP.NET Web API中的Routing(路由)

    [译]Routing in ASP.NET Web API 单击此处查看原文 本文阐述了ASP.NET Web API是如何将HTTP requests路由到controllers的. 如果你对ASP ...

  4. 处理ASP.NET Core中的HTML5客户端路由回退

    在使用由Angular,React,Vue等应用程序框架构建的客户端应用程序时,您总是会处理HTML5客户端路由,它将完全在浏览器中处理到页面和组件的客户端路由.几乎完全在浏览器中... HTML5客 ...

  5. Python框架学习之Flask中的视图及路由

    在前面一讲中我们学习如何创建一个简单的Flask项目,并做了一些简单的分析.接下来在这一节中就主要来讲讲Flask中最核心的内容之一:Werkzeug工具箱.Werkzeug是一个遵循WSGI协议的P ...

  6. 07:vue定义路由

    1.1 定义路由 1.说明 1. 路由是单页面应用程序(SPA)的关键,Vue提供过来路由插件,使用这个路由就要安装这个插件 2. 安装: npm install vue-router 3. 依赖于v ...

  7. 转:在ASP.NET MVC中通过URL路由实现对多语言的支持

    对于一个需要支持多语言的Web应用,一个很常见的使用方式就是通过请求地址来控制界面呈现所基于的语言文化,比如我们在表示请求地址的URL中将上语言文化代码(比如en或者en-US)来指导服务器应该采用怎 ...

  8. ASP.NET MVC:看 MVC 源码,学习:如何将 Area 中的 Controller 放到独立的程序集?

    背景 本文假设您已经熟悉了 ASP.NET MVC 的常规开发方式.执行模型和关键扩展点,这里主要说一下如何使用 ASP.NET MVC 的源代码解决一些问题. 如何将 Area 中的 Control ...

  9. sails route(1) -用户定义路由

    sails支持两种类型的路由: custom(or "explicit") andautomatic(or "implicit"). 先来看一下custom 即 ...

随机推荐

  1. 【Android】 Android实现录音、播音、录制视频功能

    智能手机操作系统IOS与Android平分天下(PS:WP与其他的直接无视了),而Android的免费招来了一大堆厂商分分向Android示好,故Android可能会有“较好”的前景. Android ...

  2. javascript简介和基本语法

    javascript简介 1.javascript是个脚本语言,需要有宿主文件,他的宿主文件是html文件. 用法:为了保险起见一般写在</html>之后<javascript   ...

  3. CSS纯样式实现箭头、对话框等形状

    在使用第三方框架bootstrap的时候,本以为其是图片实现的小箭头,后来使用开发工具查看是用CSS来实现的,现记录如下: 之前都没仔细去观注过其原理,都是拿来使用,在实现小箭头之前需要了解下CSS的 ...

  4. 在Jena框架下基于MySQL数据库实现本体的存取操作

    在Jena框架下基于MySQL数据库实现本体的存取操作 转自:http://blog.csdn.net/jtz_mpp/article/details/6224311 最近在做一个基于本体的管理系统. ...

  5. REDIS 在电商中的实际应用场景(转)

    1. 各种计数,商品维度计数和用户维度计数 说起电商,肯定离不开商品,而附带商品有各种计数(喜欢数,评论数,鉴定数,浏览数,etc),Redis的命令都是原子性的,你可以轻松地利用INCR,DECR等 ...

  6. Python的逻辑运算符and小析

    近期突然对验证码的识别感兴趣了,然后就研究了一些图像识别和处理的资料,其中有一种图像处理是关于字体的细化和骨架提取的,但是这种算法没有现成的java代码实现,那些号称的java版代码多半都是效果很差或 ...

  7. JMeter学习(三)元件的作用域与执行顺序

    1.元件的作用域 JMeter中共有8类可被执行的元件(测试计划与线程组不属于元件),这些元件中,取样器是典型的不与其它元件发生交互作用的元件,逻辑控制器只对其子节点的取样器有效,而其它元件(conf ...

  8. jsp 微信公众平台 token验证(php、jsp)(转载)

    微信公众平台现在推出自动回复消息接口,但是由于是接口内容用的是PHP语言写的,很多地方操作起来让本人这个对java比较熟悉的小伙很别扭,所以仿照PHP的接口代码做了一套jsp语言编写的接口. 首先先把 ...

  9. Android SQLite (四 ) 全面详解(二)

    SQLite创建数据库 创建数据库语法: sqlite3 DatabaseName.db 如下展示一个实例: SQLite附加数据库 假设这样一种情况,当在同一时间有多个数据库可用,您想使用其中的任何 ...

  10. f2fs解析(一)f2fs如何解决wandering tree

    wandering tree问题是log-structured 文件系统(LFS) 特有的一个问题,因为LFS的脏数据是追加更新的,所以如果一个数据块变脏了,那么那个数据块的直接索引块.间接索引块都会 ...