How to validate file type of HttpPostedFileBase attribute in Asp.Net MVC 4?

asp.net-mvc-4

Solution

Because your attribute inherits from and existing attribute, it would need to be registered in `global.asax` (refer this answer for an example), however do not do that in your case. Your validation code does not work, and a file type attribute should not be inheriting from `RequiredAttribute` - it needs to inherit from `ValidationAttribute` and if you want client side validation, then it also needs to implement `IClientValidatable`. An attribute to validate file types would be (note this code if for a property which is `IEnumerable<HttpPostedFileBase>` and validates 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;
    }
}

It would be applied to a property as

[FileType("JPG,JPEG,PNG")]
public IEnumerable<HttpPostedFileBase> Attachments { get; set; }

and in the view

@Html.TextBoxFor(m => m.Attachments, new { type = "file", multiple = "multiple" })
@Html.ValidationMessageFor(m => m.Attachments)

The following scripts are then required for client side validation (in conjunction with `jquery.validate.js` and `jquery.validate.unobtrusive.js`

$.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[i].name);
        if ($.inArray(extension, param.validtypes) === -1) {
            return false;
        }
    }
    return true;
});

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

Note that you code is attempting to also validate the maximum size of the file which needs to be a separate validation attribute. For an example of a validation attribute that validates the maximum allowable size, refer this article.

In addition, I recommend The Complete Guide To Validation In ASP.NET MVC 3 - Part 2 as a good guide to creating custom validation attributes

Problem

I'm trying validate a file type of `HttpPostedFileBase` attribute to check type of file but I can't do this because the validation is passing. How could I do this ? trying Model ``` public class EmpresaModel{ [Required(ErrorMessage="Choose a file .JPG, .JPEG or .PNG file")] [ValidateFile(ErrorMessage = "Please select a .JPG, .JPEG or .PNG file")] public HttpPostedFileBase imagem { get; set; } } ``` HTML ``` <div class="form-group"> <label for="@Html.IdFor(model => model.imagem)" class="cols-sm-2 control-label">Escolha a imagem <img src="~/Imagens/required.png" height="6" width="6"></label> @Html.TextBoxFor(model => Model.imagem, new { Class = "form-control", placeholder = "Informe a imagem", type = "file" }) @Html.ValidationMessageFor(model => Model.imagem) </div> ``` ValidateFileAttribute ``` using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Drawing; using System.Drawing.Imaging; using System.Linq; using System.Web; //validate file if a valid image public class ValidateFileAttribute : RequiredAttribute{ public override bool IsValid(object value) { bool isValid = false; var file = value as HttpPostedFileBase; if (file == null || file.ContentLength > 1 * 1024 * 1024) { return isValid; } if (IsFileTypeValid(file)) { isValid = true; } return isValid; } private bool IsFileTypeValid(HttpPostedFileBase file) { bool isValid = false; try { using (var img = Image.FromStream(file.InputStream)) { if (IsOneOfValidFormats(img.RawFormat)) { isValid = true; } } } catch { //Image is invalid } return isValid; } private bool IsOneOfValidFormats(ImageFormat rawFormat) { List<ImageFormat> formats = getValidFormats(); foreach (ImageFormat format in formats) { if(rawFormat.Equals(format)) { return true; } } return false; } private List<ImageFormat> getValidFormats() { List<ImageFormat> formats = new List<ImageFormat>(); formats.Add(ImageFormat.Png); formats.Add(ImageFormat.Jpeg); //add types here return formats; } } ```

Original source