4

我有一个简单的 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,但如果我将它添加到每个 input:file 元素中,它将在提交时多次运行相同的验证器,从而多次显示错误消息。如果有办法添加此验证器但不将其绑定到任何元素,那就太好了。

4

1 回答 1

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的浏览器,您可以在客户端上确定所选文件的大小 => 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。

于 2012-06-11T21:25:06.583 回答