8

According to the MSDN documentation, by default the FileExtensionsAttribute (.NET 4.5) should allow me to only upload only jpg,jpeg,gif and png files - which is what I want.

I tried uploading a jpg without the attribute, it works. Great. Then I added the attribute to my view model..

[FileExtensions(ErrorMessage = "Please specify a valid image file (.jpg, .jpeg, .gif or .png)")]
public HttpPostedFileBase ImageFile { get; set; }

No joy. The verification fails and the ErrorMessage is shown. On top of that there doesn't seem to be a way to specify any allowed custom file extensions. I ended up extending the FileExtensionsAttribute and using my own verification logic, which works as expected. But why doesn't this way work?

Will post the entire controller and view if required. I used this example as a basis for the uploading logic, but using the DataAnnotations.FileExtensionsAttribute instead of Microsoft.Web.Mvc.FileExtensions.. How do I upload images in ASP.NET MVC?

Community
  • 1
  • 1
Greig Stewart
  • 109
  • 1
  • 1
  • 6

4 Answers4

26

Since System.ComponentModel.DataAnnotations.FileExtensionsAttribute is sealed. I use a wrapper for MVC 4.

public class HttpPostedFileExtensionsAttribute : DataTypeAttribute, IClientValidatable
{
    private readonly FileExtensionsAttribute _innerAttribute =
        new FileExtensionsAttribute();

    /// <summary>
    ///     Initializes a new instance of the <see cref="HttpPostedFileExtensionsAttribute" /> class.
    /// </summary>
    public HttpPostedFileExtensionsAttribute()
        : base(DataType.Upload)
    {
        ErrorMessage = _innerAttribute.ErrorMessage;
    }

    /// <summary>
    ///     Gets or sets the file name extensions.
    /// </summary>
    /// <returns>
    ///     The file name extensions, or the default file extensions (".png", ".jpg", ".jpeg", and ".gif") if the property is not set.
    /// </returns>
    public string Extensions
    {
        get { return _innerAttribute.Extensions; }
        set { _innerAttribute.Extensions = value; }
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata,
        ControllerContext context)
    {
        var rule = new ModelClientValidationRule
        {
            ValidationType = "extension",
            ErrorMessage = FormatErrorMessage(metadata.GetDisplayName())
        };
        rule.ValidationParameters["extension"] = _innerAttribute.Extensions;
        yield return rule;
    }

    /// <summary>
    ///     Applies formatting to an error message, based on the data field where the error occurred.
    /// </summary>
    /// <returns>
    ///     The formatted error message.
    /// </returns>
    /// <param name="name">The name of the field that caused the validation failure.</param>
    public override string FormatErrorMessage(string name)
    {
        return _innerAttribute.FormatErrorMessage(name);
    }

    /// <summary>
    ///     Checks that the specified file name extension or extensions is valid.
    /// </summary>
    /// <returns>
    ///     true if the file name extension is valid; otherwise, false.
    /// </returns>
    /// <param name="value">A comma delimited list of valid file extensions.</param>
    public override bool IsValid(object value)
    {
        var file = value as HttpPostedFileBase;
        if (file != null)
        {
            return _innerAttribute.IsValid(file.FileName);
        }

        return _innerAttribute.IsValid(value);
    }
}
Zhaph - Ben Duguid
  • 26,785
  • 5
  • 80
  • 117
