MVC File upload and unobtrusive validation
Upload a file with MVC and validate it with MVC and unobtrusive validation
You can download the code here
Introduction
This example will show you how to upload a file to a MVC controller, and how to validate the file on the client side(JavaScript) and server side.
First we need to create a form with a input type of upload
@using (Html.BeginForm("Index", "Profile", FormMethod.Post, new { @class = "form-horizontal", enctype = "multipart/form-data" }))
{
<div class="form-group">
@Html.LabelFor(m => m.Username, new { @class = "col-sm-2 control-label" })
<div class="col-sm-10">
@Html.TextBoxFor(m => m.Username, new { @class = "form-control", placeholder = "Username" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(m => m.FirstName, new { @class = "col-sm-2 control-label" })
<div class="col-sm-10">
@Html.TextBoxFor(m => m.FirstName, new { @class = "form-control", placeholder = "First Name" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(m => m.LastName, new { @class = "col-sm-2 control-label" })
<div class="col-sm-10">
@Html.TextBoxFor(m => m.LastName, new { @class = "form-control", placeholder = "Last Name" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(m => m.Avatar, new { @class = "col-sm-2 control-label" })
<div class="col-sm-10">
@Html.TextBoxFor(m => m.Avatar, new { @class = "form-control", type = "file" })
@Html.ValidationMessageFor(m => m.Avatar)
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">Save</button>
</div>
</div>
}
NOTE: The form enctype needs to be multipart/form-data
We have a simple controller that accepts the ProfileModel
public class ProfileController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View(new ProfileModel());
}
[HttpPost]
public ActionResult Index(
ProfileModel model)
{
if(!ModelState.IsValid)
{
return View(model);
}
return View(model);
}
}
The ProfileModel code:
public class ProfileModel
{
public string Username { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[MinimumFileSizeValidator(0.5)]
[MaximumFileSizeValidator(2.4)]
[ValidFileTypeValidator("png")]
//[FileUploadValidator(0.5, 2.4, "png")]
public HttpPostedFileBase Avatar { get; set; }
}
The ProfileModel uses the following validators: MinimumFileSizeValidator,MaximumFileSizeValidator, ValidFileTypeValidator and FileUploadValidator
MinimumFileSizeValidator
public class MinimumFileSizeValidator
: ValidationAttribute, IClientValidatable
{
private string _errorMessage = "{0} can not be smaller than {1} MB";
/// <summary>
/// Minimum file size in MB
/// </summary>
public double MinimumFileSize { get; private set; }
/// <param name="minimumFileSize">MinimumFileSize file size in MB</param>
public MinimumFileSizeValidator(
double minimumFileSize)
: base()
{
MinimumFileSize = minimumFileSize;
}
public override bool IsValid(
object value)
{
if (value == null)
{
return true;
}
if (!IsValidMinimumFileSize((value as HttpPostedFileBase).ContentLength))
{
ErrorMessage = String.Format(_errorMessage, "{0}", MinimumFileSize);
return false;
}
return true;
}
public override string FormatErrorMessage(
string name)
{
return String.Format(_errorMessage, name, MinimumFileSize);
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
ModelMetadata metadata
, ControllerContext context)
{
var clientValidationRule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "minimumfilesize"
};
clientValidationRule.ValidationParameters.Add("size", MinimumFileSize);
return new[] { clientValidationRule };
}
private bool IsValidMinimumFileSize(
int fileSize)
{
return ConvertBytesToMegabytes(fileSize) >= MinimumFileSize;
}
private double ConvertBytesToMegabytes(
int bytes)
{
return (bytes / 1024f) / 1024f;
}
}
MaximumFileSizeValidator
public class MaximumFileSizeValidator
: ValidationAttribute, IClientValidatable
{
private string _errorMessage = "{0} can not be larger than {1} MB";
/// <summary>
/// Maximum file size in MB
/// </summary>
public double MaximumFileSize { get; private set; }
/// <param name="maximumFileSize">Maximum file size in MB</param>
public MaximumFileSizeValidator(
double maximumFileSize)
{
MaximumFileSize = maximumFileSize;
}
public override bool IsValid(
object value)
{
if (value == null)
{
return true;
}
if (!IsValidMaximumFileSize((value as HttpPostedFileBase).ContentLength))
{
ErrorMessage = String.Format(_errorMessage, "{0}", MaximumFileSize);
return false;
}
return true;
}
public override string FormatErrorMessage(
string name)
{
return String.Format(_errorMessage, name, MaximumFileSize);
}
public System.Collections.Generic.IEnumerable<ModelClientValidationRule> GetClientValidationRules(
ModelMetadata metadata
, ControllerContext context)
{
var clientValidationRule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "maximumfilesize"
};
clientValidationRule.ValidationParameters.Add("size", MaximumFileSize);
return new[] { clientValidationRule };
}
private bool IsValidMaximumFileSize(
int fileSize)
{
return (ConvertBytesToMegabytes(fileSize) <= MaximumFileSize);
}
private double ConvertBytesToMegabytes(
int bytes)
{
return (bytes / 1024f) / 1024f;
}
}
ValidFileTypeValidator
public class ValidFileTypeValidator
: ValidationAttribute, IClientValidatable
{
private string _errorMessage = "{0} must be one of the following file types: {1}";
/// <summary>
/// Valid file extentions
/// </summary>
public string[] ValidFileTypes { get; private set; }
/// <param name="validFileTypes">Valid file extentions(without the dot)</param>
public ValidFileTypeValidator(
params string[] validFileTypes)
{
ValidFileTypes = validFileTypes;
}
public override bool IsValid(
object value)
{
var file = value as HttpPostedFileBase;
if (value == null || String.IsNullOrEmpty(file.FileName))
{
return true;
}
if (ValidFileTypes != null)
{
var validFileTypeFound = false;
foreach (var validFileType in ValidFileTypes)
{
var fileNameParts = file.FileName.Split('.');
if (fileNameParts[fileNameParts.Length - 1] == validFileType)
{
validFileTypeFound = true;
break;
}
}
if (!validFileTypeFound)
{
ErrorMessage = String.Format(_errorMessage, "{0}", ValidFileTypes.ToConcatenatedString(","));
return false;
}
}
return true;
}
public override string FormatErrorMessage(
string name)
{
return String.Format(_errorMessage, name, ValidFileTypes.ToConcatenatedString(","));
}
public System.Collections.Generic.IEnumerable<ModelClientValidationRule> GetClientValidationRules(
ModelMetadata metadata
, ControllerContext context)
{
var clientValidationRule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "validfiletype"
};
clientValidationRule.ValidationParameters.Add("filetypes", ValidFileTypes.ToConcatenatedString(","));
return new[] { clientValidationRule };
}
}
FileUploadValidator
public class FileUploadValidator
: ValidationAttribute, IClientValidatable
{
private MinimumFileSizeValidator _minimumFileSizeValidator;
private MaximumFileSizeValidator _maximumFileSizeValidator;
private ValidFileTypeValidator _validFileTypeValidator;
/// <param name="validFileTypes">Valid file extentions(without the dot)</param>
public FileUploadValidator(
params string[] validFileTypes)
: base()
{
_validFileTypeValidator = new ValidFileTypeValidator(validFileTypes);
}
/// <param name="maximumFileSize">Maximum file size in MB</param>
/// <param name="validFileTypes">Valid file extentions(without the dot)</param>
public FileUploadValidator(
double maximumFileSize
, params string[] validFileTypes)
: base()
{
_maximumFileSizeValidator = new MaximumFileSizeValidator(maximumFileSize);
_validFileTypeValidator = new ValidFileTypeValidator(validFileTypes);
}
/// <param name="minimumFileSize">MinimumFileSize file size in MB</param>
/// <param name="maximumFileSize">Maximum file size in MB</param>
/// <param name="validFileTypes">Valid file extentions(without the dot)</param>
public FileUploadValidator(
double minimumFileSize
, double maximumFileSize
, params string[] validFileTypes)
: base()
{
_minimumFileSizeValidator = new MinimumFileSizeValidator(minimumFileSize);
_maximumFileSizeValidator = new MaximumFileSizeValidator(maximumFileSize);
_validFileTypeValidator = new ValidFileTypeValidator(validFileTypes);
}
protected override ValidationResult IsValid(
object value
, ValidationContext validationContext)
{
if (value == null)
{
return ValidationResult.Success;
}
try
{
if (value.GetType() != typeof(HttpPostedFileWrapper))
{
throw new InvalidOperationException("");
}
var errorMessage = new StringBuilder();
var file = value as HttpPostedFileBase;
if (_minimumFileSizeValidator != null)
{
if (!_minimumFileSizeValidator.IsValid(file))
{
errorMessage.Append(String.Format("{0}. ", _minimumFileSizeValidator.FormatErrorMessage(validationContext.DisplayName)));
}
}
if (_maximumFileSizeValidator!=null)
{
if (!_maximumFileSizeValidator.IsValid(file))
{
errorMessage.Append(String.Format("{0}. ", _maximumFileSizeValidator.FormatErrorMessage(validationContext.DisplayName)));
}
}
if (_validFileTypeValidator != null)
{
if (!_validFileTypeValidator.IsValid(file))
{
errorMessage.Append(String.Format("{0}. ", _validFileTypeValidator.FormatErrorMessage(validationContext.DisplayName)));
}
}
if (String.IsNullOrEmpty(errorMessage.ToString()))
{
return ValidationResult.Success;
}
else
{
return new ValidationResult(errorMessage.ToString());
}
}
catch(Exception excp)
{
return new ValidationResult(excp.Message);
}
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
ModelMetadata metadata
, ControllerContext context)
{
var clientValidationRule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "fileuploadvalidator"
};
var clientvalidationmethods = new List<string>();
var parameters = new List<string>();
var errorMessages = new List<string>();
if (_minimumFileSizeValidator != null)
{
clientvalidationmethods.Add(_minimumFileSizeValidator.GetClientValidationRules(metadata, context).First().ValidationType);
parameters.Add(_minimumFileSizeValidator.MinimumFileSize.ToString());
errorMessages.Add(_minimumFileSizeValidator.FormatErrorMessage(metadata.GetDisplayName()));
}
if (_maximumFileSizeValidator != null)
{
clientvalidationmethods.Add(_maximumFileSizeValidator.GetClientValidationRules(metadata, context).First().ValidationType);
parameters.Add(_maximumFileSizeValidator.MaximumFileSize.ToString());
errorMessages.Add(_maximumFileSizeValidator.FormatErrorMessage(metadata.GetDisplayName()));
}
if (_validFileTypeValidator != null)
{
clientvalidationmethods.Add(_validFileTypeValidator.GetClientValidationRules(metadata, context).First().ValidationType);
parameters.Add(String.Join(",", _validFileTypeValidator.ValidFileTypes));
errorMessages.Add(_validFileTypeValidator.FormatErrorMessage(metadata.GetDisplayName()));
}
clientValidationRule.ValidationParameters.Add("clientvalidationmethods", clientvalidationmethods.ToConcatenatedString(","));
clientValidationRule.ValidationParameters.Add("parameters" , parameters.ToConcatenatedString("|"));
clientValidationRule.ValidationParameters.Add("errormessages" , errorMessages.ToConcatenatedString(","));
yield return clientValidationRule;
}
private double ConvertBytesToMegabytes(
long bytes)
{
return (bytes / 1024f) / 1024f;
}
}
This concludes the server side validation.
Each validator has a corresponding client-side validator that does the client side validation.
First we need to call the addSingleVal function for each validator
$.validator.unobtrusive.adapters.addSingleVal("minimumfilesize", "size");
$.validator.unobtrusive.adapters.addSingleVal("maximumfilesize", "size");
$.validator.unobtrusive.adapters.addSingleVal("validfiletype", "filetypes");
We can then add the validators
minimumfilesize validator
$.validator.addMethod('minimumfilesize', function (value, element, minSize) {
return convertBytesToMegabytes(element.files[0].size) >= parseFloat(minSize);
});
maximumfilesize validator
$.validator.addMethod('maximumfilesize', function (value, element, maxSize) {
return convertBytesToMegabytes(element.files[0].size) <= parseFloat(maxSize);
});
validfiletype validator
$.validator.addMethod('validfiletype', function (value, element, validFileTypes) {
if (validFileTypes.indexOf(',') > -1) {
validFileTypes = validFileTypes.split(',');
} else {
validFileTypes = [validFileTypes];
}
var fileType = value.split('.')[value.split('.').length - 1];
for (var i = 0; i < validFileTypes.length; i++) {
if (validFileTypes[i] === fileType) {
return true;
}
}
return false;
});
fileuploadvalidator
$.validator.unobtrusive.adapters.add('fileuploadvalidator', ['clientvalidationmethods', 'parameters', 'errormessages'], function (options) {
options.rules['fileuploadvalidator'] = {
clientvalidationmethods: options.params['clientvalidationmethods'].split(','),
parameters: options.params['parameters'].split('|'),
errormessages: options.params['errormessages'].split(',')
};
});
$.validator.addMethod("fileuploadvalidator", function (value, element, param) {
if (value == "" || value == null || value == undefined) {
return true;
}
//array of jquery validation rule names
var validationrules = param["clientvalidationmethods"];
//array of paramteres required by rules, in this case regex patterns
var patterns = param["parameters"];
//array of error messages for each rule
var rulesErrormessages = param["errormessages"];
var validNameErrorMessage = new Array();
var index = 0
for (i = 0; i < patterns.length; i++) {
var valid = true;
var pattern = patterns[i].trim();
//get a jquery validator method.
var rule = $.validator.methods[validationrules[i].trim()];
//create a paramtere object
var parameter = new Object();
parameter = pattern;
//execute the rule
var isValid = rule.call(this, value, element, parameter);
if (!isValid) {
//if rule fails, add error message
validNameErrorMessage[index] = rulesErrormessages[i];
index++;
}
}
//if we have more than on error message, one of the rule has failed
if (validNameErrorMessage.length > 0) {
//update the error message for 'validname' rule
$.validator.messages.fileuploadvalidator = validNameErrorMessage.toString();
return false;
}
return true;
}, "The file is not valid"//default error message
);
and the convertBytesToMegabytes function
function convertBytesToMegabytes(bytes) {
return (bytes / 1024) / 1024;
}