MVC 用扩展方法执行自定义视图,替代 UIHint

项目中用了 Bootstrap , 这样就不用写太多的CSS了,省去很多事情。
但是这个业务系统需要输入的地方很多,每个表都有100多个字段,每个页面需要大量的表单。
把这些表单按 bootstrap 的格式写出来,也是件头痛的事情。
我想到模板,EditorTemplates UIHint, 但是 UIHint 需要用 Metadata 标注,一个一个的加,也是不现实的。
还有别外一种办法,就是扩展 HtmlHelper。
要用HtmlHelper ,大家可能就想到了 TagBuilder 了,TagBuilder 基本全是 Hard code 了,不方便调整显示格式。

最终我用了另外一种办法:
在 HtmlHelper 扩展里,取自定义的视图,视图可以随时改,又不用每个字段去加 UIHint.

 1 public static MvcHtmlString EditorBlockAFor<TModel, TProperty>(this HtmlHelper<TModel> helper, string template, Expression<Func<TModel, TProperty>> property, bool withLabel, string containerClass = "col-xs-4", object htmlAttributes = null) {
2
3 var body = (MemberExpression)property.Body;
4 if (body == null)
5 throw new ArgumentException();
6
7 var ctx = helper.ViewContext.Controller.ControllerContext;
8 var result = ViewEngines.Engines.FindPartialView(helper.ViewContext.Controller.ControllerContext, template);
9 if (result.View != null) {
10
11 var metadata = ModelMetadata.FromLambdaExpression(property, helper.ViewData);
12 //var model = metadata.Model;
13
14 var attrs = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
15
16 using (var writer = new StringWriter(CultureInfo.CurrentCulture)) {
17 var vctx = new ViewContext(ctx, result.View, helper.ViewData, helper.ViewContext.TempData, writer);
18 vctx.ViewBag.PropertyName = string.Join(".", body.ToString().Split(new char[] { '.' }).Skip(1));//body.Member.Name;
19
20 vctx.ViewBag.ContainerClass = containerClass;
21 vctx.ViewBag.IsRequired = metadata.IsRequired;
22 vctx.ViewBag.HtmlAttributes = attrs;
23 vctx.ViewBag.WithLabel = withLabel;
24
25 vctx.ViewBag.DisplayName = metadata.DisplayName ?? metadata.PropertyName;
26
27 result.View.Render(vctx, writer);
28
29 return MvcHtmlString.Create(writer.ToString());
30 }
31 } else {
32 throw new InvalidOperationException(string.Format("particle view {0} not found", template));
33 }
34 }

这个是核心,其它的扩展都是调用这个方法,比如:

1 public static MvcHtmlString TextBlockFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> property, string containerClass = "col-lg-4 col-md-4 col-xs-4", object htmlAttributes = null, bool withLabel = true) {
2 return EditorBlockAFor(helper, "TextBlock", property, withLabel, containerClass, htmlAttributes);
3 }

在 EditorBlockAFor 这个方法里,有一句:
var result = ViewEngines.Engines.FindPartialView(helper.ViewContext.Controller.ControllerContext, template);
这个是去查找指定的自定义视图,它就是整个思路的关键。

自定义视图 TextBlock.cshtml

@model object
@{
this.Layout = null;
string propertyExpression = ViewBag.PropertyName;
string containerClass = ViewBag.ContainerClass; RouteValueDictionary htmlAttributes = SharedTemplatesHelper.MargeClass(ViewBag);
} <div class="@containerClass">
@SharedTemplatesHelper.Label(ViewBag)
@Html.TextBox(propertyExpression, null, htmlAttributes)
</div>

注意,模型是object , 因为不确定模型的类型。

这个文件需要放到 Shared 下,这个是简单的结构。

调用:
@Html.TextBlockFor(m => m.VesselInfo.VESSEL_NAME_CN, "col-lg-2 col-md-2")
最终会生成这样一段HTML:

<div class="col-lg-2 col-md-2">
<span class="help-block">中文名称 <span class="red">*</span>
</span>
<input class="form-control input-sm" data-val="true" data-val-length="The field 中文名称 must be a string with a maximum length of 500." data-val-length-max="500" data-val-required="The 中文名称 field is required." id="VesselInfo_VESSEL_NAME_CN" name="VesselInfo.VESSEL_NAME_CN" type="text" value="" />
</div>

长这个样子:

--------------分隔线内是废话,有兴趣可以了解一下我的崩溃经历-------------------

一切都按照设想的样子,直到。。。。
有一天,同事说明明是 Required 的,为什么没有执行验证?
我简单的看了一下,是因为没有生成 data-xxx 这样的验证属性。看了一下 Action ,就是 Return View(); 没有传递 model 到视图。
声明了一个 model 传给视图 (return View(XXX);) 后,一切正常。
我做MVC3的时候,不给 Model 都会输出验证属性,当时赶时间,没有去深入研究为什么,还以为 MVC 5 的新特性呢。

