表单中的输入项,有些是固定的,不变的验证规则,比如字符长度,必填等。但有些是动态的,比如注册用户名是否存在这样的检查,这个需要访问服务器后台才能解决。这篇文章将会介绍MVC中如何使用【RemoteAttribute】来解决这类验证需求,同时会分析【RemoteAttribute】的不足,以及改进的方法.

本文相关的源代码在这里 MVC-Remote-Validation.zip

一, RemoteAttribute验证使用

如果需要用户把整个表单填完后,提交到后台,然后才告诉用户说,“你注册的用户已经被占用了,请换一个用户名”,估计很多用户都可能要飚脏话了. MVC中的Remote验证是通过Ajax实现的,也就是说,当你填写用户名的时候,就会自动的发送你填写的内容到后台,后台返回检查结果。

1. 实现Remote验证非常简单,首先需要有个后台的方法来响应验证请求, 也就是需要创建一个Controller, 这里我们用ValidationController:

  1. public class ValidationController : Controller
  2. {
  3. public JsonResult IsEmployeeNameAvailable(string employeeName)
  4. {
  5. //这里假设已经存在的用户是”justrun”, 如果输入的名字不是justrun,就通过验证
  6. if (employeeName != "justrun")
  7. {
  8. return Json(true, JsonRequestBehavior.AllowGet);
  9. }
  10. return Json("The name 'justrun' is not available, please try another name.", JsonRequestBehavior.AllowGet);
  11. }
  12. }

2. 接着在我们的Employee Model上应用上RemoteAttribute

  1. public class Employee
  2. {
  3. public int EmpId { get; set; }
  4. [DisplayName("Employee Name")]
  5. [Remote("IsEmployeeNameAvailable", "Validation")] //使用RemoteAttribute,指定验证的Controller和Action
  6. public String EmployeeName { get; set; }
  7.  
  8. }

3. 对应的View

  1. @using (Html.BeginForm()) {
  2. @Html.AntiForgeryToken()
  3. @Html.ValidationSummary()
  4.  
  5. <fieldset>
  6. <legend>Registration Form</legend>
  7. <ol>
  8. <li>
  9. @Html.LabelFor(m => m.EmployeeName)
  10. @Html.EditorFor(m => m.EmployeeName)
  11. @Html.ValidationMessageFor(m => m.EmployeeName)
  12. </li>
  13. </ol>
  14. <input type="submit" value="Register" />
  15. </fieldset>
  16. }

4. 最后,看看验证的效果

通过firebug能够看到,在填写表单的过程中,会不断的把表单的EmployeeName发送到我们指定的Controller, Action上做验证。

二, RemoteAttribute的局限性

使用 【RemoteAttribute】 来做远端验证的确是很棒– 它会自动的发起AJAX请求去访问后台代码来实现验证. 但是注意, 一旦表单提交了,就不会在存在这个验证了。比如当我用上【Required】这个验证标签的时候,无论在客户端还是服务器端,都存在着对于必填项的验证。服务器端可以通过ModelState.IsValid非常容易地判断,当前提交到后台的表单数据是否合法。但是【RemoteAttribute】只有客户端验证,而没有服务器端验证。 也就是说,如果用户的浏览器中,关闭js,我们的Remote检查就形同虚设。

是不是非常意外, 当接触Remote验证的时候,原以为默认的就会认为它会和其它验证标签一样。所以使用RemoteAttribute验证,是存在一定的安全隐患的。

三, RemoteAttribute的改进

先介绍一下对于RemoteAttribute的改进思路:

如果我们也想让RemoteAttribute和其它的验证特性一样工作,也就是说,如果不符合Remote的验证要求,我们希望ModelState.IsValid也是false, 同时会添加上相应的ModelError. 这里选择在MVC的Model binding的时候,做这个事情,因为在Model Binding的时候,正是将表单数据绑定到对应的model对象的时候。只要在绑定的过程中,如果发现Model中的属性有使用RemoteAttribute, 我们调用相应的验证代码。验证失败了,就添加上对于的ModelError.

