Asp.net MVC验证哪些事(3)-- Remote验证及其改进(附源码)
表单中的输入项,有些是固定的,不变的验证规则,比如字符长度,必填等。但有些是动态的,比如注册用户名是否存在这样的检查,这个需要访问服务器后台才能解决。这篇文章将会介绍MVC中如何使用【RemoteAttribute】来解决这类验证需求,同时会分析【RemoteAttribute】的不足,以及改进的方法.
本文相关的源代码在这里 MVC-Remote-Validation.zip
一, RemoteAttribute验证使用
如果需要用户把整个表单填完后,提交到后台,然后才告诉用户说,“你注册的用户已经被占用了,请换一个用户名”,估计很多用户都可能要飚脏话了. MVC中的Remote验证是通过Ajax实现的,也就是说,当你填写用户名的时候,就会自动的发送你填写的内容到后台,后台返回检查结果。
1. 实现Remote验证非常简单,首先需要有个后台的方法来响应验证请求, 也就是需要创建一个Controller, 这里我们用ValidationController:
- public class ValidationController : Controller
- {
- public JsonResult IsEmployeeNameAvailable(string employeeName)
- {
- //这里假设已经存在的用户是”justrun”, 如果输入的名字不是justrun,就通过验证
- if (employeeName != "justrun")
- {
- return Json(true, JsonRequestBehavior.AllowGet);
- }
- return Json("The name 'justrun' is not available, please try another name.", JsonRequestBehavior.AllowGet);
- }
- }
2. 接着在我们的Employee Model上应用上RemoteAttribute
- public class Employee
- {
- public int EmpId { get; set; }
- [DisplayName("Employee Name")]
- [Remote("IsEmployeeNameAvailable", "Validation")] //使用RemoteAttribute,指定验证的Controller和Action
- public String EmployeeName { get; set; }
- }
3. 对应的View
- @using (Html.BeginForm()) {
- @Html.AntiForgeryToken()
- @Html.ValidationSummary()
- <fieldset>
- <legend>Registration Form</legend>
- <ol>
- <li>
- @Html.LabelFor(m => m.EmployeeName)
- @Html.EditorFor(m => m.EmployeeName)
- @Html.ValidationMessageFor(m => m.EmployeeName)
- </li>
- </ol>
- <input type="submit" value="Register" />
- </fieldset>
- }
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
- public class CustomRemoteAttribute : RemoteAttribute
- {
- public CustomRemoteAttribute(string action, string controller)
- : base(action, controller)
- {
- Action = action;
- Controller = controller;
- }
- public string Action { get; set; }
- public string Controller { get; set; }
- }
看了上面的代码,你也学会说,这不是什么都没干吗? 是的,这个CustomRemoteAttribute 的确是什么都没干,作用只是公开了RemoteAttribute的Controller和Action属性,因为只有这样我们才能知道Model添加的remote验证,是要访问那段代码。
2. 替换RemoteAttribute为CustomRemoteAttribute
这个非常简单,没有什么要解释的。
- public class Employee
- {
- public int EmpId { get; set; }
- [DisplayName("Employee Name")]
- [CustomRemote("IsEmployeeNameAvailable", "Validation")]
- public String EmployeeName { get; set; }
- }
3. 自定义的CustomModelBinder
下面的CustomModelBinder就是在Model绑定的时候,调用相应的Action方法做验证,失败了,就写ModelError. 注释中已经解释了整个代码的工作流程。
- public class CustomModelBinder : DefaultModelBinder
- {
- protected override void BindProperty(ControllerContext controllerContext,
- ModelBindingContext bindingContext,
- PropertyDescriptor propertyDescriptor)
- {
- if (propertyDescriptor.PropertyType == typeof(string))
- {
- //检查Model绑定的属性中,是否应用了CustomRemoteAttribute
- var remoteAttribute =
- propertyDescriptor.Attributes.OfType<CustomRemoteAttribute>()
- .FirstOrDefault();
- if (remoteAttribute != null)
- {
- //如果使用了CustomRemoteAttribute, 就开始找到CustomAttribute中指定的Controller
- var allControllers = GetControllerNames();
- var controllerType = allControllers.FirstOrDefault(x => x.Name ==
- remoteAttribute.Controller + "Controller");
- if (controllerType != null)
- {
- //查找Controller中的Action方法
- var methodInfo = controllerType.GetMethod(remoteAttribute.Action);
- if (methodInfo != null)
- {
- //调用方法,得到验证的返回结果
- string validationResponse = callRemoteValidationFunction(
- controllerContext,
- bindingContext,
- propertyDescriptor,
- controllerType,
- methodInfo,
- remoteAttribute.AdditionalFields);
- //如果验证失败,添加ModelError
- if (validationResponse != null)
- {
- bindingContext.ModelState.AddModelError(propertyDescriptor.Name,
- validationResponse);
- }
- }
- }
- }
- }
- base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
- }
- /// This function calls the indicated method on a new instance of the supplied
- /// controller type and return the error string. (NULL if not)
- private string callRemoteValidationFunction(
- ControllerContext controllerContext,
- ModelBindingContext bindingContext,
- MemberDescriptor propertyDescriptor,
- Type controllerType,
- MethodInfo methodInfo,
- string additionalFields)
- {
- var propertyValue = controllerContext.RequestContext.HttpContext.Request.Form[
- bindingContext.ModelName + propertyDescriptor.Name];
- var controller = (Controller)Activator.CreateInstance(controllerType);
- object result = null;
- var parameters = methodInfo.GetParameters();
- if (parameters.Length == )
- {
- result = methodInfo.Invoke(controller, null);
- }
- else
- {
- var parametersArray = new List<string> {propertyValue};
- if (parameters.Length == )
- {
- result = methodInfo.Invoke(controller, parametersArray.ToArray());
- }
- else
- {
- if (!string.IsNullOrEmpty(additionalFields))
- {
- foreach (var additionalFieldName in additionalFields.Split(','))
- {
- string additionalFieldValue =
- controllerContext.RequestContext.HttpContext.Request.Form[
- bindingContext.ModelName + additionalFieldName];
- parametersArray.Add(additionalFieldValue);
- }
- if (parametersArray.Count == parameters.Length)
- {
- result = methodInfo.Invoke(controller, parametersArray.ToArray());
- }
- }
- }
- }
- if (result != null)
- {
- return (((JsonResult)result).Data as string);
- }
- return null;
- }
- /// Returns a list of all Controller types
- private static IEnumerable<Type> GetControllerNames()
- {
- var controllerNames = new List<Type>();
- GetSubClasses<Controller>().ForEach(controllerNames.Add);
- return controllerNames;
- }
- private static List<Type> GetSubClasses<T>()
- {
- return Assembly.GetCallingAssembly().GetTypes().Where(
- type => type.IsSubclassOf(typeof(T))).ToList();
- }
- }
4. 在MVC项目中应Global.asax.cs用上CustomModelBinder
打开Global.asax.cs, 添加上这段代码
- protected void Application_Start()
- {
- //修改MVC默认的Model Binder为CustomBinder
- ModelBinders.Binders.DefaultBinder = new CustomModelBinder();
- ……
- }
5. 关闭客户端验证,看看效果
打开web.config文件,ClientValidationEnabled设置成false, 关闭客户端验证
- <appSettings>
- <add key="webpages:Version" value="2.0.0.0" />
- <add key="webpages:Enabled" value="false" />
- <add key="PreserveLoginUrl" value="true" />
- <add key="ClientValidationEnabled" value="false" />
- <add key="UnobtrusiveJavaScriptEnabled" value="true" />
- </appSettings>
最终的运行效果如下,能够明显的看到,页面刷新,表单提交到了后台处理。
Asp.net MVC验证哪些事(3)-- Remote验证及其改进(附源码)的更多相关文章
- Remote验证及其改进(附源码)
Remote验证及其改进(附源码) 表单中的输入项,有些是固定的,不变的验证规则,比如字符长度,必填等.但有些是动态的,比如注册用户名是否存在这样的检查,这个需要访问服务器后台才能解决.这篇文章将会介 ...
- asp.net mvc 之旅 —— 第六站 ActionFilter的应用及源码分析
这篇文章我们开始看一下ActionFilter,从名字上其实就大概知道ActionFilter就是Action上的Filter,对吧,那么Action上的Filter大概有几个呢??? 这个问题其实还 ...
- Asp.NetCoreWebApi图片上传接口(二)集成IdentityServer4授权访问(附源码)
写在前面 本文地址:http://www.cnblogs.com/yilezhu/p/9315644.html 作者:yilezhu 上一篇关于Asp.Net Core Web Api图片上传的文章使 ...
- ASP.NET没有魔法——ASP.NET MVC使用Oauth2.0实现身份验证
随着软件的不断发展,出现了更多的身份验证使用场景,除了典型的服务器与客户端之间的身份验证外还有,如服务与服务之间的(如微服务架构).服务器与多种客户端的(如PC.移动.Web等),甚至还有需要以服务的 ...
- ASP.NET MVC使用Oauth2.0实现身份验证
随着软件的不断发展,出现了更多的身份验证使用场景,除了典型的服务器与客户端之间的身份验证外还有,如服务与服务之间的(如微服务架构).服务器与多种客户端的(如PC.移动.Web等),甚至还有需要以服务的 ...
- ASP.NET MVC 5 学习教程:添加验证
原文 ASP.NET MVC 5 学习教程:添加验证 起飞网 ASP.NET MVC 5 学习教程目录: 添加控制器 添加视图 修改视图和布局页 控制器传递数据给视图 添加模型 创建连接字符串 通过控 ...
- 【译】ASP.NET MVC 5 教程 - 10:添加验证
原文:[译]ASP.NET MVC 5 教程 - 10:添加验证 在本节中,我们将为Movie模型添加验证逻辑,并确认验证规则在用户试图使用程序创建和编辑电影时有效. DRY 原则 ASP.NET M ...
- asp.net mvc3 数据验证(四)—Remote验证的一个注意事项
原文:asp.net mvc3 数据验证(四)-Remote验证的一个注意事项 前几篇把asp.net mvc3 中基于Model的主要数据验证的方法都已经讲完了,本节纯粹只是讲一个我 ...
- ASP.NET MVC中对Model进行分步验证的解决方法
原文:ASP.NET MVC中对Model进行分步验证的解决方法 在我之前的文章:ASP.NET MVC2.0结合WF4.0实现用户多步注册流程中将一个用户的注册分成了四步,而这四个步骤都是在完善一个 ...
随机推荐
- VirtualBox网络设置的问题
在VirtualBox里新建了一个虚拟Linux系统,默认的连接方式是网络地址转换(NAT).发现主机不能访问虚拟机的samba服务器,ping了一下,虚拟机可以ping主机,但是主机不能ping虚拟 ...
- GNOME on Arch Linux
Arch Linux上Gnome桌面截图欣赏: 相比而言,Debian的壁纸一直好像格调不够啊:
- Mybatis批量添加对象List
1.对应的xml文件: <!--批量添加--><insert id="insertStandardItemInfo" parameterType="ha ...
- http gzip 解压缩
var sContentEncoding = httpRespone.Headers["Content-Encoding"]; if(sContentEncoding == &qu ...
- Hadoop学习笔记1-如何简单布署hadoop
企业机型配置: 选型标准:普通的,廉价的,标准的(容易替换的),工业化大规模生产的 CPU:支持多核CPU,如2个4核CPU 内存:16G以上,内存越大,常用数据都缓存在内存,提高速度 硬盘:不需RA ...
- vim自动补全插件YouCompleteMe
前言 Valloric/YouCompleteMe可以说是vim安装最复杂的插件之一,但是一旦装好,却又是非常好用的.YouCompleteMe简称ycm 在安装折腾的过程中,我再一次的体会到,除了官 ...
- angular学习的一些小笔记(中)之ng-disabled轻松实现按钮是否可点击状态
哇,这个可以轻松实现输入值就按钮可点击,输入框没有值则不可点击的状态呀 看代码 <!doctype html> <html ng-app=""> <h ...
- Typecast 免费了!献给设计师们的礼物
TypeCast 让你可以从 Fonts.com.TypeKit.FontDeck 和 Google 这些字体供应和商选择字体,而且能非常方便的比较这些字体使用效果.如果你想获得用户对这些字体效果的反 ...
- Click Magick – 下一代点击跟踪和链接管理
Click Magick 是新一代的广告跟踪和链接管理系统,让每一个点击都能给你带去更多的利润.它是专门设计来跟踪所有类型的点击计费广告,包括从谷歌,必应和 Facebook 的 PPC 广告,就好像 ...
- ae IMap接口成员
使用IMap接口显示各种数据源的数据.IMap接口的成员ActiveGraphicsLayer:活动图形图层,如果没有将创建一个基本memory graphics layer.AddLayer:向地图 ...