AspNet MVC与T4,我定制的视图模板

原文http://www.cnblogs.com/hbq-fczzw/archive/2011/11/11/2191614.html

一. 遇到的问题

文章开头部分想先说一下自己的困惑,在用AspNet MVC时,完成Action的编写,然后添加一个视图,这个时候弹出一个添加视图的选项窗口,如下:

  很熟悉吧,继续上面说的,我添加一个视图,强类型的、继承母版页的视图,点击确定,mvc会为我们添加一些自动生成的代码,感觉很方便。呵呵,刚开始的时候还真方便一些,但也仅仅只是方便一些而已。当遇到以下情景的时候,可能我们就不觉得了:

  程序中都要对N个实体类进行CRUD,就只说添加的功能,生成一个强类型的Create视图,但是这个自带的Create视图的布局可能并不能 符合我们界面的要求,没关系啊,这个改改界面就ok了,这个是个不错的方法。但是有N个实体要进行CRUD的时候工作量就是time*N了,而且因为要求 页面风格一致,我们几乎在做一样的工作,有必要吗?

  当然没必要了,应该把重复的工作交给程序去做,省点时间去……

  举个例子吧,自带的Create视图使用的是div进行排版的,而我们需要的是table来进行排版,这个改起来不难,但蛮麻烦的。为什么我就不能定制Create视图的模板,让它生成我想要的布局呢?这个可以有。

二. 解决问题

  经过多方搜罗,终于找到了AspNet MVC中用于生成这些视图的东东:T4(Text Template Transformation Toolkit)文本模板转换工具箱。在MVC项目中正是使用了T4来生成视图模板的,它藏在哪呢?是在你VS安装目录下:...\Microsoft Visual Studio 10.0\Common7\IDE\ItemTemplates\CSharp\Web,在这个文件夹下面有MVC2、MVC3和MVC4的模板,创建什 么项目就会用到对应的模板。这里只演示MVC3 生成Razor的,用2和4的同学就自己摸索了,都差不多的。在MVC3文件夹里面的CodeTemplates文件夹中包含了生成Controller 和View两个文件夹。这里只说视图的,Controller里面的东西也可以去试试。在...\MVC 3\CodeTemplates\AddView\CSHTML,在这个文件夹下我们可以看到:

  这些正好是在创建强类型视图时系统自带的模板。好了,源头找到了,也应该进行修改了吧。不急,还有一点东西要了解,T4的基本编写语法:

T4基本语法

T4包括三个部分:

Directives(指令) 元素,用于控制模板如何被处理
Texts blocks(文本块) 用于直接复制到输出文件
Control blocks(控制块) 编程代码,用于控制变量显示文本

1)指令

   语法:

    <#@ DirectiveName [AttributeName = "AttributeValue"] … #>

     常用的指令

    <#@ template [language="C#"] [hostspecific="true"] [debug="true"] [inherits="templateBaseClass"] [culture="code"] [complierOption="options"] #>

    <#@ parameter type="Full.TypeName" name="ParameterName" #>

    <#@ output extension=".fileNameExtension" [encoding="encoding"] #>

    <#@ assembly name="[assembly strong name| assembly file name]" #>

    <#@ import namespace="namespace" #>

    <#@ include file="filepath" #>

2)文本块

  只需要输入文本就可以了