由于涉及到了Model Binding和Atrribute的使用,如果有兴趣的,可以先看看这2篇文章:

Asp.net MVC使用Model Binding解除Session, Cookie等依赖

.Net Attribute详解(上)-Attribute本质以及一个简单示例

1. 继承RemoteAttribute, 创建CustomRemoteAttribute

  1. public class CustomRemoteAttribute : RemoteAttribute
  2. {
  3. public CustomRemoteAttribute(string action, string controller)
  4. : base(action, controller)
  5. {
  6. Action = action;
  7. Controller = controller;
  8. }
  9. public string Action { get; set; }
  10. public string Controller { get; set; }
  11. }

看了上面的代码,你也学会说,这不是什么都没干吗? 是的,这个CustomRemoteAttribute 的确是什么都没干,作用只是公开了RemoteAttribute的Controller和Action属性,因为只有这样我们才能知道Model添加的remote验证,是要访问那段代码。

2. 替换RemoteAttribute为CustomRemoteAttribute

这个非常简单,没有什么要解释的。

  1. public class Employee
  2. {
  3. public int EmpId { get; set; }
  4. [DisplayName("Employee Name")]
  5. [CustomRemote("IsEmployeeNameAvailable", "Validation")]
  6. public String EmployeeName { get; set; }
  7.  
  8. }

3. 自定义的CustomModelBinder

下面的CustomModelBinder就是在Model绑定的时候,调用相应的Action方法做验证,失败了,就写ModelError. 注释中已经解释了整个代码的工作流程。

  1. public class CustomModelBinder : DefaultModelBinder
  2. {
  3. protected override void BindProperty(ControllerContext controllerContext,
  4. ModelBindingContext bindingContext,
  5. PropertyDescriptor propertyDescriptor)
  6. {
  7. if (propertyDescriptor.PropertyType == typeof(string))
  8. {
  9.  
  10. //检查Model绑定的属性中,是否应用了CustomRemoteAttribute
  11. var remoteAttribute =
  12. propertyDescriptor.Attributes.OfType<CustomRemoteAttribute>()
  13. .FirstOrDefault();
  14.  
  15. if (remoteAttribute != null)
  16. {
  17.  
  18. //如果使用了CustomRemoteAttribute, 就开始找到CustomAttribute中指定的Controller
  19. var allControllers = GetControllerNames();
  20.  
  21. var controllerType = allControllers.FirstOrDefault(x => x.Name ==
  22. remoteAttribute.Controller + "Controller");
  23.  
  24. if (controllerType != null)
  25. {
  26.  
  27. //查找Controller中的Action方法
  28. var methodInfo = controllerType.GetMethod(remoteAttribute.Action);
  29.  
  30. if (methodInfo != null)
  31. {
  32.  
  33. //调用方法,得到验证的返回结果
  34. string validationResponse = callRemoteValidationFunction(
  35. controllerContext,
  36. bindingContext,
  37. propertyDescriptor,
  38. controllerType,
  39. methodInfo,
  40. remoteAttribute.AdditionalFields);
  41.  
  42. //如果验证失败,添加ModelError
  43.  
  44. if (validationResponse != null)
  45. {
  46. bindingContext.ModelState.AddModelError(propertyDescriptor.Name,
  47. validationResponse);
  48. }
  49. }
  50. }
  51. }
  52. }
  53.  
  54. base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
  55. }
  56.  
  57. /// This function calls the indicated method on a new instance of the supplied
  58. /// controller type and return the error string. (NULL if not)
  59. private string callRemoteValidationFunction(
  60. ControllerContext controllerContext,
  61. ModelBindingContext bindingContext,
  62. MemberDescriptor propertyDescriptor,
  63. Type controllerType,
  64. MethodInfo methodInfo,
  65. string additionalFields)
  66. {
  67.  
  68. var propertyValue = controllerContext.RequestContext.HttpContext.Request.Form[
  69. bindingContext.ModelName + propertyDescriptor.Name];
  70.  
  71. var controller = (Controller)Activator.CreateInstance(controllerType);
  72. object result = null;
  73. var parameters = methodInfo.GetParameters();
  74. if (parameters.Length == )
  75. {
  76. result = methodInfo.Invoke(controller, null);
  77. }
  78. else
  79. {
  80. var parametersArray = new List<string> {propertyValue};
  81.  
  82. if (parameters.Length == )
  83. {
  84. result = methodInfo.Invoke(controller, parametersArray.ToArray());
  85. }
  86. else
  87. {
  88. if (!string.IsNullOrEmpty(additionalFields))
  89. {
  90. foreach (var additionalFieldName in additionalFields.Split(','))
  91. {
  92. string additionalFieldValue =
  93. controllerContext.RequestContext.HttpContext.Request.Form[
  94. bindingContext.ModelName + additionalFieldName];
  95. parametersArray.Add(additionalFieldValue);
  96. }
  97.  
  98. if (parametersArray.Count == parameters.Length)
  99. {
  100. result = methodInfo.Invoke(controller, parametersArray.ToArray());
  101. }
  102. }
  103. }
  104. }
  105.  
  106. if (result != null)
  107. {
  108. return (((JsonResult)result).Data as string);
  109. }
  110. return null;
  111. }
  112.  
  113. /// Returns a list of all Controller types
  114. private static IEnumerable<Type> GetControllerNames()
  115. {
  116. var controllerNames = new List<Type>();
  117. GetSubClasses<Controller>().ForEach(controllerNames.Add);
  118. return controllerNames;
  119. }
  120.  
  121. private static List<Type> GetSubClasses<T>()
  122. {
  123. return Assembly.GetCallingAssembly().GetTypes().Where(
  124. type => type.IsSubclassOf(typeof(T))).ToList();
  125. }
  126.  
  127. }

