FileExtension Validation using custom validation c

2020-01-29 03:31发布

问题:

This question raises after what I've tried from the answer mentioned in my previous question. I followed this article exactly the same way but validations for image files instead of doc files mentioned in the article.

Description: I have a input control of type=file which is to upload image files and this exists in one of the partialview. The partialview gets loaded on click of a button. And to apply validations mentioned in model, explicitly add unobtrusive to the form. But after following all the set-ups mentioned in the above-said article, I am not able to validate the file on submit also the the data-* created by unobtrusive validation is quite fishy or better say invalid. Below is the code to show how my setup looks like and here is the html which gets created by unobtrusive validation with invalid data-* attribute, may be because of which the validation fails to happen.

<input data-charset="file" data-val="true" data-val-fileextensions="" data-val-fileextensions-fileextensions="png,jpg,jpeg" id="File" multiple="multiple" name="File" type="file" value="">

Load Partial View Js

$('.getpartial').on('click', function () {
    $('.loadPartial').empty().load('/Home/GetView',function () {
        var form = $('form#frmUploadImages');
        form.data('validator', null);
        $.validator.unobtrusive.parse(form);
        $(function () {
            jQuery.validator.unobtrusive.adapters.add('fileextensions', ['fileextensions'], function (options) {
                var params = {
                    fileextensions: options.params.fileextensions.split(',')
                };
                options.rules['fileextensions'] = params;
                if (options.message) {
                    options.messages['fileextensions'] = options.message;
                }
            });

            jQuery.validator.addMethod("fileextensions", function (value, element, param) {
                var extension = getFileExtension(value);
                var validExtension = $.inArray(extension, param.fileextensions) !== -1;
                return validExtension;
            });

            function getFileExtension(fileName) {
                var extension = (/[.]/.exec(fileName)) ? /[^.]+$/.exec(fileName) : undefined;
                if (extension != undefined) {
                    return extension[0];
                }
                return extension;
            };
        }(jQuery));
    })
})

ModelClass

public class ImageUploadModel
{
    [FileValidation("png|jpg|jpeg")]
    public HttpPostedFileBase File { get; set; }
}

View

@model ProjectName.Models.ImageUploadModel

@using (Html.BeginForm("UploadImages", "Admin", FormMethod.Post, htmlAttributes: new { id = "frmUploadImages", novalidate = "novalidate", autocomplete = "off", enctype = "multipart/form-data" }))
{
    <div class="form-group">
        <span class="btn btn-default btn-file">
            Browse @Html.TextBoxFor(m => m.File, new { type = "file", multiple = "multiple", data_charset = "file" })
        </span>&nbsp;
        <span class="text-muted" id="filePlaceHolder">No files selected</span>
        @Html.ValidationMessageFor(m => m.File, null, htmlAttributes: new { @class = "invalid" })
    </div>
    <div class="form-group">
        <button class="btn btn-primary addImage pull-right">
            <i class="fa fa-upload"></i> Upload
        </button>
    </div>
}

and finally my CustomFileValidation class

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class FileValidationAttribute : ValidationAttribute, IClientValidatable
{
    private List<string> ValidExtensions { get; set; }

    public FileValidationAttribute(string fileExtensions)
    {
        ValidExtensions = fileExtensions.Split('|').ToList();
    }

    public override bool IsValid(object value)
    {
        HttpPostedFileBase file = value as HttpPostedFileBase;
        if (file != null)
        {
            var fileName = file.FileName;
            var isValidExtension = ValidExtensions.Any(y => fileName.EndsWith(y));
            return isValidExtension;
        }
        return true;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientFileExtensionValidationRule(ErrorMessage, ValidExtensions);
        yield return rule;
    }
}
public class ModelClientFileExtensionValidationRule : ModelClientValidationRule
{
    public ModelClientFileExtensionValidationRule(string errorMessage, List<string> fileExtensions)
    {
        ErrorMessage = errorMessage;
        ValidationType = "fileextensions";
        ValidationParameters.Add("fileextensions", string.Join(",", fileExtensions));
    }
}

回答1:

You need to move the block code

