You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
155 lines
4.6 KiB
Markdown
155 lines
4.6 KiB
Markdown
---
|
|
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'>
|
|
|
|

|
|
|
|
</div>
|
|
<div markdown='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.
|
|
|
|
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` |
|
|
|