4. 在MVC项目中应Global.asax.cs用上CustomModelBinder

打开Global.asax.cs, 添加上这段代码

  1. protected void Application_Start()
  2. {
  3. //修改MVC默认的Model Binder为CustomBinder
  4. ModelBinders.Binders.DefaultBinder = new CustomModelBinder();
  5. ……
  6.  
  7. }

5. 关闭客户端验证,看看效果

打开web.config文件,ClientValidationEnabled设置成false, 关闭客户端验证

  1. <appSettings>
  2. <add key="webpages:Version" value="2.0.0.0" />
  3. <add key="webpages:Enabled" value="false" />
  4. <add key="PreserveLoginUrl" value="true" />
  5. <add key="ClientValidationEnabled" value="false" />
  6. <add key="UnobtrusiveJavaScriptEnabled" value="true" />
  7. </appSettings>

最终的运行效果如下,能够明显的看到,页面刷新,表单提交到了后台处理。

Asp.net MVC验证哪些事(3)-- Remote验证及其改进(附源码)的更多相关文章

  1. Remote验证及其改进(附源码)

    Remote验证及其改进(附源码) 表单中的输入项,有些是固定的,不变的验证规则,比如字符长度,必填等.但有些是动态的,比如注册用户名是否存在这样的检查,这个需要访问服务器后台才能解决.这篇文章将会介 ...

  2. asp.net mvc 之旅 —— 第六站 ActionFilter的应用及源码分析

    这篇文章我们开始看一下ActionFilter,从名字上其实就大概知道ActionFilter就是Action上的Filter,对吧,那么Action上的Filter大概有几个呢??? 这个问题其实还 ...

  3. Asp.NetCoreWebApi图片上传接口(二)集成IdentityServer4授权访问(附源码)

    写在前面 本文地址:http://www.cnblogs.com/yilezhu/p/9315644.html 作者:yilezhu 上一篇关于Asp.Net Core Web Api图片上传的文章使 ...

  4. ASP.NET没有魔法——ASP.NET MVC使用Oauth2.0实现身份验证

    随着软件的不断发展,出现了更多的身份验证使用场景,除了典型的服务器与客户端之间的身份验证外还有,如服务与服务之间的(如微服务架构).服务器与多种客户端的(如PC.移动.Web等),甚至还有需要以服务的 ...

  5. ASP.NET MVC使用Oauth2.0实现身份验证

    随着软件的不断发展,出现了更多的身份验证使用场景,除了典型的服务器与客户端之间的身份验证外还有,如服务与服务之间的(如微服务架构).服务器与多种客户端的(如PC.移动.Web等),甚至还有需要以服务的 ...

  6. ASP.NET MVC 5 学习教程:添加验证

    原文 ASP.NET MVC 5 学习教程:添加验证 起飞网 ASP.NET MVC 5 学习教程目录: 添加控制器 添加视图 修改视图和布局页 控制器传递数据给视图 添加模型 创建连接字符串 通过控 ...

  7. 【译】ASP.NET MVC 5 教程 - 10:添加验证

    原文:[译]ASP.NET MVC 5 教程 - 10:添加验证 在本节中,我们将为Movie模型添加验证逻辑,并确认验证规则在用户试图使用程序创建和编辑电影时有效. DRY 原则 ASP.NET M ...

  8. asp.net mvc3 数据验证(四)—Remote验证的一个注意事项

    原文:asp.net mvc3 数据验证(四)-Remote验证的一个注意事项         前几篇把asp.net mvc3 中基于Model的主要数据验证的方法都已经讲完了,本节纯粹只是讲一个我 ...

  9. ASP.NET MVC中对Model进行分步验证的解决方法

    原文:ASP.NET MVC中对Model进行分步验证的解决方法 在我之前的文章:ASP.NET MVC2.0结合WF4.0实现用户多步注册流程中将一个用户的注册分成了四步,而这四个步骤都是在完善一个 ...