国庆过7天猪一样的生活,项目也进行的七七八八了,终于有时间回头看看了。
下了MVC的最新源码:
http://aspnetwebstack.codeplex.com/SourceControl/latest
版本号是 5.2.3.0, 我们项目中用的是 5.2.0.0 ,差别不大。

新建了一个测试项目,
编译了一份MVC的DLL,连同PDB一起放到项目的引用目录下,改了一下MVC的配置、引用,在 InputExtensions 下加了断点,但是却无法断点。
按照网上的搜到的调试 MVC 源码的方法去做,很不幸,没有一个适用的。

搞到晚上8点多,还是没有办法调试进源码。真是崩溃至极了。
又找到了篇 pdb 符号服务器的博文:
http://weblogs.asp.net/gunnarpeipman/stepping-into-asp-net-mvc-source-code-with-visual-studio-debugger
但是需要下载这些符号,电脑没关,回去了。

今天按照博文的说明去做,仍然不能调试进源码。

很有幸,找到另外一篇文:
http://blogs.msdn.com/b/micl/archive/2014/06/07/how-to-debug-your-code-with-mvc-fresh-source-code.aspx

Before each version of MVC launch, the contribute team always strong name each MVC related assembly by a specified keyfile 35MSSharedLib1024.snk which is located in tools folder to prevent assembly tamper. But the snk file that you get doesn't contain private key, that you can only delay signed all assemblies if you compile directly. Unfortunately, delay signed assembly doesn't support debug feature. 
简单的翻译一下:
因为我们下载到的 MVC 源码是经过签名的,源码里提供的密钥不包含私钥,只能是延迟签名。很不幸,延迟答名是不能DEBUG 的。
(这方面我没有经验,不懂)

按照博文的做法,将相关的项目都去掉了签名:
System.Web.Helpers
System.Web.Mvc
System.Web.Razor
System.web.WebPages

然后将 System.Web.WebPages 下的
AssemblyInfo.cs (在 Properties 下) 中的 InternalsVisibleTo 参数换成不带版本号的。
[assembly: InternalsVisibleTo("System.Web.Mvc")]
[assembly: InternalsVisibleTo("System.Web.Helpers")]

编译,修改测试项目的的 web.config ,将 System.Web.Mvc 的版本改成编译的版本号,删除原来的相关引用,添加为刚刚编译过的相关DLL
加断点,运行,调试进去了。

--------------分隔线-------------------

上面都是废话。
调了一圈,发现在 ModelMetadata.cs 第 382 行:
ModelMetadata propertyMetadata = viewData.ModelMetadata.Properties.Where(p => p.PropertyName == expression).FirstOrDefault();
匹配不到 property, propertyMetadata 的结果是 null.
属性明明是有的,就死活就是没有属性的 Metadata.

当快速监视 viewData.ModelMetadata 后,这个值又有了,说明问题出在这个 viewData.ModelMetadata 上。

在 ViewDataDictionary.cs ,定义的是 ViewDataDictionary , 第82行,
if (_modelMetadata == null && _model != null)
_model 无疑是传到视图里的 model
当 _modelMetadata = null 且 _model 不为 null 的时候,会去取模型的Metadata ,这就解释了为什么当传 Model 到视图的时候,会输出验证属性了。
当是不传 Model 到视图,就返回 null 了。

进入 ViewDataDictionaryOfModel.cs 
定义的是 ViewDataDictionary<TModel> ,继承自 ViewDataDictionary
在 第35行,取父类的 ModelMetadata, 因为返回的是 null, 又去按 模型(Model) 的类型去取 Metadata.

接上面的自定义视图,TextBlock.cshtml
@model object
...
...

指定的模型类型是 object , 这是因为它是 Shared 的,不确定模型的确切类型,所以我用了 object.
好了,问题来了,挖掘机哪家强?按 object 去取 Metadata 当然是取不到的!

转了一转,又把我逼到当初处理这个东西的原点上。
想到快速监视后,是可以取到想要的结果的,我把上面的扩展方法改加一句:

。。。
var vctx = new ViewContext(ctx, result.View, helper.ViewData, helper.ViewContext.TempData, writer);
vctx.ViewData.ModelMetadata = helper.ViewData.ModelMetadata;//////必须的,调了很长时间,定位问题在这个 ModelMetadata 上
。。。

运行,通过!

 
 
标签: mvc

