一、校验 — 表单不是你想提想提就能提
1.1 DataAnnotations(数据注解)
位于 System.ComponentModel.DataAnnotations
命名空间中的特性指定对数据模型中的各个字段的验证。这些特性用于定义常见的验证模式,例如范围检查和必填字段。而 DataAnnotations
特性使 MVC 能够提供客户端和服务器验证检查,使你无需进行额外的编码来控制数据的有效。
通过为模型类增加数据描述的 DataAnnotations ,我们可以容易地为应用程序增加验证的功能。DataAnnotations
允许我们描述希望应用在模型属性上的验证规则,ASP.NET MVC
将会使用这些 DataAnnotations
,然后将适当的验证信息返回给用户。
在DataAnnotations
为我们所提供的众多内置验证特性中,用的最多的其中的四个是:
(0)[DisplayName]:显示名 – 定义表单字段的提示名称
(1)[Required] :必须 – 表示这个属性是必须提供内容的字段
(2)[StringLength]:字符串长度 – 定义字符串类型的属性的最大长度
(3)[Range]:范围 – 为数字类型的属性提供最大值和最小值
(4)[RegularExpression]:正则表达式 – 指定动态数据中的数据字段值必须与指定的正则表达式匹配
1.2 使用DataAnnotations为Model进行校验
假设我们的Model
中有一个UserInfo
的实体,其定义如下:
public class UserInfo
{
public int Id { get; set; }
public string UserName { get; set; }
public int Age { get; set; }
}
UserInfo
的属性很简单,只有三个:Id,UserName
和Age
三个字段;现在我们可以为其增加验证特性,看看其为我们提供的强大的校验功能。
(1)非空验证
添加特性:
[Display(Name="用户名")]
[Required(ErrorMessage = "*姓名必填")]
public string UserName { get; set; }
[Display(Name = "年龄")]
[Required(ErrorMessage = "*年龄必填")]
public int Age { get; set; }
验证效果:
(2)字符串长度验证
添加特性:
[Display(Name="用户名")]
[Required(ErrorMessage = "*姓名必填")]
[StringLength(5, ErrorMessage = "*长度必须小于5")]
public string UserName { get; set; }
验证效果:
(3)范围验证
添加特性:
[Display(Name = "年龄")]
[Required(ErrorMessage = "*年龄必填")]
[Range(18, 120)]
public int Age { get; set; }
验证效果:
(4)正则表达式验证
添加特性:验证用户输入的是否是数字,正则表达式匹配
[Display(Name = "年龄")]
[Required(ErrorMessage = "*年龄必填")]
[Range(18, 120)]
[RegularExpression(@"^\d+$", ErrorMessage = "*请输入合法数字")]
public int Age { get; set; }
验证效果:
(5)浏览所生成的HTML代码
从上图可以看出,我们在浏览器端的校验都是通过为html标签设置自定义属性来实现的,我们在Model
中为其添加的各种校验特性,都会在客户端生成一个特定的属性,例如:data-val-length-max=“5”
与data-val-length="*长度必须小于5"
对应[StringLength(5, ErrorMessage = "*长度必须小于5")]
。然后,通过jquery validate
在客户端每次提交之前进行校验,如果校验匹配中有不符合规则的,则将message
显示在一个特定的span
标签(class="field-validation-valid"
)内,并阻止此次表单提交操作。
1.3 使用DataAnnotations的注意事项
(1)首先,要确保需要进行校验的页面中引入了指定的几个js文件:
<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>
当然,jquery
库的js文件也是必须的,而且在上面这两个js之前引入;
(2)在 Web.config
的appSettings
中,已经默认支持了客户端验证(MVC3.0及更高版本中默认支持,MVC2.0则需要修改一下):
<!-- 是否启用全局客户端校验 -->
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
PS:
Unobtrusive Javascript
有三层含义:一是在HTML代码中不会随意的插入Javsscript代码,只在标签中加一些额外的属性值,然后被引用的脚本文件识别和处理;
二是通过脚本文件所增加的功能是一种渐进式的增强,当客户端不支持或禁用了Javsscript时网页所提供的功能仍然能够实现,只是用户体验会降低;
三是能够兼容不同的浏览器。
(3)在Action中如果要对客户端是否通过了校验进行验证,可以通过以下代码实现:
[HttpPost]
public ActionResult Add(UserInfo userInfo)
{
if (ModelState.IsValid)
{
// To do fun
}
return RedirectToAction("Index");
}
如果通过校验,则ModelState
的IsValid
属性(bool类型)会变为true
,反之则为false
。
二、ASP.Net MVC下的两种AJAX方式
2.1 使用JQuery AJAX方式
首先,在ASP.Net MVC
中使用此种方式跟普通的WebForm
的开发方式是一致的,需要注意的是:Url地址不同->请求的是Controller
下的Action
,例如在WebForm
中请求的url通常是/Ajax/UserHandler.ashx
,而在MVC中请求的url通常为:/User/GetAll
。
例如,我们在一个View
中添加一个按钮,用于使用AJAX
获取一个服务器端的时间:
<h1>JQuery Ajax方式</h1>
<input id="btnJQuery" type="button" value="获取服务器时间" />
在Home
控制器中增加一个用于返回时间的Action
:
public ActionResult GetServerDate()
{
return Content(DateTime.Now.ToString());
}
在View
中增加一段JQuery
代码,为btnJQuery
按钮绑定一个Click
事件:
$(function () {
$("#btnJQuery").click(function () {
$.post("/Home/GetServerDate", {}, function (data) {
if (data != null) {
$("#spTime").html(data);
}
});
});
});
这里通过JQuery AJAX发送一个异步的POST
请求,获取服务器时间结果,并将其显示在span标签内:
至此,一个使用JQuery Ajax
的MVC
页面就完成了。但是,这仅是一个最简单的AJAX
示例,在实际开发中往往比较复杂一点。
需要注意的是:
(1)如果你在
JQuery AJAX
中使用的是get
方式的提交,那么在在使用Json
返回JsonResult
时注意要将第二个参数设置允许Get
提交方式:return Json("",JsonRequestBehavior.AllowGet)
,否则你用get
方式是无权执行要请求的Action
方法的。(2)在
Ajax
开发中要注意Ajax
方法体内的参数设置正确,特别是参数名要和Action
中的参数名保持一致;(3)如果在
Action
中为其设置了[HttpPost]
或[HttpGet]
,那么提交方式要跟Action
打的标签一致;
2.2 使用Microsoft AJAX方式
在ASP.Net MVC中除了可以使用JQuery AJAX外,Microsoft为我们提供了另一套实用且更简单的AJAX方案,我们姑且称其为:Microsoft AJAX方式。
(1)首先:
需要将微软提供的js脚本引入到页面中:其实就是jquery.unobtrusive-ajax.js
<script src="~/Scripts/jquery-1.7.1.min.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
确保在Web.config
中启用了Unobtrusive JavaScript
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
(2)其次,使用Ajax.BeginForm
方法构造一个form
表单:
<h1>Microsoft Ajax方式</h1>
@using (Ajax.BeginForm("GetServerDate", "Home", new AjaxOptions()
{
HttpMethod = "POST",
Confirm = "您确定要提交?",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "spResult",
OnSuccess = "afterSuccess",
LoadingElementId="loading"
}))
{
<table>
<tr>
<td>用户名:</td>
<td>
<input id="txtUserName" name="UserName" /></td>
</tr>
<tr>
<td>密 码:</td>
<td>
<input id="txtPassword" name="Password" /></td>
</tr>
<tr>
<td align="center" colspan="2">
<input id="btnAjax" type="submit" value="提 交" />
</td>
</tr>
<tr>
<td align="center" colspan="2">
<div id="loading" style="display:none">
<img style="vertical-align:middle" src="~/Content/ico_loading2.gif" />正在获取中,请稍候...
</div>
<span id="spResult"></span>
</td>
</tr>
</table>
}
这里需要注意的是:
- ①
Ajax.BeginForm
没有提供闭合的方法,需要使用Using
配合关闭; - ②
AjaxOptions
参数的设置:
HttpMethod
代表此次AJAX
请求到底是POST
方式还是GET
方式?这里是POST
方式;
Confirm
代表点击提交按钮后提出的确认对话框,并给出用户给定的提示语,这里是:您确定要提交?
InsertionMode
代表请求获得后的数据是要替换还是追加,一般选择替换,即Replace
;
UpdateTargetId
代表需要替换的div
标签的Id
,这里是一个span
标签,代表需要显示的信息都显示在这个span
内;
OnSuccess
代表请求成功后所需要执行的回调方法,是一个js
方法,可以自定义,这里是一个function afterSuccess()
的方法;
function afterSuccess(data) { //alert("您已成功获取数据:" + data); }
LoadingElementId=”loading”是一个很有意思的属性,代表在ajax请求期间为了提供良好的用户体验,可以给出一个正在加载中的提示,而这个LoadingElementId则代表一个提示的div区域的Id。这里主要是指id为loading的这个div,其中有一张gif图片及一句话:正在获取中,请稍等…的提示。
<div id="loading" style="display:none"> <img style="vertical-align:middle" src="~/Content/ico_loading2.gif" />正在获取中,请稍候... </div>
为了显示加载提示的效果,我们人为地修改一下Action
方法,使用Thread.Sleep(3000)
来延迟一下请求返回时间
public ActionResult GetServerDate()
{
System.Threading.Thread.Sleep(3000);
return Content(DateTime.Now.ToString());
}
好了,现在我们可以看一下效果如何:
到此,我们的Microsoft AJAX
就算完成了一个最简单的Demo了。那么,我们不禁想知道Microsoft AJAX是怎么做到的?跟校验一样,我们浏览一下生成的form表单就知道了:
原来我们在AjaxOptions
中所设置的参数也被解析成了form的自定义属性,它们的对应关系如下:
三、为AOP而生 — ASP.Net MVC默认的过滤器
3.1 过滤器初步
大一点的项目总会有相关的AOP
面向切面的组件,而MVC
(特指:Asp.Net MVC
,以下皆同)项目中Action
在执行前或者执行后我们想做一些特殊的操作(比如身份验证,日志,异常,行为截取等),而不想让MVC
开发人员去关心和写这部分重复的代码。那么,我们可以通过AOP
截取实现,而在MVC
项目中我们就可以直接使用它提供的Filter的特性帮我们解决,不用自己实现复杂的AOP
了。
AOP:Aspect Oriented Programming(AOP)
是较为热门的一个话题。AOP,国内大致译作“面向切面编程”。针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。利用
AOP
可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等。
3.2 微软提供的几种默认过滤器
微软默认为我们提供了四种类型的过滤器(Filter
),如下图所示:
这里,我们主要来看看ActionFilter
(Action过滤器)和ExceptionFilter
(异常过滤器)的使用:
(1)Action Filter
ActionFilterAttribute
默认实现了IActionFilter
和IResultFilter
。而ActionFilterAttribute
是一个Abstract
的类型,所以不能直接使用,因为它不能实例化,所以我们想使用它必须继承一下它然后才能使用。
①因此,我们首先在Models
中新建一个类,取名为:MyActionFilterAttribute
(以Attribute
结尾比较符合编码规范),并使其继承自ActionFilterAttribute
,然后重写基类所提供的虚方法:
public class MyActionFilterAttribute : ActionFilterAttribute
{
public string Name { get; set; }
/// <summary>
/// Action 执行之前先执行此方法
/// </summary>
/// <param name="filterContext">过滤器上下文</param>
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
HttpContext.Current.Response.Write("<br />OnActionExecuting :" + Name);
}
/// <summary>
/// Action执行之后
/// </summary>
/// <param name="filterContext">过滤器上下文</param>
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
HttpContext.Current.Response.Write("<br />OnActionExecuted :" + Name);
}
/// <summary>
/// ActionResult执行之前先执行此方法
/// </summary>
/// <param name="filterContext">过滤器上下文</param>
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
base.OnResultExecuting(filterContext);
HttpContext.Current.Response.Write("<br />OnResultExecuting :" + Name);
}
/// <summary>
/// ActionResult执行之后先执行此方法
/// </summary>
/// <param name="filterContext">过滤器上下文</param>
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
base.OnResultExecuted(filterContext);
HttpContext.Current.Response.Write("<br />OnResultExecuted :" + Name);
}
}
这里我们重写了四个虚方法,他们各自代表了在Action
执行之前和之后需要执行的业务逻辑,以及在Result
执行之前和之后需要执行的业务逻辑。这里的Result
主要是指我们在Action
中进行return 语句返回结果时(例如:return Content("Hello Filter!");
),之前和之后要执行的逻辑处理。
比如:我们想要在每个Action
执行之前进行用户是否登录的校验,可以在OnActionExecuting
中判断用户Session
是否存在,如果存在则继续执行Action
的具体业务代码,如果不存在则重定向页面到登陆页,后边的Action
业务代码不再执行。
②现在有了自定义的过滤器,我们怎么将其应用到Action
中呢?这里有三种方式:
一是给某个控制器的某个Action
指定此Filter
:
[MyActionFilter(Name = "Filter Action")]
public ActionResult Filter()
{
Response.Write("<p>Action正在努力执行中...</p>");
return Content("<p>OK:视图成功被渲染</p>");
}
二是给某个控制器的所有Action
指定此Filter
:
[MyActionFilter(Name="Home Filter")]
public class HomeController : Controller
{
}
但是,要注意的是:如果既给Controller
指定了Filter
,又给该Controller
中的某个Action
指定了Filter
,那么具体的这个Action
以离其定义最近的Filter
为准,也就是一个优先级的顺序问题:Action
的Filter
优先级高于Controller
的Filter
。
三是给此项目中的所有控制器即全局指定此Filter
:在App_Start
中更改FilterConfig
类,此种方式优先级最低。
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
// 注册自定义Action过滤器:优先级最低,但是可以作用到所有的控制器和Action
filters.Add(new MyActionFilterAttribute() { Name = "Global Controller" });
}
③现在我们来看看具体的效果:
可以看到,我们的/Home/Filter
这个Action
中只有两句代码,一句Response.Write
,另一句是return Content();
在Response.Write
之前执行了OnActionExecuting
的过滤器方法,之后则执行了OnActionExecuted
的过滤器方法;我们刚刚说了,在Action
中的return
语句代表了Result
,那么在Result
之前执行了OnResultExecuting
过滤器方法,之后则执行了OnResultExecuted
过滤器方法。这里仅仅是为了展示,在实际开发中是需要写一些具体的业务逻辑处理的,例如:判断用户的登录状态,记录用户的操作日志等等。
(2)Exception Filter
①同样,在Models
中新建一个类,取名为:MyExceptionFilterAttribute
,并使其继承自HandleErrorAttribute
。
public class MyExceptionFilterAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
base.OnException(filterContext);
HttpContext.Current.Response.Redirect("/Home/Index");
}
}
这里,重写基类的OnException
方法,这里仅仅为了演示效果,没有对异常进行处理。在实际开发中,需要获取异常对象,并将其记录至日志中。例如,下面一段代码:
public override void OnException(ExceptionContext filterContext)
{
base.OnException(filterContext);
//获取系统异常消息记录
string strException = filterContext.Exception.Message;
if (!string.IsNullOrEmpty(strException))
{
//使用Log4Net记录异常信息
Exception exception = filterContext.Exception;
if (exception != null)
{
LogHelper.WriteErrorLog(strException, exception);
}
else
{
LogHelper.WriteErrorLog(strException);
}
}
filterContext.HttpContext.Response.Redirect("~/GlobalErrorPage.html");
}
②有了异常过滤器,我们怎么来应用到项目中呢?答案也在App_Start
中,还是在FilterConfig
类中,新添一句代码进行注册:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
// 注册自定义Action过滤器:优先级最低,但是可以作用到所有的控制器和Action
filters.Add(new MyActionFilterAttribute() { Name = "Global Controller" });
// 注册自定义Exception过滤器
filters.Add(new MyExceptionFilterAttribute());
}
}
③为了测试,我们新增一个Action
,使其能够出现一个异常:DividedByZero
public ActionResult Exception()
{
int a = 10;
int b = 0;
int c = a / b;
return Content("Exception is happened.");
}
④当我们测试这个Action
时,会发现系统执行了自定义的异常过滤器,将我们的这个请求改为重定向到Index
这个Action
了。