3)控制块

  <# #> 代码表达式

  <#= #> 显示表达式值

  <#+ #> 声明定义方法、变量

 T4和MVC2中的界面编码非常类似,这就不用多说了吧。

  其实不仅仅有页面布局的问题,还有数据显示的问题,验证的问题等等,只要是界面上需要重复编写的东西都可以使用T4来减少工作量。

  新建一个视图模板

  将CodeTemplates文件夹拷贝到项目程序的根目录下,覆盖默认视图模板。可以在MVC自带视图模板基础上进行修改,也可以自己新创建 一个。建议做法是新建一个视图模板,是Text Template文件.tt后缀的,然后将要改动的系统视图代码复制过来,再进行修改。在此之前,选中所有CodeTemplates文件夹中的tt文 件,右键属性,将Custome Tool项默认值清掉,原因还不清楚,知道的大虾指点下啊。(不去掉的话编译不通过,缺少了xxx程序集;也可以添加xxx程序集到项目中,不过没这个必 要)

  如本文修改Create模板,新建一个newCreate.tt文件,与Create.tt文件在同一文件夹内,将Create.tt内容复制到newCreate.tt中,在此基础上进行修改。

  浏览一下newCreate.tt的代码

  包括设定输出文件格式,引入程序集和命名空间。这些和我们编写cs时差不多,不多讲了。

  最关键的是MvcTextTemplateHost这个类,这个类存储着视图信息,如视图名、视图类型(部分视图、强类型视图)、是否继承母版 页等,具体的内容是有文章开篇的那一张图中设定的内容,即添加视图窗口的信息将会保存到MvcTextTemplateHost这个类实例去。好,那么这 个类藏在哪呢?上网搜了一下,VS2008 sp1是在...\Microsoft Visual Studio 9.0\Common7\IDE\Microsoft.VisualStudio.Web.Extensions.dll这个程序集中定义的。不过我找了 好久都没找到,可能是因为我用的是VS2010吧。还好最终还是找到了,是在...\Microsoft Visual Studio 10.0\Common7\IDE\Microsoft.VisualStudio.Web.Mvc.2.0.dll这个程序集中定义的,在Microsoft.VisualStudio.Web.Mvc命名空间内。具体的内容用Reflector反编译看下就知道了。

  啰嗦了好多东西,现在也该进入正题了。

修改视图生成界面

  将默认视图中div排版改成table排版,修改里面的back to list、Create等英文单词为中文。这个是最简单的修改了,只需修改tt文件的文本块内容就可以了。直接上效果图了:

  添加视图,这时我们自己新定义的模板已经在列表框中了:

  

  两个模板的效果,左边是由newCreate模板生成的,右边是由Create模板生成

  

  这个只是做了一下简单的页面修改,想怎么改就看具体要求了。

与Jquery联用,自动添加时间插件

  如果仅仅只是修改一下界面,那真的没必要这么大费周章的,我们还可以再进一步改进。在出生日期这一编辑框中,我们往往都会使用一个jquery 时间插件来美化。那我们可不可以让newCreate.tt文本模板在检测到DateTime类型时自动添加js代码,答案是可以的。

  在开始之前肯定是要先下载需要的js文件,布置到项目中。这里就只贴出关键部分的代码和效果图:

  

View Code

