原文地址:http://blog.jobbole.com/85005/

一、校验 — 表单不是你想提想提就能提

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的实体,其定义如下:

1
2
3
4
5
6
public class UserInfo
    {
        public int Id { get; set; }
        public string UserName { get; set; }
        public int Age { get; set; }
    }

UserInfo的属性很简单,只有三个:Id,UserName和Age三个字段;现在我们可以为其增加验证特性,看看其为我们提供的强大的校验功能。

(1)非空验证

添加特性:

1
2
3
4
5
6
7
[Display(Name="用户名")]
[Required(ErrorMessage = "*姓名必填")]
public string UserName { get; set; }
 
[Display(Name = "年龄")]
[Required(ErrorMessage = "*年龄必填")]
public int Age { get; set; }

验证效果:

(2)字符串长度验证

添加特性:

1
2
3
4
[Display(Name="用户名")]
[Required(ErrorMessage = "*姓名必填")]
[StringLength(5, ErrorMessage = "*长度必须小于5")]
public string UserName { get; set; }

验证效果:

(3)范围验证

添加特性:

1
2
3
4
[Display(Name = "年龄")]
[Required(ErrorMessage = "*年龄必填")]
[Range(18, 120)]
public int Age { get; set; }

验证效果:

(4)正则表达式验证

添加特性:验证用户输入的是否是数字,正则表达式匹配

1
2
3
4
5
[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文件:

1
2
<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则需要修改一下):

1
2
3
<!-- 是否启用全局客户端校验 -->
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />

PS:Unobtrusive Javascript有三层含义:

一是在HTML代码中不会随意的插入Javsscript代码,只在标签中加一些额外的属性值,然后被引用的脚本文件识别和处理;

二是通过脚本文件所增加的功能是一种渐进式的增强,当客户端不支持或禁用了Javsscript时网页所提供的功能仍然能够实现,只是用户体验会降低;

三是能够兼容不同的浏览器。

(3)在Action中如果要对客户端是否通过了校验进行验证,可以通过以下代码实现:

1
2
3
4
5
6
7
8
9
10
[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获取一个服务器端的时间:

1
2
<h1>JQuery Ajax方式</h1>
<input id="btnJQuery" type="button" value="获取服务器时间" />

在Home控制器中增加一个用于返回时间的Action:

1
2
3
public ActionResult GetServerDate()
{return Content(DateTime.Now.ToString());
}

在View中增加一段JQuery代码,为btnJQuery按钮绑定一个Click事件:

1
2
3
4
5
6
7
8
9
$(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

1
2
<script src="~/Scripts/jquery-1.7.1.min.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>

确保在Web.config中启用了Unobtrusive JavaScript

1
<add key="UnobtrusiveJavaScriptEnabled" value="true" />

(2)其次,使用Ajax.BeginForm方法构造一个form表单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<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()的方法;

1
2
3
function afterSuccess(data) {
    //alert("您已成功获取数据:" + data);
}

LoadingElementId=”loading”是一个很有意思的属性,代表在ajax请求期间为了提供良好的用户体验,可以给出一个正在加载中的提示,而这个LoadingElementId则代表一个提示的div区域的Id。这里主要是指id为loading的这个div,其中有一张gif图片及一句话:正在获取中,请稍等…的提示。

1
2
3
<div id="loading" style="display:none">
      <img style="vertical-align:middle" src="~/Content/ico_loading2.gif" />正在获取中,请稍候...
</div>

为了显示加载提示的效果,我们人为地修改一下Action方法,使用Thread.Sleep(3000)来延迟一下请求返回时间

1
2
3
4
5
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,然后重写基类所提供的虚方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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("OnActionExecuting :" + Name);
    }
 
    /// <summary>
    /// Action执行之后
    /// </summary>
    /// <param name="filterContext">过滤器上下文</param>
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);
        HttpContext.Current.Response.Write("OnActionExecuted :" + Name);
    }
 
    /// <summary>
    /// ActionResult执行之前先执行此方法
    /// </summary>
    /// <param name="filterContext">过滤器上下文</param>
    public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        base.OnResultExecuting(filterContext);
        HttpContext.Current.Response.Write("OnResultExecuting :" + Name);
 
    }
 
    /// <summary>
    /// ActionResult执行之后先执行此方法
    /// </summary>
    /// <param name="filterContext">过滤器上下文</param>
    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        base.OnResultExecuted(filterContext);
        HttpContext.Current.Response.Write("OnResultExecuted :" + Name);
    }
}

