ASP.NET MVC文件上传问题:自定义AllowFileSize验证特性未生效
看起来你遇到的核心问题是验证特性绑定的目标不对,导致永远无法触发文件大小检查。让我一步步帮你分析并解决:
问题根源
你的AllowFileSize特性标注在了string类型的FilePathName属性上,但这个属性是用来保存文件路径的,而不是接收上传的文件对象。在模型绑定过程中,FilePathName会被赋值为字符串(或者空),而不是HttpPostedFileBase类型的文件对象。这就导致你的IsValid方法里value as HttpPostedFileBase永远是null,直接返回true,完全跳过了大小验证。
另外,控制器方法里你单独接收了HttpPostedFileBase UploadFile参数,和模型里的FilePathName没有关联,这也让模型验证根本碰不到上传的文件。
解决方案
1. 调整模型结构,添加专门接收文件的属性
修改RegisterForm类,新增一个HttpPostedFileBase类型的属性来接收上传文件,并把验证特性移到这个属性上:
public class RegisterForm { // 保留原有属性... [Key] [Required] public int ID { get; set; } [StringLength(50, ErrorMessage = " error")] [TypeConverter("NVarchar(121)")] public string FullName { get; set; } [RegularExpression(@"^[0-9]*$", ErrorMessage = "enter number")] [Remote("IsUserExists", "RegisterForms", ErrorMessage = " ")] [StringLength(10, MinimumLength = 10, ErrorMessage = " length:10")] public String Mellicode { get; set; } // 新增:用于接收上传文件的属性,添加验证特性 [AllowFileSize(FileSize = 100 * 1024, ErrorMessage = "size:100kb")] // 如果文件是必填项,再加[Required(ErrorMessage = "请选择文件")] public HttpPostedFileBase UploadFile { get; set; } // 原FilePathName仅保存路径,不需要验证特性 public string FilePathName { get; set; } }
2. 修改视图,让上传控件绑定到新属性
把视图里的上传控件改成绑定到模型的UploadFile属性,推荐用强类型的方式(如果要继续用你的自定义Upload helper,可以扩展成强类型版本):
方式一:直接用TextBoxFor(最简单)
<div class="form-group"> @Html.LabelFor(model => model.UploadFile, htmlAttributes: new { @class = "control-label col-md-10" }) <div class="col-md-10"> @Html.TextBoxFor(model => model.UploadFile, new { type = "file", @class = "form-control ", id = "upload-id" }) @Html.ValidationMessageFor(model => model.UploadFile, "", new { @class = "text-danger" }) </div> </div>
方式二:扩展你的自定义Upload helper为强类型版本
如果想继续用自己的helper,新增一个UploadFor方法:
public static class UploadHelper { // 保留原有Upload方法... public static MvcHtmlString Upload(this HtmlHelper helper, string name, object htmlAttributes = null) { TagBuilder input = new TagBuilder("input"); input.Attributes.Add("type", "file"); input.Attributes.Add("id", helper.ViewData.TemplateInfo.GetFullHtmlFieldId(name)); input.Attributes.Add("name", helper.ViewData.TemplateInfo.GetFullHtmlFieldName(name)); if (htmlAttributes != null) { var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes); input.MergeAttributes(attributes); } return new MvcHtmlString(input.ToString()); } // 新增强类型版本 public static MvcHtmlString UploadFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes = null) { var propertyName = ExpressionHelper.GetExpressionText(expression); var fullName = helper.ViewData.TemplateInfo.GetFullHtmlFieldName(propertyName); var id = helper.ViewData.TemplateInfo.GetFullHtmlFieldId(propertyName); TagBuilder input = new TagBuilder("input"); input.Attributes.Add("type", "file"); input.Attributes.Add("id", id); input.Attributes.Add("name", fullName); if (htmlAttributes != null) { var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes); input.MergeAttributes(attributes); } return new MvcHtmlString(input.ToString()); } }
然后视图里使用:
@Html.UploadFor(m => m.UploadFile, new { @class = "form-control ", id = "upload-id" })
3. 调整控制器方法,使用模型的UploadFile属性
不需要再单独接收HttpPostedFileBase UploadFile参数,直接从模型里获取上传文件:
public ActionResult Create([Bind(Include = "ID,FullName,Mellicode,UploadFile")] RegisterForm registerForm) { var myUniqueFileName = string.Format(@"{0}.txt", Guid.NewGuid()); if (ModelState.IsValid) { var uploadFile = registerForm.UploadFile; // 这里不需要再手动判断大小,因为ModelState已经通过验证了 string strFileExtension = System.IO.Path.GetExtension(uploadFile.FileName).ToUpper(); string strContentType = uploadFile.ContentType.ToUpper(); registerForm.FilePathName = myUniqueFileName + uploadFile.FileName; string strPath = Server.MapPath("~/App_Data/"); if (!System.IO.Directory.Exists(strPath)) { System.IO.Directory.CreateDirectory(strPath); } string strPathName = System.IO.Path.Combine(strPath, registerForm.FilePathName); uploadFile.SaveAs(strPathName); db.Registers.Add(registerForm); db.SaveChanges(); return RedirectToAction("Back"); } return View(registerForm); }
4. 可选:添加客户端验证(提升用户体验)
默认自定义特性只有服务器端验证,如果你需要客户端即时提示,让AllowFileSizeAttribute实现IClientValidatable接口:
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public class AllowFileSizeAttribute : ValidationAttribute, IClientValidatable { public int FileSize { get; set; } = 100 * 1024; public override bool IsValid(object value) { HttpPostedFileBase file = value as HttpPostedFileBase; if (file != null) { return file.ContentLength <= FileSize; } // 如果文件非必填,这里返回true;如果必填,要配合[Required]特性 return true; } // 实现客户端验证规则 public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { var rule = new ModelClientValidationRule(); rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()); rule.ValidationType = "allowfilesize"; // 客户端验证标识 rule.ValidationParameters.Add("maxsize", FileSize); // 传递大小参数 yield return rule; } }
然后在视图底部添加客户端验证脚本:
<script src="~/Scripts/jquery.validate.min.js"></script> <script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script> <script> // 注册适配器 $.validator.unobtrusive.adapters.addSingleVal("allowfilesize", "maxsize"); // 添加验证方法 $.validator.addMethod("allowfilesize", function (value, element, params) { if (element.files.length === 0) { return true; // 没选择文件时通过,必填的话靠[Required]验证 } var fileSize = element.files[0].size; return fileSize <= params; }); </script>
这样调整后,你的文件大小验证就能正常生效了,不管是服务器端还是客户端都会检查文件大小。
内容的提问来源于stack exchange,提问作者sara

