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.
aspnet-basics/3-mvc-pattern-and-repositor...

384 lines
11 KiB
Markdown

---
marp: true
paginate: true
math: mathjax
theme: buutti
title: N. MVP Pattern and Repositories
---
# MVP Pattern and Repositories
<!-- headingDivider: 3 -->
<!-- class: invert -->
## The MVC Pattern
* For creating APIs, we now know how to
* set up an ASP.NET Core web application,
* set up routes with attributes to connect request URIs with methods,
* respond with HTTP responses
* The problem is that all this functionality is scattered here and there around the program, meaning we have no structure to what we are doing
* In order to write production level code with ASP.NET, your API should follow the *__MVC pattern__*
### What is the MVC Pattern?
* [MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) is a software architectural pattern, an acronym for Model - View - Controller
* Traditionally used for desktop graphical user interfaces
* Helps to enforce __*separation of concerns*__
* When an entity in code has only a single job:
* It is easier to read and write
* It is easier to test and debug
* $\Rightarrow$ It's easier to scale the application in complexity
* ASP.NET Core includes an [MVC Framework](https://learn.microsoft.com/en-us/aspnet/core/mvc/overview?view=aspnetcore-9.0) for implementing this design pattern
### Model, view and controller
<div class='columns' style='grid-template-columns: 0.7fr 1fr 1fr' markdown='1'>
<div markdown='1'>
#### Model
* Representation of data in code
* Can also include some logic to retrieve and save the data
</div>
<div markdown='1'>
#### View
* The data that is shown to the client, e.g. a web page
* More often than not different from the Model - client does not need to (and often *shouldn't*) see all possible data
</div>
<div markdown='1'>
#### Controller
* Communicates with the Model and the View
* How the data (models) will be processed before sending it forwards to the client or to the view
</div>
</div>
### MVC implementation
* In an ASP.NET Core API, the pattern is implemented like this:
<div class='columns111' markdown='1'>
<div markdown='1'>
#### Model
`Models/User.cs`
```csharp
namespace MyApi.Models
{
public class User
{
public int Id {get; set;}
public string Name {get; set;}
}
}
```
</div>
<div markdown='1'>
#### View
```csharp
{
"id": 1
"name": "Sonja"
}
```
(Basically just JSON data that ASP.NET can show automatically)
</div>
<div markdown='1'>
#### Controller
`Controllers/UserController.cs`
```csharp
using Microsoft.AspNetCore.Mvc;
using MyApi.Models;
namespace MyApi.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
[HttpGet("{id}")]
public IActionResult GetUser(int id)
// ...
}
}
```
</div>
</div>
### The controller
<div class='columns32' markdown='1'>
<div markdown='1'>
```csharp
using Microsoft.AspNetCore.Mvc;
using MyApi.Models;
namespace MyApi.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
[HttpGet("{id}")]
public IActionResult GetUser(int id)
{
var person = new Person
{
Id = 1,
Name = "Sonja"
};
return Ok(person);
}
}
}
```
</div>
<div markdown='1'>
* Here's an extended implementation of the earlier controller, showcasing the HTTP `GET` method
* The object that gets sent to the View (the webpage) as a JSON is just hardcoded here, but normally the data comes from a ***database***
</div>
</div>
### Exercise 1. Setting up the Project
<!--_class: "exercise invert" -->
1) Create a new ASP.NET Core Web API template project. Name it `CourseAPI`. Delete `WeatherForecastController.cs` and `WeatherForecast.cs`.
2) Add a new controller `CoursesController` by right-clicking the `Controllers` folder and selecting *Add > Controller... > API Controller > Empty*
3) Add a new folder `Models`. Inside, add a new class file ( _Add > Class..._ ) named `Course.cs`
* To the Course class, create the properties `int Id`, `string Name` and `int Credits`
### Exercise 2. Creating `GET` endpoints
<!--_class: "exercise invert" -->
1) Inside the Controller class, initialize a list of courses with a couple of test courses
2) Create endpoints for `GET` requests with URIs `api/courses` and `api/courses/{id}` which return all courses and a single course with a corresponding ID, respectively
## Repositories
### Accessing data with repositories
* When following the **_separation of concerns_** principle, the controllers or models should NOT be accessing the database directly
* Instead, the web app should have some kind of ***repository*** for reading from and writing to the database
* (Not to be confused with a Git repository!)
* For now, we can just create a mock repository to a new folder called `Repositories` for our needs, and make it static so that it can be accessed everywhere
* Later on, we will add services to make the repository available within our program via ***dependency injection***
### Repository Example
<div class='columns32' markdown='1'>
<div markdown='1'>
```csharp
// Repositories/ContactRepository.cs
public class MockRepo
{
// Replace this later on with a database context
private List<Contact> Contacts { get; set; }
// Constructor to initialize contact list
private MockRepo()
{
Contacts = new List<Contact>();
}
// Replace this later on with dependency injection
public static MockRepo Instance { get; } =
new MockRepo();
public List<Contact> GetContacts() => Contacts;
// Update database here later
// Other methods to read/write from database
// ...
}
```
</div>
<div markdown='1'>
```csharp
// Controllers/ContactsController.cs
// ...
[HttpGet]
public List<Contact> Get()
{
return MockRepo.Instance.GetContacts();
}
// ...
```
* At this point, implementation of the "database" is up to you
* Later, a real database is added
</div>
</div>
### Exercise 3: Using a Repository
<!--_class: "exercise invert" -->
Continue working on CourseAPI.
1) Within the solution, create a folder called `Repositories`
2) Add a mock repository to the folder. Move the course list from the controller to the repository to simulate our database for now. Add methods to get all courses in the list and a single course by ID.
3) Modify the controller so that it uses the repository for getting courses.
## Services
### Repository, as a service
* Services and [dependency injection (DI)](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1) have been introduced earlier in this training
* [C# Basics: Lecture 15: Dependency injection](https://gitea.buutti.com/education/csharp-basics/src/branch/main/15-design-patterns-in-csharp.md#dependency-injection), [Lecture 1: Services](1-aspnet-introduction#services)
* Recap: DI allows for ***loose coupling*** between classes and their dependencies
* This decreases complexity, makes refactoring easier and increases code testability
* DI is used in ASP.NET to distribute services to classes in a controlled way
* Let's get started by making a repository service
* Recap: Repositories are the interface for handling operations to a database
* Repositories should be accessible within the API from multiple controllers
* Logical step is to make it a service!
### Service interface
<div class='columns' markdown='1'>
<div markdown='1'>
* Create a ***service interface*** and a class implementing it:
![](imgs/4-mvc-pattern-and-repositories_0.png)
* ***A rule of thumb:*** Create a separate repository for all ***tightly coupled*** models, not necessarily every model
</div>
<div markdown='1'>
* Add **_all_** methods of the service to the interface:
```csharp
public interface IContactRepository
{
Contact GetContact(int id);
List<Contact> GetContacts();
void AddContact(Contact contact);
void UpdateContact(int id, Contact contact);
void DeleteContact(int id);
// UpdateDataBase() later on...
}
```
</div>
</div>
* Next, we'll create a class that implements this interface.
### Implementing the interface
```csharp
public class ContactRepository : IContactRepository
{
// Replace this with database context in a real life application
private static List<Contact> Contacts = new List<Contact>
{
new Contact{Id=0, Name="Johannes Kantola", Email="johkant@example.com"},
new Contact{Id=1, Name="Rene Orosz", Email="rene_king@example.com"}
};
public void AddContact(Contact contact) => Contacts.Add(contact);
public void DeleteContact(int id) =>
Contacts = Contacts.Where(c => c.Id != id).ToList();
public Contact GetContact(int id) => Contacts.FirstOrDefault(c => c.Id == id);
public List<Contact> GetContacts() => Contacts;
public void UpdateContact(int id, Contact newContact) =>
Contacts = Contacts.Select(c => c.Id != id ? c : newContact).ToList();
}
```
### Adding the service to `Program.cs`
* The service is now ready to be added to the container in the Program.cs file:
```csharp
// ...
builder.Services.AddSingleton<IContactRepository, ContactRepository>();
builder.Services.AddControllers().AddNewtonsoftJson();
// ...
```
### Using the service in a controller
* Add the service to your controller by creating a constructor and passing it as a parameter:
```csharp
public class ContactsController : ControllerBase
{
private readonly IContactRepository _contactRepository;
public ContactsController(IContactRepository contactRepository)
{
_contactRepository = contactRepository;
}
}
```
* If you need to add more services to the controller later, you can just add them as a parameter as well - the order of parameters does not matter!
### Consuming the service in endpoints
* Your service is now ready to be consumed by the controller
* No need to use `MockRepo.Instance.GetContacts()` anymore!
```csharp
[HttpGet("{id}")]
public IActionResult GetContactById(int id)
{
Contact contact = _contactRepository.GetContact(id);
if (contact == null)
{
return NotFound();
}
return Ok(contact);
}
```
### Exercise 4: A real repository
<!--_class: "exercise invert" -->
Continue working on CourseAPI.
1) Create an interface `ICourseRepository` with methods for getting a single course or all courses. Rename your mock repository to `CourseRepository` and make sure the interface is fully implemented.
2) Add `CourseRepository` as a service, and refactor the controller to use methods for `ICourseRepository`
## Wrapping Things Up
At this point, the flow of your API should be in line with this chart:
![](imgs/api-chart.svg)
### Project hierarchy
<div class='columns32' markdown='1'>
<div markdown='1'>
* In solution explorer, the project hierarchy would look like this.
* Once you start adding new classes, they should have their own models and controllers
* One repository can store multiple models, and there can be multiple repositories
* Every server/database/API you use should have its own repository!
</div>
<div markdown='1'>
![](imgs/4-mvc-pattern-and-repositories_1.png)
</div>
</div>
### What the Heck Does This Do?
* A lot of the functionality in ASP.NET comes from the base class library
* When starting out, trying to remember all the methods and what does what can feel overwhelming
* If you ever end up getting confused what any method does, Ctrl + click *Type name* to go to definition
* There is always a summary about the method
* Also looking up the method from Microsoft documentation is helpful