4

我有一個簡單的js/jquery函數我想運行時提交表單提交額外的驗證。我的表單允許多個文件輸入,並且我想確保所有文件的總數都在我設置的文件限制下。JQuery驗證:添加自定義方法來驗證提交

var totalFileSize = 0; 
$("input:file").each(function() { 
    var file = $(this)[0].files[0]; 
    if (file) totalFileSize += file.size; 
}); 

return totalFileSize < maxFileSize; // I've set maxFileSize elsewhere. 

問題是,我想運行這個作爲jQuery驗證的一部分。我的表單在其他地方使用標準的MVC3驗證和我寫的一個單獨的定製不顯眼的驗證。如果這個文件大小驗證失敗,我希望它像其他jQuery驗證器那樣工作:顯然,我想停止提交,並在其他摘要框中顯示錯誤消息。

有沒有什麼辦法可以像這樣簡單的方法作爲驗證提交的一部分?我想過$ .validator.addMethod,但是如果我將它添加到每個輸入:文件元素,它將在提交時多次運行相同的驗證程序,從而多次顯示錯誤消息。如果有一種方法可以添加驗證器,但不將它綁定到任何元素,那將是非常好的。

回答

10

您可以編寫自定義驗證屬性並註冊自定義客戶端適配器。讓我詳細說明一下。

假設您有一個視圖模型來表示要上傳的文件列表,並且您希望將所有上傳文件的總大小限制爲2 MB。您的視圖模型肯定會看的線沿線的東西:

public class MyViewModel 
{ 
    [MaxFileSize(2 * 1024 * 1024, ErrorMessage = "The total file size should not exceed {0} bytes")] 
    public IEnumerable<HttpPostedFileBase> Files { get; set; } 
} 

現在讓我們定義這顯然會執行服務器端驗證,但除此之外,它還可將實施IClientValidatable接口,允許註冊一個該自定義[MaxFileSize]驗證屬性自定義的不顯眼的客戶端驗證規則,將允許在客戶端上轉置此驗證邏輯(顯然,僅適用於支持HTML5 File API的瀏覽器,允許您在客戶端上確定所選文件的大小=> IE完全不適合像這樣的事情和使用這個瀏覽器的用戶將不得不滿足他們與服務器端只有有效或者做一些更好的事情 - 使用Internet Explorer作爲這個世界唯一有用的任務,這個軟件可以做到這一點:通過互聯網,一旦你得到一個乾淨的Windows下載一個真正的網絡瀏覽器):

public class MaxFileSizeAttribute : ValidationAttribute, IClientValidatable 
{ 
    public MaxFileSizeAttribute(int maxTotalSize) 
    { 
     MaxTotalSize = maxTotalSize; 
    } 

    public int MaxTotalSize { get; private set; } 

    public override bool IsValid(object value) 
    { 
     var files = value as IEnumerable<HttpPostedFileBase>; 
     if (files != null) 
     { 
      var totalSize = files.Where(x => x != null).Sum(x => x.ContentLength); 
      return totalSize < MaxTotalSize; 
     } 

     return true; 
    } 

    public override string FormatErrorMessage(string name) 
    { 
     return base.FormatErrorMessage(MaxTotalSize.ToString()); 
    } 

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) 
    { 
     var rule = new ModelClientValidationRule 
     { 
      ErrorMessage = FormatErrorMessage(MaxTotalSize.ToString()), 
      ValidationType = "maxsize" 
     }; 
     rule.ValidationParameters["maxsize"] = MaxTotalSize; 
     yield return rule; 
    } 
} 

下一步是爲具有控制器:

public class HomeController : Controller 
{ 
    public ActionResult Index() 
    { 
     return View(new MyViewModel()); 
    } 

    [HttpPost] 
    public ActionResult Index(MyViewModel model) 
    { 
     if (!ModelState.IsValid) 
     { 
      // Server side validation failed => redisplay the view so 
      // that the user can fix his errors 
      return View(model); 
     } 

     // Server side validation passed => here we can process the 
     // model.Files collection and do something useful with the 
     // uploaded files knowing that their total size will be smaller 
     // than what we have defined in the custom MaxFileSize attribute 
     // used on the view model 
     // ... 

     return Content("Thanks for uploading all those files"); 
    } 
} 

和相應的視圖:

@model MyViewModel 

<script src="@Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script> 
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script> 
<script type="text/javascript"> 
    (function ($) { 
     $.validator.unobtrusive.adapters.add('maxsize', ['maxsize'], function (options) { 
      options.rules['maxsize'] = options.params; 
      if (options.message) { 
       options.messages['maxsize'] = options.message; 
      } 
     }); 

     $.validator.addMethod('maxsize', function (value, element, params) { 
      var maxSize = params.maxsize; 
      var $element = $(element); 
      var files = $element.closest('form').find(':file[name=' + $element.attr('name') + ']'); 
      var totalFileSize = 0; 
      files.each(function() { 
       var file = $(this)[0].files[0]; 
       if (file && file.size) { 
        totalFileSize += file.size; 
       } 
      }); 
      return totalFileSize < maxSize; 
     }, ''); 
    })(jQuery); 
</script> 


@Html.ValidationMessageFor(x => x.Files) 
@using (Html.BeginForm(null, null, FormMethod.Post, new { enctype = "multipart/form-data" })) 
{ 
    <div> 
     @foreach (var item in Enumerable.Range(1, 3)) 
     { 
      @Html.TextBoxFor(x => x.Files, new { type = "file" }) 
     } 
    </div> 
    <button type="submit">OK</button> 
} 

顯然這裏示出什麼都沒有內部的做的JavaScript視圖。它必須進入視圖可以引用的單獨的可重用JavaScript文件。爲了更好的可讀性和輕鬆複製場景,我將它包含在內部,但在真實世界中,絕不寫入內聯JavaScript。

+1

這太不可思議了!非常感謝你的徹底例證。我根本不知道該視圖可能會多次繪製相同的TextBoxFor列表,但只會渲染一次客戶端規則,並且jQuery驗證會將它們全部視爲一個整體。優秀!其他讀者只需要注意一點:IE不支持JavaScript文件大小查找(截至IE9),因此我的/ Darin的JS代碼實際上會異常排除,導致無法驗證。驗證方法中的一個簡單的if(element.files)修復了這個問題。再次感謝! –