随机推荐

  1. VirtualBox网络设置的问题

    在VirtualBox里新建了一个虚拟Linux系统,默认的连接方式是网络地址转换(NAT).发现主机不能访问虚拟机的samba服务器,ping了一下,虚拟机可以ping主机,但是主机不能ping虚拟 ...

  2. GNOME on Arch Linux

    Arch Linux上Gnome桌面截图欣赏: 相比而言,Debian的壁纸一直好像格调不够啊:

  3. Mybatis批量添加对象List

    1.对应的xml文件: <!--批量添加--><insert id="insertStandardItemInfo" parameterType="ha ...

  4. http gzip 解压缩

    var sContentEncoding = httpRespone.Headers["Content-Encoding"]; if(sContentEncoding == &qu ...

  5. Hadoop学习笔记1-如何简单布署hadoop

    企业机型配置: 选型标准:普通的,廉价的,标准的(容易替换的),工业化大规模生产的 CPU:支持多核CPU,如2个4核CPU 内存:16G以上,内存越大,常用数据都缓存在内存,提高速度 硬盘:不需RA ...

  6. vim自动补全插件YouCompleteMe

    前言 Valloric/YouCompleteMe可以说是vim安装最复杂的插件之一,但是一旦装好,却又是非常好用的.YouCompleteMe简称ycm 在安装折腾的过程中,我再一次的体会到,除了官 ...

  7. angular学习的一些小笔记(中)之ng-disabled轻松实现按钮是否可点击状态

    哇,这个可以轻松实现输入值就按钮可点击,输入框没有值则不可点击的状态呀 看代码 <!doctype html> <html ng-app=""> <h ...

  8. Typecast 免费了!献给设计师们的礼物

    TypeCast 让你可以从 Fonts.com.TypeKit.FontDeck 和 Google 这些字体供应和商选择字体,而且能非常方便的比较这些字体使用效果.如果你想获得用户对这些字体效果的反 ...

  9. Click Magick – 下一代点击跟踪和链接管理

    Click Magick 是新一代的广告跟踪和链接管理系统,让每一个点击都能给你带去更多的利润.它是专门设计来跟踪所有类型的点击计费广告,包括从谷歌,必应和 Facebook 的 PPC 广告,就好像 ...

  10. ae IMap接口成员

    使用IMap接口显示各种数据源的数据.IMap接口的成员ActiveGraphicsLayer:活动图形图层,如果没有将创建一个基本memory graphics layer.AddLayer:向地图 ...