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 上
。。。

运行,通过!

时间: 2024-08-29 13:08:54

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

MVC系列——MVC源码学习:打造自己的MVC框架(四:自定义视图)

前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾.对于这个系列,通过学习源码,博主也学到了很多东西,在此还是把博主知道的先发出来,供大家参考. 本文原创地址:http://www.cnblogs.com/landeanfen/p/6019719.html MVC源码学习系列文章目录: MVC系列——MVC源码学习:打造自己的MVC框架(一) MVC

MVC @Html 扩展方法

在使用MVC开发过程中发现在View中需要一些自定义的方法,以前在webfrom开发是则是使用一个静态类,在里面编辑许多经常使用的方法来解决的.现在发现在MVC里面可以自定义扩展的HtmlHelper方法,具体操作如下:1.新建一个类  可以在项目中添加一个文件夹,命名为Helpers,在这个文件夹中可以添加各个类型的扩展类.下面我们添加一个文本处理扩展类,命名TextHelper.cs.注意:把该类的命名空间改为 namespace System.Web.Mvc,这样就可以在页面中使用该扩展方

jquery 扩展方法,自定义函数等一些写法

// 传参数 var aa = function(x){ //弹出对象 x 里的 aa 变量和 bb 变量 alert(x.aa + " 我成功啦 " + x.bb); } $.windowbox = aa; $.windowbox({ aa: "哈哈", bb: "啦啦" }); //方法定义 $.windowbox = { //定义一个方法aa aa: function(){ alert("aa"); }, //定义一个方

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

ASP.NET mvc的razor视图引擎是一个非常好的.NET MVC框架内置的视图引擎.一般情况我们使用.NET MVC框架为我们提供的这个Razor视图引擎就足够了.但是有时我们想在我们的项目支持多模板&skins机制,比如我们可能会有多套的模板,也就是多个View风格,而我们只需要改一下配置文件就可以轻松的改变页面的风格和模板.实现这个功能有两种方式: 一.使用接口IViewEngine自己完成一个类似Razor视图引擎的功能. 二.继承类RazorViewEngine类,重写它的一些方

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

一:自定义视图 1.自定义一个实现View接口的类,添加@Component注解,将其放入SpringIOC容器 package com.zzj.view; import java.io.PrintWriter; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.s

Asp.net模块化开发之Mvc分区扩展框架

对于一个企业级项目开发,模块化是非常重要的. 默认Mvc框架的AreaRegistration对模块化开发已经支持比较好了,还有必要扩展吗?还能扩展吗? 本文中的栗子是使用.net4.0.Mvc4.0及Unity2.0(企业库4.0)的,提供完整源码. 本分区扩展集成了IoC和分区DI(依赖注入)及分区过滤器的支持. 本分区扩展框架(Fang.Mvc)在演示栗子源码中包含完整源码,拿到自己的项目直接引用即可使用了. 感兴趣的同学请继续,用AreaRegistration有不爽的看官请拭目以待..

扩展方法学习发展之路

大学学习的java,工作后转成了C#,由于工作需要,全力去学习了C#,学习中,相信大家都会有相同的疑惑,在判断一个字符串是否为空的时候总会用到string.IsNullOrEmpty(s)这个方法,最开始就想到了是不是反射,后面才知道这是c#的扩展方法,后面的内容慢慢讲解我对扩展方法由浅到深的理解与用法. 1:扩展方法的自定义实现,我这里特别用到了判断类型是否为数值类型的方法(可以去比较学习下),里面子定义了4个扩展方法 1 namespace ConsoleApplication1 2 { 3

扩展方法用法及其原理和注意事项

前言 一直以来尤其像C#一些常见的语法,本人更愿意去探讨其内部实现的原理,为什么要这么做呢?只是为了当我真正在开发中运用语法的时候不会因为犯常识性错误或者说因为一些注意事项未曾注意到而耽误一些无谓的时间,同时也能理解的更深入而不是仅仅停留在表面(或许理解也不是太透).(当然本人能力有限,太高深的东西必定是研究不明白了,也只有这能力了). 概念 扩展方法使你能够向现有类型“添加”方法,而无需创建新的派生类型.重新编译或以其他方式修改原始类型. 扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例

关于导航自定义视图距离边界问题,点击状态栏TableView不能回滚到顶部问题

一: 默认Navigation的自定义customView,设置为 Left or Right BarButtonItem 的时候会 与屏幕边界有个15像素的距离. 导致自定义视图的上的子视图响应区域有限. 解决方法: //自定义视图 UIBarButtonItem *backBtn = [[UIBarButtonItem alloc] initWithCustomView:backButton]; //间距 UIBarButtonItem *negativeSpacer = [[UIBarBu