MVC 用扩展方法执行自定义视图,替代 UIHint的更多相关文章

  1. MVC页面扩展方法 单例模式

    MVC页面扩展方法    单例模式    /// <summary>         /// 创建一个Config内容对象         /// </summary>     ...

  2. MVC ---- 如何扩展方法

    先定义一个扩展类: public static class StringExtend { //扩展一个string的方法 public static bool IsNullOrEmpty(this s ...

  3. Asp.net MVC 控制器扩展方法实现jsonp

    项目需要,在使用KendoUI,又涉及到jsonp数据格式的处理,网上看到这样一种实现方法,在此小记一下(其实是因为公司里只能上博客园等少数网站,怕自己忘了,好查看一下,哈哈哈) 1. 新建控制器扩展 ...

  4. ASP.NET MVC扩展自定义视图引擎支持多模板&动态换肤skins机制

    ASP.NET mvc的razor视图引擎是一个非常好的.NET MVC框架内置的视图引擎.一般情况我们使用.NET MVC框架为我们提供的这个Razor视图引擎就足够了.但是有时我们想在我们的项目支 ...

  5. ASP.NET MVC 扩展自定义视图引擎支持多模板&动态换肤skins机制

    ASP.NET  mvc的razor视图引擎是一个非常好的.NET  MVC 框架内置的视图引擎.一般情况我们使用.NET MVC框架为我们提供的这个Razor视图引擎就足够了.但是有时我们想在我们的 ...

  6. ASP.NET MVC之持久化TempData及扩展方法(十三)

    前言 之前在开始该系列之前我们就讲述了在MVC中从控制器到视图传递数据的四种方式,但是还是存在一点问题,本节就这个问题进行讲述同时进行一些练习来看看MVC中的扩展方法. 话题 废话不必多说,我们直接进 ...

  7. SpringMVC:自定义视图及其执行过程

    一:自定义视图 1.自定义一个实现View接口的类,添加@Component注解,将其放入SpringIOC容器 package com.zzj.view; import java.io.PrintW ...

  8. Unity中自定义扩展方法

    问题背景 在使用unity开发过程中,通常会遇到一种情况,比如说给物体重新赋值坐标的问题, Transfrom tran: ,pos_y=,pos_z=; tran.position=new Vect ...

  9. MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)

    前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...

随机推荐

  1. Oracle左连接、右连接、全外连接以及(+)号用法(转)

    +:与附带的字段相连,和“+”相连的字段值,不管是否存在,都会展示 也就是带上相连接的字段 有数据了就显示,没数据就显示为null Oracle  外连接(OUTER JOIN) 左外连接(左边的表不 ...

  2. 亚马逊记AWS(Amazon Web Services)自由EC2应用

    很长时间,我听到AWS能够应用,但是需要结合信用卡,最近申请了. 说是免费的,我还是扣6.28,后来我上网查了.认为是通过进行验证.像服务期满将返回. 关键是不要让我进入全抵扣信用卡支付passwor ...

  3. Oracle数据表被drop后的恢复

    对于被drop的表和索引,都会存放在回收站中(所以对于生产的数据库必须设置好回收站功能) 由于本次生成环境在drop掉已有的表后,又一次创建了很多的表,全部直接还原的话会提示原有对象存在,表名反复.当 ...

  4. NSIS:延时启动软件的几个方法及探索

    原文NSIS:延时启动软件的几个方法及探索 有时候,我们想要某软件开机启动,但又不要拖慢开机速度,那么,延时启动技术就显得比较重要了.轻狂在这方面曾经研究过,也实现了自己想要的功能,看看我是怎么做的吧 ...

  5. 【IOS 开发】Object-C 入门 Xcode 环境具体解释

    作者 : 韩曙亮 转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/38424965 一. Xcode 环境安装 与 project ...

  6. ExtJS4 动态生成grid出口excel(纯粹的接待)

    搜索相当长的时间,寻找一些样本,因为我刚开始学习的原因,大多数人不知道怎么用.. 他曾在源代码.搞到现在终于实现了主下载.. 表的采集格不重复下载一个小BUG,一个使用grid初始化发生的BUG 以下 ...

  7. 配置Apacheserver

    配置Apacheserver 一.目的 能够有一个測试的server,不是全部的特殊网络服务都能找到免费得! 二.为什么我们要用"Apache"? Apache是眼下使用最广的we ...

  8. 类(class)能不能自己继承自己(转)

    类(class)能不能自己继承自己不行,继承关系会出现环. 假设类A继承类A.那么要新建一个类A的对象,就必须先建立一个类A父类的对象.这是一个递归的过程,而且没有终止条件.会死循环的. 从编译的角度 ...

  9. bnu 34986 Football on Table(数学+暴力)

    pid=34986" target="_blank" style="">题目连接:bnu 34986 Football on Table 题目大 ...

  10. (hdu step 6.3.2)Girls and Boys(比赛离开后几个人求不匹配,与邻接矩阵)

    称号: Girls and Boys Time Limit: 20000/10000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others ...