欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Asp.net MVC验证哪些事(3)-- Remote验证及其改进(附源码)

程序员文章站 2022-10-31 23:27:03
一, RemoteAttribute验证使用   如果需要用户把整个表单填完后,提交到后台,然后才告诉用户说,“你注册的用户已经被占用了,请换一个用户名&...
一, 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>