jfeinour
  • 369
  • 3
  • 3
  • Yes it should, since it is `IClientValidatable` it should emit the unobstrusive jquery attributes in the html – jfeinour Aug 21 '13 at 02:57
  • @bzlm - This works as expected with ASP.NET MVC5 and the newer [JQuery Validation Unobtrusive Native](http://johnnyreilly.github.io/jQuery.Validation.Unobtrusive.Native/) library - this uses the more up-to-date JQuery Valdiation libraries. – Zhaph - Ben Duguid Jun 04 '15 at 10:07
  • Trying to use a localized error message with this wrapper but even when ErrorMessageResourceType and ErrorMessageResourceName is set on HttpPostedFileExtensionsAttribute it still uses the default error message. Any ideas how to localize error message? – hamdiakoguz Oct 26 '16 at 21:30
  • Thanks for this, it works well, but I had an issue with client validation. I found that the method `extension` found in jQuery.Validation additional-methods.js already includes the '.' before the extension, therefore the client validation needs to pass in the extensions without the '.'. I have addressed it by changing the line `rule.ValidationParameters["extension"] = _innerAttribute.Extensions;` to `rule.ValidationParameters["extension"] = _innerAttribute.Extensions.Replace(".","");` – germankiwi Mar 06 '17 at 23:42
7

Use the Extensions property to set them. Although according to the documentation

The file name extensions, or the default file extensions (".png", ".jpg", ".jpeg", and ".gif") if the property is not set.

You can set it just like you did the ErrorMessage. The more likely issue is that it doesn't know how to assess whether the HttpPostedFileBase has the right extension. You'll need to use the one from the MVC framework or create your own.

Yuriy Faktorovich
  • 67,283
  • 14
  • 105
  • 142
  • Thanks. You were right about the FileExtensionsAttribute not knowing how to access the filename. It seems to expect a string as the parameter to verify. – Greig Stewart Dec 19 '11 at 09:44
  • Also, just as a note, I realised the error message is a property whereas the file extensions filter string can only be set as an optional constructor argument (it is a read only property). The syntax is slightly different for each. – Greig Stewart Dec 19 '11 at 11:33
1

I know this is a bit too late, but perhaps this can help someone out there. This is a modified version of @jfeinour, that will work on the client-side as well:

public class HttpPostedFileExtensionAttribute : ValidationAttribute, IClientValidatable {
    private readonly FileExtensionsAttribute _fileExtensionsAttribute = new FileExtensionsAttribute();

    public HttpPostedFileExtensionAttribute() {
        ErrorMessage = _fileExtensionsAttribute.ErrorMessage;
    }

    public string Extensions {
        get { return _fileExtensionsAttribute.Extensions; }
        set { _fileExtensionsAttribute.Extensions = value; }
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) {
        var rule = new ModelClientValidationRule {
            ValidationType = "extension",
            ErrorMessage = FormatErrorMessage(metadata.GetDisplayName())
        };

        rule.ValidationParameters["extension"] =
            _fileExtensionsAttribute.Extensions
                .Replace(" ", string.Empty).Replace(".", string.Empty)
                .ToLowerInvariant();

        yield return rule;
    }

    public override string FormatErrorMessage(string name) {
        return _fileExtensionsAttribute.FormatErrorMessage(name);
    }

    public override bool IsValid(object value) {
        var file = value as HttpPostedFileBase;
        return _fileExtensionsAttribute.IsValid(file != null ? file.FileName : value);
    }
}
Dimitar Dimitrov
  • 14,868
  • 8
  • 51
  • 79
  • I always loved your solutions, but this seems to break my client-side validation and I also can't put ErrorMessageResourceName inside. Any solution? – Dominique Alexandre Mar 09 '15 at 23:37
  • As soon as I put a FileExtensions attribute or your wrapper, the client-side validation doesn't work. – Dominique Alexandre Mar 09 '15 at 23:44
  • jfeinour's version works as expected with ASP.NET MVC5 and the JQuery Validation Unobtrusive Native](http://johnnyreilly.github.io/jQuery.Validation.Unobtrusive.Native/) library - which uses the more up-to-date JQuery Valdiation libraries rather than the very old ones that come with the MS Unobtrusive package. – Zhaph - Ben Duguid Jun 04 '15 at 10:11
  • It doesn't work for me on client-side because $.validator.methods.extension is undefined – Dmitresky Sep 11 '15 at 11:01
0

The FileExtensionsAttribute does not know how to verify a HttpPostedFileBase, so I extended it..

/// <summary>
/// A File extensions attribute for verifying the file extensions of posted files from MVC forms.
/// </summary>
public class PostedFileBaseFileExtensionsAttribute : FileExtensionsAttribute
{

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var file = value as HttpPostedFileBase; 
        if (file == null)
        {
            return new ValidationResult("No File Specified");
        }

        return base.IsValid(file.FileName, validationContext);
    }
}

Note that this method forces the file to be a required field. Alternatively, if it is an optional field, use the code below for the method body. This always returns success if no file is specified (probably more correct in most cases)..

        var file = value as HttpPostedFileBase;
        return file == null ? ValidationResult.Success : base.IsValid(file.FileName, validationContext);
Greig Stewart
  • 109
  • 1
  • 1
  • 6