finish lecture 5
parent
56278a2788
commit
4241f06b0d
File diff suppressed because one or more lines are too long
@ -1,150 +1,154 @@
|
||||
# Model validation & API Design
|
||||
|
||||
# Model Validation
|
||||
|
||||
ASP.NET has a built-in system for validating whether the data sent in requests fits the model set in the code
|
||||
|
||||
Additional requirements can be set with attributes
|
||||
|
||||
public class Contact
|
||||
|
||||
{
|
||||
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
|
||||
[MaxLength(50)]
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
[MaxLength(100)]
|
||||
|
||||
public string Email { get; set; }
|
||||
|
||||
}
|
||||
|
||||
# Model Validation (continued)
|
||||
|
||||
An object can be validated at any time with TryValidateModel method:
|
||||
|
||||
[HttpPost]
|
||||
|
||||
public IActionResult Post([FromBody] Contact contact)
|
||||
|
||||
{
|
||||
|
||||
if (!TryValidateModel(contact))
|
||||
|
||||
{
|
||||
|
||||
return BadRequest(ModelState);
|
||||
|
||||
}
|
||||
|
||||
Contacts.Add(contact);
|
||||
|
||||
return Created(Request.Path, contact);
|
||||
|
||||
}
|
||||
|
||||
The attributes set a corresponding error message and information to the ModelState which is sent with the bad request result
|
||||
|
||||

