当前位置: 首页 > news >正文

对Action方法的参数进行双向转化

昨天有朋友忽然告诉我,在G点中国上搜索URL Routing时,我的《请别埋没了URL Routing》一文排在首位。这不禁让我汗颜,这是因为从现在的角度看起来,这篇文章的内容虽不能算错,但的确也不算是一种非常合适的做法。那篇文章的目的是展示如何利用URL Routing的扩展能力,将URL和Route Values通过Formatter进行双向的转化。这样便可以在Action方法中使用复杂参数的同时,也可以使用复杂参数得到正确的URL。这个目标是好的,可惜当时的思路有些偏差。现在我总结出了更合适的做法,并已经在项目中大量使用,效果不错。

之前提出那种原因,是因为Model Binder是单向的。也就是说,使用Model Binder把URL Routing的结果转化成Action的参数时不会有任何问题。例如,我们可以定义一个DateTimeModelBinder,接受一个字符串作为格式参数,这样便可以把URL Routing过程中得到的字符串转化为Action方法的DateTime类型参数了:

public ActionResult Date([DateTime("yyyy-MM-dd")]DateTime date) { ... }

但是,如果我们想要在视图中使用URL Routing来构造URL:

Html.ActionLink("Tomorrow", "Date", new { date = date.AddDays(1) })

或借助MvcFutures里提供的强类型(表达式树)辅助方法(其实它也是利用了URL Routing):

Html.ActionLink<DemoController>(c => c.Date(date.AddDays(1)), "Tomorrow")

问题就来了,因为它们得到的结果与我们的期望相距甚远:

<a href="/Demo/Date/01/01/2003%2000:00:00">Tomorrow</a>

看这链接中Date后面的那部分,多奇妙,多恶心。出现这个问题的原因在于,我们使用Model Binder可以知道如何将一个字符串正确转化为DateTime对象。但是,在构造URL的时候,如果我们利用了URL Routing,那么直接提供的复杂对象就会通过默认的ToString方法来作为URL的一部分——这显然是有问题的,例如这里的DateTime。因为这一点,在ASP.NET MVC应用程序中使用强类型的表达式树来生成URL几乎是一个不可实现的功能。

当然,我们是程序员,我们可以扩展。因此,我当时扩展了一个FormatRoute,它使用装饰器模式,封装了一个RouteBase对象,并且在GetRouteData时使用Formatter将RouteValueDictionary中的值直接从字符串转化成复杂类型的对象——并且在GetVirtualPath方法中作一个反向的操作。由于Routing功能是双向的,因此我们这么做便可以解决上面这个问题。当然,使用FormatRoute之后,生成Action参数的职责就交到了URL Routing阶段里。在一段时间里,我在项目中定下了这样的“规范”:

  • 如果是从URL中得到的Action参数,那么转化职责交给URL Routing。
  • 如果是从别处(如Post过来的数据)得到的Action参数,转化职责交给Model Binder。

这样,我们在视图中便可以使用强类型的表达式树来生成URL了,同时享受静态检查所得到的便利。

可惜看上去很美,但用起来一般。原因便是——太麻烦了。试想,我们在配置URL Routing的时候,往往会使用同一条Routing规则对应多个不同的URL形式,最终进入不同的Action方法(如默认的{controller}/{action}/{id})。但是,复杂类型参数的转化逻辑是根据Action不同而有所改变的。因此,如果我们要将转化参数的职责交给URL Routing的话,势必需要为每个Action方法指定一个Routing规则。那么好,这样Routing规则是不是会变得泛滥?如果我要修改Action,是不是还要去修改对应的Routing规则?这不是把相关的逻辑分散了吗?于是我又想出了其他一些方式来弥补这个问题,例如由Controller负责Routing规则的配置等等,这些尝试就不多提了。

总之,这个方法的目标正确,但是解决方式有些问题。但是,我们又能如何解决呢?既然事情是出在“URL生成”的方式上,那么我们就来改变一下辅助方法的逻辑吧,反正我们已经为它优化了性能,使它支持指定的Route名称,以及可以忽略某些参数等等。于是我在MvcPatch项目中增加了额外的IRouteBinder接口:

public interface IRouteBinder
{
    RouteValueDictionary BindRoute(RequestContext requestContext, RouteBindingContext bindingContext);
}

public class RouteBindingContext
{
    public object Model { get; set; }

    public Type ModelType { get; set; }

    public string ModelName { get; set; }
}

与Model Binder的功能正好相反,Route Binder的作用是把一个复杂类型的参数转化为一个RouteValueDictionary。在构造URL的辅助方法中会去检查标记在这个参数,或者这个参数类型中的Model Binder(没错,就是ASP.NET MVC本身获取Model Binder的方式),然后如果这个Model Binder还实现了IRouteBinder接口,则会先进行转化再构造URL,否则就和以前一样,直接使用这个参数的值构造URL。

因此,文章之前的DateTime参数的转化,便可以使用这样的一个Binder来处理:

