一. 遇到的问题

文章开头部分想先说一下自己的困惑,在用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. 使用Spring+Junit4.4进行测试(使用注解)

    http://nottiansyf.iteye.com/blog/345819 使用Junit4.4测试 在类上的配置Annotation @RunWith(SpringJUnit4ClassRunn ...

  2. 磁盘的读写-想起了SGA PGA DBWR LGWR...

    衡量性能的几个指标的计算中我们可以看到一个15k转速的磁盘在随机读写访问的情况下IOPS竟然只有140左右,但在实际应用中我们却能看到很多标有5000IOPS甚至更高的存储系统,有这么大IOPS的存储 ...

  3. [手机取证] “神器”IP-BOX的一些问题

    网上最近传的纷纷扬扬的iOS密码破解神器IP-BOX,很多人感兴趣,作为一个该产品的老用户,来破除一下迷信,顺便做个普及~ Q1:这东西好神奇,是不是所有都能破解? A1:支持简单密码的穷举,有条件的 ...

  4. scichart by Kline

    <UserControl x:Class="Abt.Controls.SciChart.Example.Examples.IWantTo.CreateMultiseriesChart. ...

  5. 使用delphi+intraweb进行微信开发2—intraweb以.net方式发布(以asp.net mvc4模式部署)在IIS(.net虚拟主机)上

    在第一讲使用delphi+intraweb进行微信开发1--微信平台接入中我们编写了一个简单的微信接口程序,这个程序我是用Stand Alone Server / Service 方式编译的程序,并且 ...

  6. 转 nutch网页快照乱码解决方法

    修改apache-tomcat-7.0.55\webapps\nutch-1.2下的cached.jsp 将content = new String(bean.getContent(details)) ...

  7. sftp自动授权登录

    客户的账号下执行 ssh-keygen -t rsa 生成秘钥文件 ~/.ssh/id_rsa --秘钥文件 ~/.ssh/id_rsa.pub --公钥文件 将公钥文件id_rsa.pub放到sft ...

  8. variably modified 'dist' at file scope|

    转自:http://blog.csdn.net/wusuopubupt/article/details/18408227 错误原因: The reason for this warning is th ...

  9. HTTP头的Expires与Cache-control

    HTTP头的Expires与Cache-control 1.概念 Cache-control用于控制HTTP缓存(在HTTP/1.0中可能部分没实现,仅仅实现了Pragma: no-cache) 数据 ...

  10. Effective c++

    static 声明在堆上申请静态存储 对于局部变量,将存储方式改为静态存储 对于全局变量,将连接方式局限在文件内 类中static变量:属于整个类,独立存储,没有this指针 inline inlin ...