diff --git a/5-model-validation-and-designing-apis-slides.html b/5-model-validation-and-designing-apis-slides.html new file mode 100644 index 0000000..de57282 --- /dev/null +++ b/5-model-validation-and-designing-apis-slides.html @@ -0,0 +1,209 @@ +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
    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:
    [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:
      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: Add validation

+ +

Continue working on CourseAPI.

+
    +
  1. Mark all the properties of the Course class with the [Required] attribute
  2. +
  3. Create an endpoint for POST requests with the URI api/courses
  4. +
  5. All the contents of the new course should come from the request body and the new course should be added to the Courses list
  6. +
  7. The maximum number of credits should be 20 and minimum 1 (Tip: Use Range)
  8. +
  9. Return an appropriate response
  10. +
+
+
+

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:
    // 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:
    // 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!
  • +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodEndpointDescriptionSuccessFailure
GET/api/contactsReturn all contacts200 OK400 Bad Request, 404 Not Found
POST/api/contactsAdd a new contact201 Created400 Bad Request
PUT/api/contacts/{id}Update a contact204 No Content400 Bad Request, 404 Not Found
PATCH/api/contacts/{id}Partially update a contact204 No Content400 Bad Request, 404 Not Found
DELETE/api/contacts/{id}Remove a contact200 OK404 Not Found
+
+
\ No newline at end of file diff --git a/5-model-validation-and-designing-apis.md b/5-model-validation-and-designing-apis.md index 189424c..b25670f 100644 --- a/5-model-validation-and-designing-apis.md +++ b/5-model-validation-and-designing-apis.md @@ -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 - -![](imgs/6-model-validation-and-designing-apis_0.png) +--- +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 + +
+
![](imgs/6-model-validation-and-designing-apis_1.png) -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."); - -} +
+
-Then check the model state: - -if (!ModelState.IsValid) - -{ - -return BadRequest(ModelState); - -} +![](imgs/6-model-validation-and-designing-apis_0.png) -# Exercise 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 + * 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. -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 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 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` |