$(function () {
  ....
}(jQuery));

from inside the $('.getpartial').on(..) function to before it so that its is

<script>
  $(function () {
    ....
  }(jQuery));

  $('.getpartial').on('click', function () { // or just $('.getpartial').click(function() {
    $('.loadPartial').empty().load('/Home/GetView',function () { // recommend .load('@Url.Action("GetView", "Home")', function() {
      var form = $('form#frmUploadImages');
      form.data('validator', null);
      $.validator.unobtrusive.parse(form);
    });
  });
</script>

Currently your load the content, re-parse the validator and then add add the methods to jquery validation but its to late (the validator has already been parsed)

Sidenote: You do not need to wrap the validation functions in $(function () {. It can be deleted and simply use $.validator... instead of jQuery.validator.... as your doing elsewhere in your code.

As for the 'fishy' data-val-* attributes, that is exactly what your code generates. Your generating a ClientValidationRule named fileextensions (the ValidationType = "fileextensions"; code) and then you add a property of it also named fileextensions (the ValidationParameters.Add("fileextensions", ..) code which generates data-val-fileextensions-fileextensions="png,jpg,jpeg". As for data-val-fileextensions="", that is generated to store the error message but you have not generated one so its an empty string.

I would suggest a few changes to your code.

  1. Rename it to FileTypeAttribute so that you have the flexibility to add other file validation attributes, for example FileSizeAttribute to validate the maximum size.
  2. In the constructor, generate a default error message, for example add private const string _DefaultErrorMessage = "Only the following file types are allowed: {0}"; and in the last line of the constructor include ErrorMessage = string.Format(_DefaultErrorMessage, string.Join(" or ", ValidExtensions));
  3. Change ValidationParameters.Add("fileextensions", ...) to (say) ValidationParameters.Add("validtypes", ...) so it generates data-val-fileextensions-validtypes="png,jpg,jpeg" which is a bit more meaningful (note you will need to change the script to ...add('fileextensions', ['validtypes'], function() ....

Edit

Your code will not work with <input type="file" multiple="multiple" ... /> In order to do so your property needs to be IEnumerable (note a few minor changes to your code)

[FileType("png, jpg, jpeg")]
public IEnumerable<HttpPostedFileBase> Files { get; set; }

Then the validation attribute needs to check each file in the collection

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class FileTypeAttribute : ValidationAttribute, IClientValidatable
{
    private const string _DefaultErrorMessage = "Only the following file types are allowed: {0}";
    private IEnumerable<string> _ValidTypes { get; set; }

    public FileTypeAttribute(string validTypes)
    {
        _ValidTypes = validTypes.Split(',').Select(s => s.Trim().ToLower());
        ErrorMessage = string.Format(_DefaultErrorMessage, string.Join(" or ", _ValidTypes));
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        IEnumerable<HttpPostedFileBase> files = value as IEnumerable<HttpPostedFileBase>;
        if (files != null)
        {
            foreach(HttpPostedFileBase file in files)
            {
                if (file != null && !_ValidTypes.Any(e => file.FileName.EndsWith(e)))
                {
                    return new ValidationResult(ErrorMessageString);
                }
            }
        }
        return ValidationResult.Success;
    }
    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule
        {
            ValidationType = "filetype",
            ErrorMessage = ErrorMessageString
        };
        rule.ValidationParameters.Add("validtypes", string.Join(",", _ValidTypes));
        yield return rule;
    }
}

And finally the script needs to check each file

$.validator.unobtrusive.adapters.add('filetype', ['validtypes'], function (options) {
    options.rules['filetype'] = { validtypes: options.params.validtypes.split(',') };
    options.messages['filetype'] = options.message;
});

$.validator.addMethod("filetype", function (value, element, param) {
    for (var i = 0; i < element.files.length; i++) {
        var extension = getFileExtension(element.files[0].name);
        if ($.inArray(extension, param.validtypes) === -1) {
            return false;
        }
    }
    return true;
});

function getFileExtension(fileName) {
    if (/[.]/.exec(fileName)) {
        return /[^.]+$/.exec(fileName)[0].toLowerCase();
    }
    return null;
}