Working with Data
Model Binding, Data Validation and Files in ASP.NET Core
Table of Contents
Model Binding
– Custom Model Binding
Model Validation
– Custom Model Validation
Working with Files
– Upload and Download Files
2
MODEL BINDING
3
Model Binding
• Model binding in ASP.NET Core MVC maps data from HTTP requests to
action method parameters.
– The parameters may be primitive types or complex types
– Implemented abstractly, paving the way for reusability in different apps
• The framework binds request data to action parameters by name
– The value of each parameter will be searched, using the parameter name
– Classes are mapped using the names of the public settable properties
Request URL PostsController
https://mysite.com/posts/edit/6 public IActionResult Edit(int? id)
Model Binding
• Model binding can look through several data sources per Request
– Form values – POST Request parameters
– Route values – The set of Route values provided by the Routing
– Query strings – The query string parameters in the URL
– Even in headers, cookies, session, etc. in custom model binders
– Data from these sources are stored as name-value pairs
• The framework checks each of the data sources for a parameter value
– If there is no parameter in the data source, the next in order is checked
– The data sources are checked in the order specified above
5
Incoming Request to MVC
Populate Instance Created
Model State by Model Binder
Model public MyController : Controller
Validation {
Binder IActionResult Index(MyInputModel
input)
{
if (ModelState.IsValid)
{
return View();
}
MyInputModel }
instance }
6
Model Binding
• If binding fails, the framework does not throw an error
– Every action, accepting user input, should check if binding was successful
– This is done through the ModelState.IsValid property
• Each entry in the controller's ModelState property is a ModelStateEntry
– Each ModelStateEntry contains an Errors property
– It's rarely necessary to query this collection, though
• Default Model binding works great for most development scenarios
– It is also extensible, and you can customize the built-in behavior
7
Model Binding
• You can easily iterate over the errors in the ModelState
public class UsersController : Controller
{
public IActionResult Register(RegisterUserBindingModel model)
{
if(!ModelState.IsValid)
{
foreach (var error in ModelState.Values.SelectMany(v =>
v.Errors))
{
DoSomething(error);
}
// TODO: Return Error Page
}
return Ok("Success!");
}
}
8
Model Binding
• Built-in Model binding behavior can be directed to a different source
– The framework provides several attributes for that
Attribute Description
[BindRequired] Adds a model state error if binding cannot occur.
[BindNever] Thells the model binder to never bind this parameter.
[From{source}] Used to specify the exact binding source. [FromHeader], [FromQuery], [FromRoute],
[FromForm]
[FromServices] Uses dependency injection to bind parameters from services.
[FromBody] Use configure formatters to bind data from request body. Formatter is selected based
on Content-Type of Request.
[ModelBinder] Used to override the default model binder, binding source and name.
9
Custom Model Binder
• Custom Model Binding can be completely customized
– You need to create a BindingProvider and a Binder
[ModelBinder(BinderType = public class StudentEntityBinder :
typeof(StudentEntityBinder))] IModelBinder
public class Student {
{ public Task BindModelAsync
public int Id { get; set; } (ModelBindingContext bindingContext)
public string Name { get; set; } {
public int Age { get; set; } //TODO: Do Magic ...
}
bindingContext.Result
=
ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
10
Custom Model Binder
public class StudentEntityBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if(context == null) throw new
ArgumentNullException(nameof(context));
if(context.Metadata.ModelType == typeof(Student))
return new
BinderTypeModelBinder(typeof(StudentEntityBinder));
return null;
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.ModelBinderProviders
.Insert(0, new StudentEntityBinderProvider()); // Add custom binder to
beginning
});
} 11
MODEL VALIDATION
Once Model binding is complete, Validation occurs
12
Model Validation
• Validation is absolutely necessary before persisting data
– The may be potential security threats
– There may be malformed data (type, size, data constraints)
• In ASP.NET Core MVC, validation happens both on client and server
Model Validation
• .NET provides us an abstracted validation through attributes
– Some attributes configure model validation by constraint
• Similar to validation on database fields
– Other apply patterns to data to enforce business rules
• Credit Cards, Phone Numbers, Email Addresses etc.
• Validation attributes make enforcing these requirements simple
– They are specified at the property or parameter level
14
Model Validation
• Some of the most popular built-in validation attributes are:
Attribute Description
[CreditCard] Validates the property has a credit card format
[Compare] Validates 2 properties in a model match. (Useful for password confirmation)
[EmailAddress] Validates the property has an email format.
[Phone] Validates the property has a telephone format.
[Range] Validates the property value falls within the given range.
[RegularExpression] Validates the data matches the specified regular expression.
[Required] Makes the property required. Value cannot be null.
[StringLength] Validates that a string property has at most the given maximum length.
[Url] Validates the property has a URL format.
15
Custom Model Validation
• Validation attributes work for most needs, but not for all
– Sometimes you need to implement your own validation attributes
public class IsBefore : ValidationAttribute
{
private const string DateTimeFormat = "dd/MM/yyyy";
private readonly DateTime date;
public IsBefore(string dateInput)
{
this.date = DateTime.ParseExact(dateInput, DateTimeFormat,
CultureInfo.InvariantCulture);
}
protected override ValidationResult IsValid(object value, ValidationContext
validationContext)
{
if ((DateTime)value >= this.date) return new ValidationResult(this.ErrorMessage);
return ValidationResult.Success;
}
16
}
Custom Model Validation
• Then you can use it in your model
public class RegisterUserModel
{
[Required]
public string Username { get; set; }
[Required]
[StringLength(20)]
public string Password { get; set; }
[Required
public string FirstName { get;
set; }
[Required
public string LastName { get; set; }
[IsBefore("01/01/2000")]
public DateTime BirthDate { get;
set; }
17
}
Custom Model Validation
• You can also use validation directly in the Binding Model
– This is done by using the IValidatableObject interface
public class RegisterUserModel : IValidatableObject
{
public string Username { get; set; }
public string Password { get; set; }
public string ConfirmPassword { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if(string.IsNullOrEmpty(Username)) yield return new ValidationResult("Username cannot be
empty");
if(string.IsNullOrEmpty(Password)) yield return new ValidationResult("Password cannot be
empty");
if(ConfirmPassword != Password) yield return new ValidationResult("Passwords do not
match");
}
}
18
Incoming Request to MVC
Can you create a Model Binder IActionResult Index(MyInputModel input) {
for MyInputModel? Validation return View();
}
ModelBinderProvider 1
Sorry, can't create a Model Binder for that
Value Providers
Default
Route URL Query FormData
ModelBinderProvider 2 Model Binder
Yes, I can, here you go Input Formatters
Will never be checked [FormBody]
JSON XML
(if applicable)
ModelBinderProvider 3
19
FILES
Uploading and Downloading files
20
Uploading Files
• ASP.NET Core MVC supports File Upload using simple model binding
– For larger files, Streaming is used
<form method="post" enctype="multipart/form-data"
asp-controller="Files" asp-
action="Upload">
<input type="file" name="file">
<button type="submit" value="Upload" />
</form>
– Multiple-file upload is also supported
<form method="post" enctype="multipart/form-data"
asp-controller="Files" asp-
action="Upload">
<input type="file" name="files" multiple >
<button type="submit" value="Upload" />
</form>
21
Uploading Files
• When uploading files using model binding, your action should accept:
– IFormFile (for single file) or IEnumerable<IFormFile> (or List<IFormFile>)
[HttpPost("Upload")]
public async Task<IActionResult> Upload(List<IFormFile> files)
{
var filePath = Path.GetTempFileName(); // Full path to file in temp
location
foreach (var formFile in files.Where(f => f.Length > 0))
{
using (var stream = new FileStream(filePath, FileMode.Create))
{
await formFile.CopyToAsync(stream);
}
} // Copy files to FileSystem using Streams
return Ok(new { count = files.Count, files.Sum(f => f.Length),
filePath});
22
}
Downloading Files
• ASP.NET Core abstracts file system access through File Providers
– File Providers are used throughout the ASP.NET Core framework
• Examples of where ASP.NET Core uses File Providers internally
– IHostingEnvironment exposes the app’s content root and web root
– Static File Middleware uses File Providers to locate static files
– Razor uses File Providers to locate pages and views
23
Downloading Files
• To access physical files, you have to use PhysicalFileProvider
– You’ll have to initialize it with your server physical files folder path
– Then you can extract information about the File
public IActionResult Download(string fileName)
{
// Construct the path to the physical files folder
string filePath = this.env.ContentRootPath +
this.config["FileSystem:FilesFolderPath"];
IFileProvider provider = new PhysicalFileProvider(filePath); // Initialize the
Provider
IFileInfo fileInfo = provider.GetFileInfo(fileName); // Extract the FileInfo
var readStream = fileInfo.CreateReadStream(); // Extact the Stream
var mimeType = "application/octet-stream"; // Set a mimeType
return File(readStream, mimeType, fileName); // Return FileResult
24
} // NOTE: There is no check if the File exists. This action may result in an error