这里我们重写了四个虚方法,他们各自代表了在Action执行之前和之后需要执行的业务逻辑,以及在Result执行之前和之后需要执行的业务逻辑。这里的Result主要是指我们在Action中进行return 语句返回结果时(例如:return Content(“Hello Filter!”);),之前和之后要执行的逻辑处理。

比如:我们想要在每个Action执行之前进行用户是否登录的校验,可以在OnActionExecuting中判断用户Session是否存在,如果存在则继续执行Action的具体业务代码,如果不存在则重定向页面到登陆页,后边的Action业务代码不再执行。

②现在有了自定义的过滤器,我们怎么将其应用到Action中呢?这里有三种方式:

一是给某个控制器的某个Action指定此Filter:

1
2
3
4
5
6
[MyActionFilter(Name = "Filter Action")]
public ActionResult Filter()
{
    Response.Write("<p>Action正在努力执行中...</p>");
    return Content("<p>OK:视图成功被渲染</p>");
}

二是给某个控制器的所有Action指定此Filter:

1
2
3
4
[MyActionFilter(Name="Home Filter")]
public class HomeController : Controller
{
}

但是,要注意的是:如果既给Controller指定了Filter,又给该Controller中的某个Action指定了Filter,那么具体的这个Action以离其定义最近的Filter为准,也就是一个优先级的顺序问题:Action的Filter优先级高于Controller的Filter。

三是给此项目中的所有控制器即全局指定此Filter:在App_Start中更改FilterConfig类,此种方式优先级最低。

1
2
3
4
5
6
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。

1
2
3
4
5
6
7
8
9
public class MyExceptionFilterAttribute : HandleErrorAttribute
{
    public override void OnException(ExceptionContext filterContext)
    {
        base.OnException(filterContext);
 
        HttpContext.Current.Response.Redirect("/Home/Index");
    }
}

这里,重写基类的OnException方法,这里仅仅为了演示效果,没有对异常进行处理。在实际开发中,需要获取异常对象,并将其记录至日志中。例如,下面一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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类中,新添一句代码进行注册:

1
2
3
4
5
6
7
8
9
10
11
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

1
2
3
4
5
6
7
public ActionResult Exception()
{
    int a = 10;
    int b = 0;
    int c = a / b;
    return Content("Exception is happened.");
}

④当我们测试这个Action时,会发现系统执行了自定义的异常过滤器,将我们的这个请求改为重定向到Index这个Action了。

参考资料

(1)蒋金楠,《ASP.NET MVC下的四种验证编程方式》,http://www.cnblogs.com/artech/p/asp-net-mvc-validation-programming.html

(2)蒋金楠,《ASP.NET MVC下的四种验证编程方式[续篇]》,http://www.cnblogs.com/artech/p/asp-net-mvc-4-validation.html

(3)马伦,《ASP.NET MVC 2014特供教程》,http://bbs.itcast.cn/thread-26722-1-1.html

(4)w809026418,《MVC中使用 DataAnnotations 进行模型验证》,http://www.cnblogs.com/haogj/archive/2011/11/16/2251920.html

(5)刘俊峰,《ASP.NET MVC中Unobtrusive Ajax的妙用》,http://www.cnblogs.com/rufi/archive/2012/03/31/unobtrusive-ajax.html

