一. 遇到的问题

文章开头部分想先说一下自己的困惑,在用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文件,布置到项目中。这里就只贴出关键部分的代码和效果图:

  

<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的全部代码献上:

  

<#@ 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 model
class 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/edit
static 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);
} // Helper
List<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;
} // Helper
bool IsBindableType(Type type) {
return type.IsPrimitive || bindableNonPrimitiveTypes.Contains(type);
} #>

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

 

 

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

  1. MVC开发T4代码生成之二----vs模板扩展

    在上一篇MVC开发T4代码生成之一----文本模板基础中介绍了与T4模板相关的基础知识,并对MVC内使用T4模板添加视图做了介绍.知道了T4模板的使用后自然就想着怎么对vs自带的T4模板进行扩展,添加 ...

  2. vs 2013下自定义ASP.net MVC 5/Web API 2 模板(T4 视图模板/控制器模板)

    vs 2013下自定义ASP.net MVC 5/Web API 2  模板(T4 视图模板/控制器模板): Customizing ASP.NET MVC 5/Web API 2 Scaffoldi ...

  3. 【ASP.Net MVC】AspNet Mvc一些总结

    AspNet Mvc一些总结 RestaurantReview.cs using System; using System.Collections.Generic; using System.Comp ...

  4. VS2015突然报错————Encountered an unexpected error when attempting to resolve tag helper directive '@addTagHelper' with value 'Microsoft.AspNet.Mvc.Razor.TagHelpers.UrlResolutionTagHelper

    Encountered an unexpected error when attempting to resolve tag helper directive '@addTagHelper' with ...

  5. AspNet MVC中各种上下文理解

    0  前言 AspNet MVC中比较重要的上下文,有如下: 核心的上下文有HttpContext(请求上下文),ControllerContext(控制器上下文) 过滤器有关有五个的上下文Actio ...

  6. Solon Web 开发,七、视图模板与Mvc注解

    Solon Web 开发 一.开始 二.开发知识准备 三.打包与运行 四.请求上下文 五.数据访问.事务与缓存应用 六.过滤器.处理.拦截器 七.视图模板与Mvc注解 八.校验.及定制与扩展 九.跨域 ...

  7. Encountered an unexpected error when attempting to resolve tag helper directive '@addTagHelper' with value '"*, Microsoft.AspNet.Mvc.TagHelpers"'

    project.json 配置: { "version": "1.0.0-*", "compilationOptions": { " ...

  8. ASPNET MVC中断请求

    ASPNET MVC如何正确的中断请求? 感觉是这样? 在aspnet开发过程中如果想要中断当前的http处理,以前在aspnet中一直是Response.End(); 在这Response.End( ...

  9. [Asp.net MVC]Asp.net MVC5系列——添加视图

    目录 系列文章 概述 添加视图 总结 系列文章 [Asp.net MVC]Asp.net MVC5系列——第一个项目 概述 在这一部分我们添加一个新的控制器HelloWorldController类, ...

随机推荐

  1. sql 中 left join 的使用

    left join .是以左表为基础,查询右表的值.如果在右表中没用没有数据,则为NULL. 这里有三张表. 线路bs_line:id,name(id主键) 线路段bs_seg:id,l_id,nam ...

  2. SQLSERVER分页查询

    转:http://blog.csdn.net/qiaqia609/article/details/41445233   Sqlserver数据库分页查询一直是Sqlserver的短板,   第一种方案 ...

  3. BAE log服务的配置(nodejs)

    log4js.loadAppender('baev3-log'); var options = { 'user': appConfig.AK, //这里是在用户的安全认证中的Access Key ID ...

  4. VC++6.0 配置CppUTest测试环境

    最近看<软件项目成功之道>,书中无数次提及到“单元测试”对于项目成败的重要性,看到同事将CppUTest用于Linux动态库测试,于是在VC++6.0环境下搭建一个基于CppUTest的单 ...

  5. 删除项目中的.svn信息

    有时候我们新开发一个项目时,会将以前的项目从svn上down下来,然后复制一份.这样就会有个问题,项目中的svn信息就会一直存在.下面介绍删除方法: 1.新建一个.txt的文档.然后将下面代码粘贴到文 ...

  6. JS正则大全

    验证数字:^[0-9]*$ 验证n位的数字:^\d{n}$ 验证至少n位数字:^\d{n,}$ 验证m-n位的数字:^\d{m,n}$ 验证零和非零开头的数字:^(0|[1-9][0-9]*)$ 验证 ...

  7. 解决WAMP搭建PHP环境后后局域网其他机器无法访问的问题

    刚安装wamp以后本地访问localhost或者127.0.0.1可以访问,但是如果局域网内其他电脑访问则出现403错误.从网上找了很多,各种说法都有了,却没几个好用的.解决问题方法如下: 1,首先确 ...

  8. [转载]SVN-主干/分支

    一个大项目在开发中可能会拆分成几个小项目,分别分去,同时共通的部分再由人做,做完后再统一合并.同时,在开发中,共通的部分修改后,其它人要保持同步. 这种情况反应到SVN的分支/合并功能上,再贴切不过了 ...

  9. vs2010 sql server 2008数据库管理界面安装

    http://jingyan.baidu.com/article/1e5468f928e106484961b7b0.html

  10. EntityFramwork入门

    原blog https://msdn.microsoft.com/zh-cn/data/ee712907 本人测试环境:VS2015+SQL Server 2008 R2 遇到问题 使用SQL Man ...