---
marp: true
paginate: true
math: mathjax
theme: buutti
title: 5. Model Validation & API Design
---
# Model Validation & API Design
## 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


---
* 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
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 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` |