Reputation: 7697
The basic question to start: How can you put a custom, unobtrusive validator ontop of a list of objects within your model? Like, say my model allows multiple file uploads, and thus I have a list of files, and I want my validator to run on each of those files?
Now for a specific example. I've got a custom, unobtrusive validator that checks to see if a file extension is not within a list of prohibited extensions:
public class FileExtensionValidatorAttribute : ValidationAttribute, IClientValidatable {
protected static string[] PROHIBITED_EXTENSIONS = {
// ... List of extensions I don't allow.
};
public override bool IsValid(object value) {
if (value is IEnumerable<HttpPostedFileBase>) {
foreach (var file in (IEnumerable<HttpPostedFileBase>)value) {
var fileName = file.FileName;
if (PROHIBITED_EXTENSIONS.Any(x => fileName.EndsWith(x))) return false;
}
} else {
var file = (HttpPostedFileBase)value;
var fileName = file.FileName;
if (PROHIBITED_EXTENSIONS.Any(x => fileName.EndsWith(x))) return false;
}
return true;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) {
var modelClientVlidationRule = new ModelClientValidationRule {
ErrorMessage = this.ErrorMessageString,
ValidationType = "fileextension",
};
modelClientVlidationRule.ValidationParameters.Add("prohibitedextensions", string.Join("|", PROHIBITED_EXTENSIONS));
yield return modelClientVlidationRule;
}
}
Take note in my IsValid that I built this to accept a single file or a list of files.
In my model class, I can make use of this on a single HttpPostedFileBase:
[FileExtensionValidator(ErrorMessage = "Invalid Extension")]
public HttpPostedFileBase Upload { get; set; }
Then I attach to jquery's validator in my view:
jQuery.validator.addMethod("fileExtension", function (value, element, param) {
var extension = "";
var dotIndex = value.lastIndexOf('.');
if (dotIndex != -1) extension = value.substring(dotIndex + 1).toLowerCase();
return $.inArray(extension, param.prohibitedExtensions) === -1;
});
jQuery.validator.unobtrusive.adapters.add('fileextension', ['prohibitedextensions'], function (options) {
options.rules['fileExtension'] = {
prohibitedExtensions: options.params.prohibitedextensions.split('|')
};
options.messages['fileExtension'] = options.message;
});
This all works great, client side and server side ...but only on a single HttpPostedFileBase. The problem is that I need to provide users the ability to upload one or more files. If I change my model to this:
[FileExtensionValidator(ErrorMessage = "Invalid Extension")]
public List<HttpPostedFileBase> Uploads { get; set; }
...the Client-side validation no longer runs; only the server-side works. This is evident when doing a view-source. The <input> tag that gets generated is missing all the data-val attributes it needs to run. In doing a debug, GetClientValidationRules is never called.
What am I missing?
Could this be because of how I render it? I'm simply using an EditorTemplate for HttpPostedFileBase:
@model System.Web.HttpPostedFileBase
@Html.TextBoxFor(m => m, new { type = "file", size = 60 })
...and my view renders it like this:
<p>@Html.EditorFor(m => m.Uploads)</p>
Any advice is appreciated.
Upvotes: 4
Views: 1704
Reputation: 7697
Here's what I came up with.
I actually think the problem is ultimately caused because MVC doesn't know that I want that Data Annotation on the List to be applied to all of its members. Nor should it I suppose.
So I simply made a "viewmodel" wrapper around HttpPostedFileBase, and put my validator there:
public class UploadedFile {
[FileExtensionValidator(ErrorMessage = "Invalid Extension")]
public HttpPostedFileBase File { get; set; }
}
Then, in my actual model, I now just use a list of those instead:
public List<UploadedFile> Uploads { get; set; }
...with no more dataannotations here of course since they're now in UploadedFile.
Then, with minor modifications to the view and editortemplate to use these, this now works a-ok, client side and server side. (Still, feels clunky to me. If anyone has a simpler way I'm still happy to hear it.)
Upvotes: 3