JQuery验证:添加自定义方法以在提交时进行验证

时间:2012-06-11 18:53:21

标签: jquery asp.net-mvc-3 jquery-validate unobtrusive-validation

我提交了一个简单的js / 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,但是如果我要将它添加到每个输入:file元素,它将在提交时多次运行相同的验证器,从而不止一次显示错误消息。如果有一种方法可以添加此验证器但不将其绑定到任何元素,那将会很棒。

1 个答案:

答案 0 :(得分: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; }
}

现在让我们定义这个自定义[MaxFileSize]验证属性,该属性显然会执行服务器端验证,但除此之外,它还将实现IClientValidatable接口,允许注册自定义不显眼的客户端验证规则。允许在客户端上转换此验证逻辑(显然只适用于支持HTML5的浏览器File API,允许您在客户端上确定所选文件的大小=&gt; 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。