<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>    <script src="@Url.Content( "~/Content/jquery-ui/jquery-ui-1.8.12.custom.min.js")" type="text/javascript"></script>    <script src="@Url.Content( "~/Content/jquery-ui/jquery-ui-i18n.min.js")" type="text/javascript"></script>    <script src="@Url.Content( "~/Content/jquery-ui-timepicker-addon.js")" type="text/javascript"></script>    <link href="@Url.Content( "~/Content/jquery-ui/redmond/jquery-ui-1.8.5.custom.css")" rel="stylesheet" type="text/css" />    <script type="text/javascript">        $(document).ready(function () {// 自动绑定时间插件            <#        foreach (ModelProperty property in GetModelProperties(mvcHost.ViewDataType)) {if (property.UnderlyingType == typeof(DateTime)) {        #>        $('#<#= property.Name #>').datetimepicker({            currentText: '当前时间',            closeText: '完成',            timeText: '时间',            hourText: '小时',            minuteText: '分钟',            dateFormat: 'yy年mm月dd日',        });        <#            }        }        #>        });    </script>

 

  

  这样只要程序中有用到时间的地方就会自动生成js代码然后就方便很多了。当然可以写个MVC的html扩展方法来实现,方便的程度差不多吧。

数据验证

  还可以再改进么?必然可以,只要你想得到。数据验证,这个在添加编辑数据的地方都会用到。这回不说能够全自动吧,起码也是半自动。其实在MVC 中已经有通过后台编写Metadata来进行数据验证了,不过那个是在后台。这回要做的是在前端页面进行半自动添加js验证代码。要想做得方便的话就得自 己编写一些js代码,这个肯定要的,方便的前提是先需要复杂一段时间(不过也没多复杂)。

  在做验证的时候,我们无非要验证不可空、验证数字、手机号码、邮件地址等这些东西。还有一点是js代码是通过文本模板生成的,这就要求我们需要 创建一个通用的验证函数。这个函数怎么设计呢?想想,每一个验证都会对应一个form表单、需要验证的格式、验证不通过时的提示信息。也就是说这个js函 数有三个参数:

  1 被验证的表单的id:这个可以在文本模板中获得

  2 验证的格式:这个编写一个js的枚举类型吧,把要用到的所有格式的正则表达式写好。

  3 提示信息:这个总不能也要自动生成吧

  说了思路我就直接贴代码和效果图了,想去了解的可以下载程序来看一下。

  看一下这个自动生成的js代码:

  

  第一个formValidatorRegex.js文件存储的就是各种格式的正则表达式。

  第二个formValidatorUI.js文件是checkValue这个验证函数的定义。

  自动生成之后就可以做一些小的修改来达到我们需要的验证功能了,两个地方要修改的,第一个就是设置验证格式,第二个是填写提示信息。

  现在把newCreate.tt的全部代码献上:

  

View Code

<#@ template language="C#" HostSpecific="True" #><#@ output extension=".cshtml" #><#@ assembly name="System.ComponentModel.DataAnnotations" #><#@ assembly name="System.Core" #><#@ assembly name="System.Data.Entity" #><#@ assembly name="System.Data.Linq" #><#@ import namespace="System" #><#@ import namespace="System.Collections.Generic" #><#@ import namespace="System.ComponentModel.DataAnnotations" #><#@ import namespace="System.Data.Linq.Mapping" #><#@ import namespace="System.Data.Objects.DataClasses" #><#@ import namespace="System.Linq" #><#@ import namespace="System.Reflection" #><#MvcTextTemplateHost mvcHost = (MvcTextTemplateHost)(Host);  // 关键类,其实例是通过Add View窗口所做的设定获取的#>@model <#= mvcHost.ViewDataTypeName #>    <#// The following chained if-statement outputs the file header code and markup for a partial view, a content page, or a regular view.if(mvcHost.IsPartialView) {    // 部分视图#>

<#} else if(mvcHost.IsContentPage) {    // 内容页#>

@{    ViewBag.Title = "<#= mvcHost.ViewName#>";    @*ViewName视图名,第一张图中的View name 中的值*@<#if (!String.IsNullOrEmpty(mvcHost.MasterPageFile)) {    // 母版页#>    Layout = "<#= mvcHost.MasterPageFile#>";    @*MasterPageFile母版页路径*@<#}#>}

<h2><#= mvcHost.ViewName#></h2>

<#} else {#>

@{    Layout = null;}

<!DOCTYPE html>

<html><head>    <title><#= mvcHost.ViewName #></title></head><body><#    PushIndent("");}#><#if (mvcHost.ReferenceScriptLibraries) {    // ReferenceScriptLibraries是否勾取了引入javascript脚本库#><#if (!mvcHost.IsContentPage) {#><script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script><#    }#><script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script><script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

<#}#>@using (Html.BeginForm()) {    @Html.ValidationSummary(true)    <fieldset>        <legend><#= mvcHost.ViewDataType.Name #></legend>        <table class="cls"><#foreach (ModelProperty property in GetModelProperties(mvcHost.ViewDataType)) {if (!property.IsPrimaryKey && !property.IsReadOnly) {#>        <tr>            <td>                @Html.LabelFor(model => model.<#= property.Name #>)            </td>            <td>                @Html.EditorFor(model => model.<#= property.Name #>)                @Html.ValidationMessageFor(model => model.<#= property.Name #>)            </td>        </tr><#    }}#>        <tr>            <td></td>            <td><input type="submit" id="submit1" value="创建" /></td>        </tr>        </table>    </fieldset>}

<div>    @Html.ActionLink("返回列表", "Index")</div><div id="alertDialog" title="消息提示"></div>

<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>    <script src="@Url.Content( "~/Content/jquery-ui/jquery-ui-1.8.12.custom.min.js")" type="text/javascript"></script>    <script src="@Url.Content( "~/Content/jquery-ui/jquery-ui-i18n.min.js")" type="text/javascript"></script>    <script src="@Url.Content( "~/Content/jquery-ui-timepicker-addon.js")" type="text/javascript"></script>    <link href="@Url.Content( "~/Content/jquery-ui/redmond/jquery-ui-1.8.5.custom.css")" rel="stylesheet" type="text/css" />    <script src="@Url.Content("~/Content/formValidatorRegex.js")" type="text/javascript"></script>    <script src="@Url.Content("~/Content/formValidatorUI.js")" type="text/javascript"></script>

<script type="text/javascript">        $(document).ready(function () {// 添加验证代码        $("#submit1").click(function () {if (1==2  //初始化,验证默认不通过(验证时将其删除)                <#foreach (ModelProperty property in GetModelProperties(mvcHost.ViewDataType)) {                #>                    && checkValue($("#<#= property.Name #>").val(), "", "")                <#                }                #>                ) {  //checkValue($("#").val(), regexEnum, "") //第1个参数是需要验证的值//第2个参数为正则表达式//第3个参数是验证不通过的提示信息                    return true;                }return false;            });

// 自动绑定时间插件            <#foreach (ModelProperty property in GetModelProperties(mvcHost.ViewDataType)) {if (property.UnderlyingType == typeof(DateTime)) {        #>        $('#<#= property.Name #>').datetimepicker({            currentText: '当前时间',            closeText: '完成',            timeText: '时间',            hourText: '小时',            minuteText: '分钟',            dateFormat: 'yy年mm月dd日',        });        <#            }        }        #>        });    </script>

<#// The following code closes the asp:Content tag used in the case of a master page and the body and html tags in the case of a regular view page#><#if(!mvcHost.IsPartialView && !mvcHost.IsContentPage) {    ClearIndent();#></body></html><#}#>

<#+// Describes the information about a property on the modelclass ModelProperty {public string Name { get; set; }public string ValueExpression { get; set; }public Type UnderlyingType { get; set; }public bool IsPrimaryKey { get; set; }public bool IsReadOnly { get; set; }}

// Change this list to include any non-primitive types you think should be eligible for display/editstatic Type[] bindableNonPrimitiveTypes = new[] {typeof(string),typeof(decimal),typeof(Guid),typeof(DateTime),typeof(DateTimeOffset),typeof(TimeSpan),};

// Call this to get the list of properties in the model. Change this to modify or add your// own default formatting for display values.List<ModelProperty> GetModelProperties(Type type) {    List<ModelProperty> results = GetEligibleProperties(type);

foreach (ModelProperty prop in results) {if (prop.UnderlyingType == typeof(double) || prop.UnderlyingType == typeof(decimal)) {            prop.ValueExpression = "String.Format(\"{0:F}\", " + prop.ValueExpression + ")";        }else if (prop.UnderlyingType == typeof(DateTime)) {            prop.ValueExpression = "String.Format(\"{0:g}\", " + prop.ValueExpression + ")";        }    }

return results;}

// Call this to determine if the property represents a primary key. Change the// code to change the definition of primary key.bool IsPrimaryKey(PropertyInfo property) {if (string.Equals(property.Name, "id", StringComparison.OrdinalIgnoreCase)) {  // EF Code First convention        return true;    }

if (string.Equals(property.Name, property.DeclaringType.Name + "id", StringComparison.OrdinalIgnoreCase)) {  // EF Code First convention        return true;    }

foreach (object attribute in property.GetCustomAttributes(true)) {if (attribute is KeyAttribute) {  // WCF RIA Services and EF Code First explicit            return true;        }

var edmScalar = attribute as EdmScalarPropertyAttribute;if (edmScalar != null && edmScalar.EntityKeyProperty) {  // EF traditional            return true;        }

var column = attribute as ColumnAttribute;if (column != null && column.IsPrimaryKey) {  // LINQ to SQL            return true;        }    }

return false;}

// This will return the primary key property name, if and only if there is exactly// one primary key. Returns null if there is no PK, or the PK is composite.string GetPrimaryKeyName(Type type) {    IEnumerable<string> pkNames = GetPrimaryKeyNames(type);return pkNames.Count() == 1 ? pkNames.First() : null;}

// This will return all the primary key names. Will return an empty list if there are none.IEnumerable<string> GetPrimaryKeyNames(Type type) {return GetEligibleProperties(type).Where(mp => mp.IsPrimaryKey).Select(mp => mp.Name);}

// HelperList<ModelProperty> GetEligibleProperties(Type type) {    List<ModelProperty> results = new List<ModelProperty>();

foreach (PropertyInfo prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) {    // 遍历所有公共实例属性        Type underlyingType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;    // 去掉可空之后的类型// GetGetMethod()获取公共get访问器(就是属性定义中的get方法)、GetIndexParameters()获取索引器// 这个判断条件就是属性必须有get方法,且不能为索引器类型,再一个返回类型必须是基元类型或者[String、Guid、DateTime、TimeSpan、decimal、TimeSpan、DateTimeOffset]这些类型之一        if (prop.GetGetMethod() != null && prop.GetIndexParameters().Length == 0 && IsBindableType(underlyingType)) {            results.Add(new ModelProperty {    // 添加到ModelProperty                Name = prop.Name,                ValueExpression = "Model." + prop.Name,                UnderlyingType = underlyingType,                IsPrimaryKey = IsPrimaryKey(prop),                IsReadOnly = prop.GetSetMethod() == null            });        }    }

return results;}

// Helperbool IsBindableType(Type type) {return type.IsPrimitive || bindableNonPrimitiveTypes.Contains(type);}

#>

 

  好了,写得差不多了。其实还好很多可以改进的地方,感兴趣的同学可以再去修改一下默认的controller模板,然后再结合自己自定的视图模板,完成一个实体的简单增删改查应该用10分钟就可以搞定了,只是简单的,不要想太多了。

  程序下载:MvcT4Project

  ps:在2011年11月11日巨型光棍节这天送上自己的处男作,希望自己和园子里面所有的兄弟早日脱离光棍大军,祝大家节日快乐!

  ps again:文章有说得不清楚或不详细的地方还望海涵。

 

时间: 2025-01-25 15:17:11

AspNet MVC与T4,我定制的视图模板的相关文章

AspNet MVC是什么?

 ASP.NET 是一个开发框架,用于通过 HTML.CSS.JavaScript 以及服务器脚本来构建网页和网站. MVC 是三个 ASP.NET 开发模型之一. MVC 是用于构建 web 应用程序的一种框架,使用 MVC (Model View Controller) 设计: Model(模型)表示应用程序核心(比如数据库记录列表) View(视图)对数据(数据库记录)进行显示 Controller(控制器)处理输入(写入数据库记录) MVC 模型同时提供对 HTML.CSS 以及 Jav

aspnet mvc 拦截器 怎么不执行action的代码

问题描述 aspnet mvc 拦截器 怎么不执行action的代码 public class CheckCertAttribute : ActionFilterAttribute { /// /// 验证证书是否有效 /// public bool ValidCert { get; set; } /// /// 解析证书 /// public bool ResolveCert { get; set; } public override void OnActionExecuting(Action

ASP.NET MVC 5 学习教程:添加视图

原文 ASP.NET MVC 5 学习教程:添加视图 起飞网 ASP.NET MVC 5 学习教程目录: 添加控制器 添加视图 修改视图和布局页 控制器传递数据给视图 添加模型 创建连接字符串 通过控制器访问模型的数据 生成的代码详解 使用 SQL Server LocalDB Edit方法和Edit视图详解 添加查询 Entity Framework 数据迁移之添加字段 添加验证 Details 和 Delete 方法详解 在本节内容中,我们将修改HelloWorldController类,使

ASP.NET MVC :实现我们自己的视图引擎

在ASP.NET MVC的一个开源项目MvcContrib中,为我们提供了几个视图引擎,例如NVelocity, Brail, NHaml, XSLT.那么如果我们想在ASP.NET MVC中实现我们自己的一个视图引擎,我们应该要怎么做呢? 我们知道呈现视图是在Controller中通过传递视图名和数据到RenderView()方法来实现的.好,我们就从这里下手.我们查看一下ASP.NET MVC的源代码,看看RenderView()这个方法是如何实现的: 以下为引用的内容:protected

【译】ASP.NET MVC 5 教程 - 3:添加视图

原文:[译]ASP.NET MVC 5 教程 - 3:添加视图 在本节内容中,我们将修改HelloWorldController类,使用视图模板来干净利索的封装生成HTML响应客户端的过程. 您将创建一个使用Razor 视图引擎的视图模板文件..cshtml扩展名的文件都是基于 razor 视图模板文件,Razor 视图引擎将编写视图模板所需的代码降至最低. 目前的 Index 方法返回一条消息,是在控制器类中直接写入的字符串.更改 Index 方法使其返回一个View对象,如以下代码所示: 1

ThinkPHP框架视图详细介绍 View 视图--模板(九)

原文:ThinkPHP框架视图详细介绍 View 视图--模板(九) 视图也是ThinkPHP使用的核心部分: 一.模板的使用 a.规则模板文件夹下[TPL]/[分组文件夹/][模板主题文件夹/]和模块名同名的文件夹[Index]/和方法名同名的文件[index].html(.tpl)  -->更换模板文件的后缀名(修改配置文件) 'TMPL_TEMPLATE_SUFFIX'=>'.tpl',//更改模板文件后缀名,默认是html b.修改模板文件目录层次Tpl/Index/index.htm

SharePoint 2013 定制搜索显示模板(二)

前言 之前一篇博客,简单的介绍了如何定制搜索显示模板,这一次,我们介绍一下如何定制搜索显示时,弹出来的那个页面,相信这个大家也都会遇到的. 1.第一部分就是搜索显示模板的部分,第二部分就是搜索项目详情的部分,如下图: 2.按照之前一篇博客介绍的过程,找到下面红框的html,下载一份副本到本地修改,如下图: 3.改个名字上传回去,之后也会自动生成一个JavaScript文件,具体信息对比一下,不要选择错了(一般默认就是对的,如果有问题了记得校对一下),如下图: 4.首先修改一下上一篇介绍的显示模板

ASP.NET MVC 在控制器中获取某个视图动态的HTML代码

如果我们需要动态的用AJAX从服务器端获取HTML代码,拼接字符串是一种不好的方式,所以我们将HTML代码写在cshtml文件中,然后通过代码传入model,动态获取cshtml中的HTML代码 当然,我们想要使用通用的方法去获取cshtml,就必须重写RazorViewEngine视图引擎,配置视图搜索位置 在查找一个视图时,Razor视图引擎遵循了MVC框架早期版本建立起来的约定.例如,如果你请求与Home控制器相关的Index视图,Razor会审查这样的视图列表:  ~/Views/Hom

ASP.NET MVC Model元数据及其定制:一个重要的接口IMetadataAware

在介绍用于自定义Model元数据属性的AdditionalMetadataAttribute特性时我们提到了它实现的接口IMedataAware,我们说这是一个非常重要并且有用的接口,通过自定义实现该接口的特性我们可以对最终生成的Model元数据进行自由地定制.如下面的代码片断所示,IMedataAware接口具有唯一的方法成员OnMetadataCreated.当Model元数据被创建出来后,会先获取上述的这一系列标注特性对其进行初始化,然后获取应用在目标元素上所有实现了IMedataAwar