Validating for large files upon Upload

30,864

Solution 1

One possibility is to write a custom validation attribute:

public class MaxFileSizeAttribute : ValidationAttribute
{
    private readonly int _maxFileSize;
    public MaxFileSizeAttribute(int maxFileSize)
    {
        _maxFileSize = maxFileSize;
    }

    public override bool IsValid(object value)
    {
        var file = value as HttpPostedFileBase;
        if (file == null)
        {
            return false;
        }
        return file.ContentLength <= _maxFileSize;
    }

    public override string FormatErrorMessage(string name)
    {
        return base.FormatErrorMessage(_maxFileSize.ToString());
    }
}

and then you could have a view model:

public class MyViewModel
{
    [Required]
    [MaxFileSize(8 * 1024 * 1024, ErrorMessage = "Maximum allowed file size is {0} bytes")]
    public HttpPostedFileBase File { get; set; }
}

controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View(new MyViewModel());
    }

    [HttpPost]
    public ActionResult Index(MyViewModel model)
    {
        if (!ModelState.IsValid)
        {
            // validation failed => redisplay the view
            return View(model);
        }

        // the model is valid => we could process the file here
        var fileName = Path.GetFileName(model.File.FileName);
        var path = Path.Combine(Server.MapPath("~/App_Data/uploads"), fileName);
        model.File.SaveAs(path);

        return RedirectToAction("Success");
    }
}

and a view:

@model MyViewModel

@using (Html.BeginForm(null, null, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
    @Html.TextBoxFor(x => x.File, new { type = "file" })
    @Html.ValidationMessageFor(x => x.File)
    <button type="submit">OK</button>
}

Now of course for this to work you will have to increase the maximum allowed upload file size in web.config to a sufficiently large value:

<!-- 1GB (the value is in KB) -->
<httpRuntime maxRequestLength="1048576" />

and for IIS7:

<system.webServer>
    <security>
        <requestFiltering>
           <!-- 1GB (the value is in Bytes) -->
            <requestLimits maxAllowedContentLength="1073741824" />
        </requestFiltering>
    </security>
</system.webServer>

We could now bring our custom validation attribute a step further and enable client side validation to avoid wasting bandwidth. Of course verifying the file size before uploading is only possible with HTML5 File API. As a consequence only browsers that support this API will be able to take advantage of it.

So the first step is to make our custom validation attribute implement the IClientValidatable interface which will allow us to attach a custom adapter in javascript:

public class MaxFileSizeAttribute : ValidationAttribute, IClientValidatable
{
    private readonly int _maxFileSize;
    public MaxFileSizeAttribute(int maxFileSize)
    {
        _maxFileSize = maxFileSize;
    }

    public override bool IsValid(object value)
    {
        var file = value as HttpPostedFileBase;
        if (file == null)
        {
            return false;
        }
        return file.ContentLength <= _maxFileSize;
    }

    public override string FormatErrorMessage(string name)
    {
        return base.FormatErrorMessage(_maxFileSize.ToString());
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule
        {
            ErrorMessage = FormatErrorMessage(_maxFileSize.ToString()),
            ValidationType = "filesize"
        };
        rule.ValidationParameters["maxsize"] = _maxFileSize;
        yield return rule;
    }
}

and all that's left is configure the custom adapter:

jQuery.validator.unobtrusive.adapters.add(
    'filesize', [ 'maxsize' ], function (options) {
        options.rules['filesize'] = options.params;
        if (options.message) {
            options.messages['filesize'] = options.message;
        }
    }
);

jQuery.validator.addMethod('filesize', function (value, element, params) {
    if (element.files.length < 1) {
        // No files selected
        return true;
    }

    if (!element.files || !element.files[0].size) {
        // This browser doesn't support the HTML5 API
        return true;
    }

    return element.files[0].size < params.maxsize;
}, '');

Solution 2

You can increase request max length for certain urls in web.config:

<location path="fileupload">
  <system.web>
    <httpRuntime executionTimeout="600" maxRequestLength="10485760" />
  </system.web>
</location>
Share:
30,864
William Calleja
Author by

William Calleja

I'm a Web developer based in Malta interested in developing my own controls.

Updated on April 01, 2020

Comments

  • William Calleja
    William Calleja about 4 years

    I am working with c# MVC 2 and ASP.NET. One of my forms includes a file input field which allows a use to select any file type which will then be converted into a blob and saved into the database. My problem is that whenever a user selects a file that exceeds a certain amoutn of Mb (about 8) I get a page error that says the following:

    The connection was reset
    The connection to the server was reset while the page was loading.
    

    I don't mind that there's an 8Mb limit to the files the users are uploading however I need to stop the current error from happening and display a proper validation message (preferably with the ModelState.AddModelError function). Can anybody help me? I can't seem to 'catch' the error before anything else happens in the page since it's happening before it arrives in the upload function within the controller.

  • William Calleja
    William Calleja about 12 years
    First of all, thank you very much Darin. I am attempting to implement your solution however I cannot seem to be able to use 'IClientValidatable'. I have the System.Web.Mvc both added in the references of the project and in the page usings. What am I doing wrong?
  • Darin Dimitrov
    Darin Dimitrov about 12 years
    I don't know what you are doing wrong. The IClientValidatable was added in ASP.NET MVC 3 in the System.Web.Mvc.dll assembly inside the System.Web.Mvc namespace.
  • William Calleja
    William Calleja about 12 years
    I verified and I was mistaken into thinking that we were using MVC3, we're in fact using MVC2. Since upgrading isn't an option for me, can any part of this solution still apply?
  • Darin Dimitrov
    Darin Dimitrov about 12 years
    @WilliamCalleja, the server side validation solution still applies. As far as client side is concerned, ASP.NET MVC 3 uses the jQuery validate plugin alongside to perform unobtrusive client side validation. In ASP.NET MVC 1 and 2 the default client side validation framework is MicrosoftMvcValidation.js. It's a pretty different model. You may take a look at the following post: haacked.com/archive/2009/11/18/…
  • William Calleja
    William Calleja about 12 years
    It worked! I wish I could give the answer to two of you but alas I can't, I combined ideas from Darin and Jenea, using the '</system.web> <location path="Media/Upload"> <system.web> <httpRuntime executionTimeout="600" maxRequestLength="10485760" /> </system.web> </location>'
  • MsBao
    MsBao about 10 years
    Awesome answer! There is one problem, though. MaxFileSizeAttribute.IsValid returns false when the value is null, effectively requiring a file to be uploaded.
  • Matt
    Matt almost 10 years
    There is one minor bug in your client side javascript validation. The last line should be "return element.files[0].size <= params.maxsize;" in the event they are uploading a file that is the exact size as your maximum. Otherwise this was very helpful.
  • Dominique Alexandre
    Dominique Alexandre over 8 years
    To prevent the required check, just type in "IsValid" : if (file == null) return true;