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
|
---
|
||||||
|
marp: true
|
||||||
# Model Validation
|
paginate: true
|
||||||
|
math: mathjax
|
||||||
ASP.NET has a built-in system for validating whether the data sent in requests fits the model set in the code
|
theme: buutti
|
||||||
|
title: 5. Model Validation & API Design
|
||||||
Additional requirements can be set with attributes
|
---
|
||||||
|
|
||||||
public class Contact
|
# Model Validation & API Design
|
||||||
|
|
||||||
{
|
<!-- headingDivider: 5 -->
|
||||||
|
<!-- class: invert -->
|
||||||
public int Id { get; set; }
|
|
||||||
|
## Model validation in ASP.NET
|
||||||
[Required]
|
|
||||||
|
* ASP.NET has a built-in system for validating whether the data sent in requests fits the model set in the code
|
||||||
[MaxLength(50)]
|
* Additional requirements can be set with attributes
|
||||||
|
```csharp
|
||||||
public string Name { get; set; }
|
public class Contact
|
||||||
|
{
|
||||||
[MaxLength(100)]
|
public int Id { get; set; }
|
||||||
|
|
||||||
public string Email { get; set; }
|
[Required]
|
||||||
|
[MaxLength(50)]
|
||||||
}
|
public string Name { get; set; }
|
||||||
|
|
||||||
# Model Validation (continued)
|
[MaxLength(100)]
|
||||||
|
public string Email { get; set; }
|
||||||
An object can be validated at any time with TryValidateModel method:
|
}
|
||||||
|
```
|
||||||
[HttpPost]
|
|
||||||
|
### `TryValidateModel`
|
||||||
public IActionResult Post([FromBody] Contact contact)
|
|
||||||
|
* An object can be validated at any time with `TryValidateModel` method:
|
||||||
{
|
```csharp
|
||||||
|
[HttpPost]
|
||||||
if (!TryValidateModel(contact))
|
public IActionResult Post([FromBody] Contact contact)
|
||||||
|
{
|
||||||
{
|
if (!TryValidateModel(contact))
|
||||||
|
{
|
||||||
return BadRequest(ModelState);
|
return BadRequest(ModelState);
|
||||||
|
}
|
||||||
}
|
Contacts.Add(contact);
|
||||||
|
return Created(Request.Path, contact);
|
||||||
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
|
||||||
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
|
</div>
|
||||||
|
<div markdown='1'>
|
||||||
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.");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
Continue working on CourseAPI.
|
||||||
|
|
||||||
Mark all the properties of the Course class with the [Required] attribute
|
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`
|
||||||
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`)
|
||||||
All the contents of the new course should come from the request body and the new course should be added to the Courses list
|
5) Return an appropriate response
|
||||||
|
|
||||||
The maximum number of credits should be 20 and minimun 1 (Tip: Range)
|
## Limiting `PATCH` with validation
|
||||||
|
|
||||||
Return an appropriate response
|
* 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:
|
||||||
# Model Validation - Limiting PATCH
|
```csharp
|
||||||
|
// Models/ContactPatch.cs
|
||||||
* If we only want to allow the PATCH operation to affect Name and Email properties of Contact class
|
public class ContactPatch
|
||||||
* Begin by declaring a class with only those fields:
|
{
|
||||||
* public class ContactPatch
|
public string Name { get; set; }
|
||||||
* {
|
public string Email { get; set; }
|
||||||
* 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:
|
||||||
Use the ContactPatch class instead of the actual class for patching:
|
```csharp
|
||||||
|
// Controllers/ContactsController.cs
|
||||||
[HttpPatch("{id}")]
|
[HttpPatch("{id}")]
|
||||||
|
public IActionResult Patch(int id, [FromBody] JsonPatchDocument<ContactPatch> patchDocument)
|
||||||
public IActionResult Patch(int id, [FromBody] JsonPatchDocument<ContactPatch> patchDocument)
|
{
|
||||||
|
Contact initialContact = _contactRepository.GetContact(id);
|
||||||
{
|
ContactPatch patchContact = new ContactPatch
|
||||||
|
{ Name = initialContact.Name, Email = initialContact.Email };
|
||||||
Contact initialContact = _contactRepository.GetContact(id);
|
|
||||||
|
patchDocument.ApplyTo(patchContact, ModelState);
|
||||||
ContactPatch patchContact = new ContactPatch
|
|
||||||
|
if (!ModelState.IsValid)
|
||||||
{ Name = initialContact.Name, Email = initialContact.Email };
|
{
|
||||||
|
return BadRequest();
|
||||||
patchDocument.ApplyTo(patchContact, ModelState);
|
}
|
||||||
|
|
||||||
if (!ModelState.IsValid)
|
initialContact.Name = patchContact.Name;
|
||||||
|
initialContact.Email = patchContact.Email;
|
||||||
{
|
|
||||||
|
_contactRepository.UpdateContact(id, initialContact);
|
||||||
return BadRequest();
|
return Ok(initialContact);
|
||||||
|
}
|
||||||
}
|
```
|
||||||
|
|
||||||
initialContact.Name = patchContact.Name;
|
## Designing APIs
|
||||||
|
|
||||||
initialContact.Email = patchContact.Email;
|
* 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
|
||||||
_contactRepository.UpdateContact(id, initialContact);
|
* HTTP method
|
||||||
|
* Description of the method
|
||||||
return Ok(initialContact);
|
* HTTP code on success
|
||||||
|
* HTTP codes on different failure states
|
||||||
}
|
* See the following example!
|
||||||
|
|
||||||
# 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 |
|
||||||
|
|:---------|:---------------------|:---------------------------|:-----------------|:-----------------------------------|
|
||||||
| __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` |
|
||||||
| __GET__ | __/api/contacts__ | __Return all contacts__ | __200 Ok__ | __400 Bad request__ __404 Not found__ |
|
| `PUT` | `/api/contacts/{id}` | Update a contact | `204 No Content` | `400 Bad Request`, `404 Not Found` |
|
||||||
| __POST__ | __/api/contacts__ | __Add a new contact__ | __201 Created__ | __400 Bad request__ |
|
| `PATCH` | `/api/contacts/{id}` | Partially update a contact | `204 No Content` | `400 Bad Request`, `404 Not Found` |
|
||||||
| __PUT__ | __/api/contacts/{id}__ | __Update a contact__ | __204 No content__ | __400 Bad request__ __404 Not found__ |
|
| `DELETE` | `/api/contacts/{id}` | Remove a contact | `200 OK` | `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