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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM