---
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'>

![](imgs/6-model-validation-and-designing-apis_1.png)

</div>
<div markdown='1'>

![](imgs/6-model-validation-and-designing-apis_0.png)

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

