• 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. 最后,看看验证的效果

    MVC-Validation-remote

    通过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 == 0)
               {
                   result = methodInfo.Invoke(controller, null);
               }
               else
               {
                   var parametersArray = new List<string> {propertyValue};
    
                   if (parameters.Length == 1)
                   {
                       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>
    复制代码

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

    MVC-Remote-Validation3

     
  • 相关阅读:
    侦测卡 变色龙侦测卡 chameleon-Mini(迷你变色龙) (三)
    侦测卡 变色龙侦测卡 chameleon-Mini(迷你变色龙) (二)
    侦测卡 变色龙侦测卡 chameleon-Mini(迷你变色龙) (一)
    Android使用NFC模拟M卡实现 (二)
    scala中eq,==和equals的区别
    scala @BeanProperty 自动添加getter 和setter
    Scala 可变参数
    Flink 侧输出流 DEMO
    Flink 写数据到MySql (JDBC Sink)
    Fink 写数据到kafka (kafka sink)
  • 原文地址:https://www.cnblogs.com/webenh/p/7691656.html
Copyright © 2020-2023  润新知