public class DateTimeBinder : IModelBinder, IRouteBinder
{
    public DateTimeBinder(string format)
    {
        this.Format = format;
    }

    public string Format { get; private set; }

    #region IModelBinder Members

    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var modelName = bindingContext.ModelName;
        var rawValue = bindingContext.ValueProvider[modelName].RawValue;
        return DateTime.ParseExact(rawValue.ToString(), this.Format, null);
    }

    #endregion

    #region IRouteBinder Members

    public RouteValueDictionary BindRoute(RequestContext requestContext, RouteBindingContext bindingContext)
    {
        var modelName = bindingContext.ModelName;
        var model = (DateTime)bindingContext.Model;
        var routeValues = new RouteValueDictionary();
        routeValues.Add(modelName, model.ToString(this.Format));
        return routeValues;
    }

    #endregion
}

于是,这个世界和平了。

当然,这个做法和之前相比也有缺陷,那是因为IRouteBinder的功能是在辅助方法内调用的,因此如果您绕开辅助方法使用URL Routing构造URL的话,复杂类型的参数便无法得到转化了。不过权衡之下,现在这个做法使用更加便捷,由于把双向的转化逻辑放在同一处,其维护性也很好。经过我目前项目的使用,效果良好。

最近,我打算在MvcPatch中构建一个合适的示例程序,不会太复杂也但也足以用上MvcPatch里的各种功能(例如一个类似博客形式的应用程序)。如果您有合适的题材,也请及时告诉我。

转载于:https://www.cnblogs.com/JeffreyZhao/archive/2009/10/23/bidirectional-conversion-for-action-parameter-with-route-binder.html

相关文章:

  • MATLAB中帮助的几种使用方法
  • 伪静态技术说明
  • Java中Model1和Model2
  • config jre for openoffice3.0
  • 2017敏捷沙滩大会概述:学习、心理安全和持续交付的重要性
  • 简洁的一键SSH脚本
  • Page-Enter、Page-Exit的使用
  • 很认真的聊一聊程序员的自我修养(转)
  • ERP系统各种单据流水号的产生方案
  • WebSocket在spring messagemapping下获取httpsession
  • 图片的动画 ease.js
  • [翻译].net 2.0(c#)下简单的FTP应用程序(转)
  • Python Unicode 转换 字符串
  • java中关于、、|、||之间的区别和运算
  • 生成静态页面的方法
  • dva中组件的懒加载
  • HTTP 简介
  • React 快速上手 - 06 容器组件、展示组件、操作组件
  • unity如何实现一个固定宽度的orthagraphic相机
  • vue.js框架原理浅析
  • vuex 笔记整理
  • Vue实战(四)登录/注册页的实现
  • 工作踩坑系列——https访问遇到“已阻止载入混合活动内容”
  • #if 1...#endif
  • #在线报价接单​再坚持一下 明天是真的周六.出现货 实单来谈
  • ( )的作用是将计算机中的信息传送给用户,计算机应用基础 吉大15春学期《计算机应用基础》在线作业二及答案...
  • (42)STM32——LCD显示屏实验笔记
  • (Matlab)遗传算法优化的BP神经网络实现回归预测
  • (Ruby)Ubuntu12.04安装Rails环境
  • (附源码)ssm码农论坛 毕业设计 231126
  • (六)vue-router+UI组件库
  • (七)理解angular中的module和injector,即依赖注入
  • (一)搭建springboot+vue前后端分离项目--前端vue搭建
  • .h头文件 .lib动态链接库文件 .dll 动态链接库
  • .NET Core使用NPOI导出复杂,美观的Excel详解
  • .net wcf memory gates checking failed
  • .NET 编写一个可以异步等待循环中任何一个部分的 Awaiter
  • .NET 药厂业务系统 CPU爆高分析
  • .Net6支持的操作系统版本(.net8已来,你还在用.netframework4.5吗)
  • .NET设计模式(11):组合模式(Composite Pattern)
  • /usr/local/nginx/logs/nginx.pid failed (2: No such file or directory)
  • [ IO.File ] FileSystemWatcher
  • [ 攻防演练演示篇 ] 利用通达OA 文件上传漏洞上传webshell获取主机权限
  • [2016.7 Day.4] T1 游戏 [正解:二分图 偏解:奇葩贪心+模拟?(不知如何称呼不过居然比std还快)]
  • [2024] 十大免费电脑数据恢复软件——轻松恢复电脑上已删除文件
  • [Angularjs]ng-select和ng-options
  • [ARM]ldr 和 adr 伪指令的区别
  • [AX]AX2012 AIF(四):文档服务应用实例
  • [bzoj1901]: Zju2112 Dynamic Rankings
  • [CTF]2022美团CTF WEB WP
  • [C语言]——C语言常见概念(1)
  • [HarekazeCTF2019]encode_and_encode 不会编程的崽
  • [leetcode]_Symmetric Tree
  • [LeetCode]—Add Binary 两个字符串二进制相加
  • [SpringBoot] AOP-AspectJ 切面技术