|
||||
---
|
||||
marp: true
|
||||
paginate: true
|
||||
math: mathjax
|
||||
theme: buutti
|
||||
title: 5. Model Validation & API Design
|
||||
---
|
||||
|
||||
# Model Validation & API Design
|
||||
|
||||
<!-- headingDivider: 5 -->
|
||||
<!-- class: invert -->
|
||||
|
||||
## Model validation in ASP.NET
|
||||
|
||||
* ASP.NET has a built-in system for validating whether the data sent in requests fits the model set in the code
|
||||
* Additional requirements can be set with attributes
|
||||
```csharp
|
||||
public class Contact
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
[MaxLength(50)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[MaxLength(100)]
|
||||
public string Email { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
### `TryValidateModel`
|
||||
|
||||
* An object can be validated at any time with `TryValidateModel` method:
|
||||
```csharp
|
||||
[HttpPost]
|
||||
public IActionResult Post([FromBody] Contact contact)
|
||||
{
|
||||
if (!TryValidateModel(contact))
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
Contacts.Add(contact);
|
||||
return Created(Request.Path, contact);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
* The attributes set a corresponding error message and information to the `ModelState` which is sent with the bad request result
|
||||
|
||||
<div class='columns23' markdown='1'>
|
||||
<div markdown='1'>
|
||||
|
||||

|
||||
|
||||
Sometimes you might have custom requirements and the action should, according to the standard, return a bad request if the action does not fit those requirements
|
||||
|
||||
For example, the simplest possible way to validate the format of an email is to check whether it contains the '@' symbol and with AddModelError add an error if it does not:
|
||||
|
||||
if (!contact.Email.Contains('@'))
|
||||
|
||||
{
|
||||
|
||||
ModelState.AddModelError("Description", "The email is not in valid form.");
|
||||
|
||||
}
|
||||
</div>
|
||||
<div markdown='1'>
|
||||
|
||||
Then check the model state:
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
|
||||
{
|
||||
|
||||
return BadRequest(ModelState);
|
||||
|
||||
}
|
||||

|
||||
|
||||
# Exercise 1:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
* Sometimes you might have custom requirements and the action should, according to the standard, return a bad request if the action does not fit those requirements
|
||||
* For example, the simplest possible way to validate the format of an email is to check whether it contains the `@` symbol
|
||||
* Add an error with `AddModelError` if it does not contain the symbol:
|
||||
```csharp
|
||||
if (!contact.Email.Contains('@'))
|
||||
{
|
||||
ModelState.AddModelError("Description", "The email is not in valid form.");
|
||||
}
|
||||
```
|
||||
* Then check the model state:
|
||||
```csharp
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
```
|
||||
|
||||
## Exercise 1: Add validation
|
||||
<!--_class: "exercise invert" -->
|
||||
|
||||
Continue working on CourseAPI.
|
||||
|
||||
Mark all the properties of the Course class with the [Required] attribute
|
||||
|
||||
Create an endpoint for POST requests with the URI api/courses
|
||||
|
||||
All the contents of the new course should come from the request body and the new course should be added to the Courses list
|
||||
|
||||
The maximum number of credits should be 20 and minimun 1 (Tip: Range)
|
||||
|
||||
Return an appropriate response
|
||||
|
||||
# Model Validation - Limiting PATCH
|
||||
|
||||
* If we only want to allow the PATCH operation to affect Name and Email properties of Contact class
|
||||
* Begin by declaring a class with only those fields:
|
||||
* public class ContactPatch
|
||||
* {
|
||||
* public string Name { get; set; }
|
||||
* public string Email { get; set; }
|
||||
* }
|
||||
|
||||
# Model Validation - Limiting PATCH (continued)
|
||||
|
||||
Use the ContactPatch class instead of the actual class for patching:
|
||||
|
||||
[HttpPatch("{id}")]
|
||||
|
||||
public IActionResult Patch(int id, [FromBody] JsonPatchDocument<ContactPatch> patchDocument)
|
||||
|
||||
{
|
||||
|
||||
Contact initialContact = _contactRepository.GetContact(id);
|
||||
|
||||
ContactPatch patchContact = new ContactPatch
|
||||
|
||||
{ Name = initialContact.Name, Email = initialContact.Email };
|
||||
|
||||
patchDocument.ApplyTo(patchContact, ModelState);
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
|
||||
{
|
||||
|
||||
return BadRequest();
|
||||
|
||||
}
|
||||
|
||||
initialContact.Name = patchContact.Name;
|
||||
|
||||
initialContact.Email = patchContact.Email;
|
||||
|
||||
_contactRepository.UpdateContact(id, initialContact);
|
||||
|
||||
return Ok(initialContact);
|
||||
|
||||
}
|
||||
|
||||
# Designing APIs
|
||||
|
||||
It's useful to get a good overview of the API for yourself and others before getting to work
|
||||
|
||||
| __Method__ | __Endpoint__ | __Description__ | __Success__ | __Failure__ |
|
||||
| :-: | :-: | :-: | :-: | :-: |
|
||||
| __GET__ | __/api/contacts__ | __Return all contacts__ | __200 Ok__ | __400 Bad request__ __404 Not found__ |
|
||||
| __POST__ | __/api/contacts__ | __Add a new contact__ | __201 Created__ | __400 Bad request__ |
|
||||
| __PUT__ | __/api/contacts/{id}__ | __Update a contact__ | __204 No content__ | __400 Bad request__ __404 Not found__ |
|
||||
| __PATCH__ | __/api/contacts/{id}__ | __Partially update a contact__ | __204 No content__ | __400 Bad request__ __404 Not found__ |
|
||||
| __DELETE__ | __/api/contacts/{id}__ | __Remove a contact__ | __200 OK__ | __404 Not found__ |
|
||||
1) Mark all the properties of the Course class with the `[Required]` attribute
|
||||
2) Create an endpoint for `POST` requests with the URI `api/courses`
|
||||
3) All the contents of the new course should come from the request body and the new course should be added to the `Courses` list
|
||||
4) The maximum number of credits should be 20 and minimum 1 (Tip: Use `Range`)
|
||||
5) Return an appropriate response
|
||||
|
||||
## Limiting `PATCH` with validation
|
||||
|
||||
* Suppose we only want to allow the `PATCH` operation to affect `Name` and `Email` properties of the `Contact` class
|
||||
* Begin by declaring a class with only those fields:
|
||||
```csharp
|
||||
// Models/ContactPatch.cs
|
||||
public class ContactPatch
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Email { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
* Use the `ContactPatch` class instead of the actual class for patching:
|
||||
```csharp
|
||||
// Controllers/ContactsController.cs
|
||||
[HttpPatch("{id}")]
|
||||
public IActionResult Patch(int id, [FromBody] JsonPatchDocument<ContactPatch> patchDocument)
|
||||
{
|
||||
Contact initialContact = _contactRepository.GetContact(id);
|
||||
ContactPatch patchContact = new ContactPatch
|
||||
{ Name = initialContact.Name, Email = initialContact.Email };
|
||||
|
||||
patchDocument.ApplyTo(patchContact, ModelState);
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
initialContact.Name = patchContact.Name;
|
||||
initialContact.Email = patchContact.Email;
|
||||
|
||||
_contactRepository.UpdateContact(id, initialContact);
|
||||
return Ok(initialContact);
|
||||
}
|
||||
```
|
||||
|
||||
## Designing APIs
|
||||
|
||||
* It can be useful to sketch an overview of the API before starting to implement it.
|
||||
* Write a table with all the endpoints, writing out the
|
||||
* HTTP method
|
||||
* Description of the method
|
||||
* HTTP code on success
|
||||
* HTTP codes on different failure states
|
||||
* See the following example!
|
||||
|
||||
---
|
||||
|
||||
| Method | Endpoint | Description | Success | Failure |
|
||||
|:---------|:---------------------|:---------------------------|:-----------------|:-----------------------------------|
|
||||
| `GET` | `/api/contacts` | Return all contacts | `200 OK` | `400 Bad Request`, `404 Not Found` |
|
||||
| `POST` | `/api/contacts` | Add a new contact | `201 Created` | `400 Bad Request` |
|
||||
| `PUT` | `/api/contacts/{id}` | Update a contact | `204 No Content` | `400 Bad Request`, `404 Not Found` |
|
||||
| `PATCH` | `/api/contacts/{id}` | Partially update a contact | `204 No Content` | `400 Bad Request`, `404 Not Found` |
|
||||
| `DELETE` | `/api/contacts/{id}` | Remove a contact | `200 OK` | `404 Not Found` |
|
||||
|
||||
|
Loading…
Reference in New Issue