转:ASP.Net MVC:校验、AJAX与过滤器的更多相关文章

  1. Asp.Net MVC 使用 Ajax

    Asp.Net MVC 使用 Ajax Ajax 简单来说Ajax是一个无需重新加载整个网页的情况下,可以更新局部页面或数据的技术(异步的发送接收数据,不会干扰当前页面). Ajax工作原理 Ajax ...

  2. ASP.NET MVC 实现AJAX跨域请求方法《1》

    ASP.NET MVC 实现AJAX跨域请求的两种方法 通常发送AJAX请求都是在本域内完成的,也就是向本域内的某个URL发送请求,完成部分页面的刷新.但有的时候需要向其它域发送AJAX请求,完成数据 ...

  3. asp.net mvc 使用ajax请求 控制器 (PartialViewResult)分部的action,得到一个分部视图(PartialView)的HTML,进行渲染

    在asp.net mvc 使用ajax请求获取数据的时候,我们一般是返回json或者xml,然后解析这些数据进行渲染,这样会比较麻烦,可以请求一个 分部action,返回一个分部视图 直接可以渲染,不 ...

  4. 在Asp.Net MVC中用Ajax回调后台方法

    在Asp.Net MVC中用Ajax回调后台方法基本格式: var operData = ...; //传递的参数(action中定义的) var type = ...; //传递的参数(action ...

  5. [代码示例]用Fine Uploader+ASP.NET MVC实现ajax文件上传

    原文 [代码示例]用Fine Uploader+ASP.NET MVC实现ajax文件上传 Fine Uploader(http://fineuploader.com/)是一个实现 ajax 上传文件 ...

  6. Asp.Net MVC Unobtrusive Ajax

    1.   Unobtrusive JavaScript介绍 说到Unobtrusive Ajax,就要谈谈UnobtrusiveJavaScript了,所谓Unobtrusive JavaScript ...

  7. Asp.Net MVC学习总结之过滤器详解(转载)

    来源:http://www.php.cn/csharp-article-359736.html   一.过滤器简介 1.1.理解什么是过滤器 1.过滤器(Filters)就是向请求处理管道中注入额外的 ...

  8. ASP.NET MVC中有四种过滤器类型

    在ASP.NET MVC中有四种过滤器类型

  9. ASP.NET MVC之Ajax如影随行

    一.Ajax的前世今生 我一直觉得google是一家牛逼的公司,为什么这样说呢?<舌尖上的中国>大家都看了,那些美食估计你是百看不厌,但是里边我觉得其实也有这样的一个哲学:关于食材,对于种 ...

  10. ASP.NET MVC 实现 AJAX 跨域请求

    ASP.NET MVC 实现AJAX跨域请求的两种方法 和大家分享下Ajax 跨域的经验,之前也找了好多资料,但是都不行,后来看到个可行的修改了并测试下 果然OK了   希望对大家有所帮助! 通常发送 ...

随机推荐

  1. 扩展Visual Studio IDE

    安装visual studio 2012 SDK 下载visual studio SDK. 安装可能遇到的问题 安装时报错:Visual Studio 2012 Install Fails: Prog ...

  2. IE浏览器开启对JavaScript脚本的支持

    在IE浏览器的"工具"菜单中选择"internet选项",在弹出命令对话框中选择"安全"选项卡.在该选项卡下的"该区域的安全级别& ...

  3. sqlite3经常使用命令&amp;语法

    http://blog.csdn.net/linchunhua/article/details/7184439 sqlite数据库仅仅用一个文件就ok,小巧方便,所以是一个很不错的嵌入式数据库,SQL ...

  4. codeforces Arrival of the General 题解

    A Ministry for Defense sent a general to inspect the Super Secret Military Squad under the command o ...

  5. Calendar 日历控件使用

    <link rel="stylesheet" href="__STATIC__/js/calendar/calendar-blue.css"/> & ...

  6. HttpClient get返回String类型 JAVA

    public static String httpGet(String url) { // get请求返回结果 String strResult = ""; try { Defau ...

  7. java下io文件切割合并功能加配置文件

    package cn.stat.p1.file; import java.io.File; import java.io.FileInputStream; import java.io.FileNot ...

  8. poj2486

    题目大意:给出一个树,每个节点有一个值,问从1开始走走k步最多能遍历到最大的值的和:dp[i][j][k] 当i为零是表示如果从j点开始遍历以j为跟的子树,走k步后回到原点的最大值.dp[i][j][ ...

  9. SDP (Session Description Protocol)

    SDP的描述网络上一大堆中文的,可以看看RFC2327 SDP 信息是文本信息,采用 UTF-8 编 码中的 ISO 10646 字符集.SDP 会话描述如下:(标注 * 符号的表示可选字段):  v ...

  10. javascript中的substr和substring

    1.substr 方法 返回一个从指定位置开始的指定长度的子字符串. stringvar.substr(start [, length ]) 参数: stringvar 必选项.  要提取子字符串的字 ...