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. “采用VS2010至MFC4.2发育”最后溶液

    我层2010年这方面的研究进行了简单(http://blog.csdn.net/boweirrking/article/details/5477062),那时候没有深入思考过这当中的原理,最终给出的方 ...

  2. SQL Server创建索引

    原文:SQL Server创建索引 什么是索引 拿汉语字典的目录页(索引)打比方:正如汉语字典中的汉字按页存放一样,SQL Server中的数据记录也是按页存放的,每页容量一般为4K .为了加快查找的 ...

  3. NSIS脚本:更改壁纸

    原文 NSIS脚本:更改壁纸 我们在制作主题安装包的时候,经常要进行自动更改壁纸的操作,其实用NSIS实现这一点非常简单.示例代码如下: 01 Name "更改壁纸" 02 Out ...

  4. 【安德鲁斯】于java代码集drawableLeft给予适当的大小如何,当?

    textView.setCompoundDrawables(drawable, null, null, null);如果看不到图片,这是由于需要手动定drawable适当的大小,使用drawable. ...

  5. MOCK.JS 生成随机数据,拦截 Ajax 请求

    mock.js 的用处 前后端分离 :让前端攻城师独立于后端进行开发. 增加单元测试的真实性 :通过随机数据,模拟各种场景. 开发无侵入 :不需要修改既有代码,就可以拦截 Ajax 请求,返回模拟的响 ...

  6. Node.js 开发指南笔记

    第一章:node简介 介绍了node是什么:node.js是一个让javascript运行在服务器端的开发平台, node能做些什么:[书上的] 具有复杂逻辑的网站 基于社交网络的大规模Web应用 W ...

  7. handlebar的一些用法——个人使用总结

    handlebar的一些用法 概述与介绍 Handlebars 是 JavaScript 一个语义模板库,通过对view和data的分离来快速构建Web模板.它采用"Logic-less t ...

  8. iOS开发必看的博客汇总

    OneV's Den http://onevcat.com/ 沉船家园 http://beyondvincent.com/ NSHipster http://nshipster.cn/ Limboy ...

  9. bigdata_hadoop_namenode手动重启错误解决分析

    现象: 集群大面积异常,通过ambari启动不起来.逐一排查,顺序 hdfs -> mapreduce->yarn->hive -other hdfs下发现namenode ,dat ...

  10. 兼容Firefox和IE的onpropertychange事件oninput

    原文 兼容Firefox和IE的onpropertychange事件oninput onpropertychange能够捕获每次输入值的变化.例如:对象的value值被改变时,onpropertych ...