diff --git a/1-aspnet-introduction-slides.html b/1-aspnet-introduction-slides.html index edc687e..8a9d940 100644 --- a/1-aspnet-introduction-slides.html +++ b/1-aspnet-introduction-slides.html @@ -13,13 +13,13 @@ /* buutti.css */ /* @theme buutti */div#\:\$p>svg>foreignObject>section .columns{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:calc(var(--marpit-root-font-size, 1rem) * 1)}div#\:\$p>svg>foreignObject>section .columns12{display:grid;grid-template-columns:1fr 2fr;gap:calc(var(--marpit-root-font-size, 1rem) * 1)}div#\:\$p>svg>foreignObject>section .columns21{display:grid;grid-template-columns:2fr 1fr;gap:calc(var(--marpit-root-font-size, 1rem) * 1)}div#\:\$p>svg>foreignObject>section .columns32{display:grid;grid-template-columns:3fr 2fr;gap:calc(var(--marpit-root-font-size, 1rem) * 1)}div#\:\$p>svg>foreignObject>section .columns23{display:grid;grid-template-columns:2fr 3fr;gap:calc(var(--marpit-root-font-size, 1rem) * 1)}div#\:\$p>svg>foreignObject>section .columns111{display:grid;grid-template-columns:1fr 1fr 1fr;gap:calc(var(--marpit-root-font-size, 1rem) * 1)}div#\:\$p>svg>foreignObject>section .centered{display:flex;flex-direction:column;justify-content:center;text-align:center}div#\:\$p>svg>foreignObject>section .tableborderless td,div#\:\$p>svg>foreignObject>section th{border:none!important;border-collapse:collapse}div#\:\$p>svg>foreignObject>section.extra{background-color:#5d275d;background-image:linear-gradient(to bottom,#401a40,#1d0c1d);color:white}div#\:\$p>svg>foreignObject>section.extra a{color:rgb(145,255,209)}div#\:\$p>svg>foreignObject>section.exercise{background-color:#29366f;background-image:linear-gradient(to bottom,#20636a,#173742);color:white}div#\:\$p>svg>foreignObject>section.exercise a{color:rgb(211,173,255)} -/* @theme zk24th0mt1mugosj50ud5w3iz0lt31kd4zssn5haex6 */div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]{columns:initial!important;display:block!important;padding:0!important}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]:after,div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]:before,div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=content]:after,div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=content]:before{display:none!important}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]>div[data-marpit-advanced-background-container]{all:initial;display:flex;flex-direction:row;height:100%;overflow:hidden;width:100%}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]>div[data-marpit-advanced-background-container][data-marpit-advanced-background-direction=vertical]{flex-direction:column}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background][data-marpit-advanced-background-split]>div[data-marpit-advanced-background-container]{width:var(--marpit-advanced-background-split,50%)}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background][data-marpit-advanced-background-split=right]>div[data-marpit-advanced-background-container]{margin-left:calc(100% - var(--marpit-advanced-background-split, 50%))}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]>div[data-marpit-advanced-background-container]>figure{all:initial;background-position:center;background-repeat:no-repeat;background-size:cover;flex:auto;margin:0}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]>div[data-marpit-advanced-background-container]>figure>figcaption{position:absolute;border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;white-space:nowrap;width:1px}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=content],div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=pseudo]{background:transparent!important}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=pseudo],div#\:\$p>svg[data-marpit-svg]>foreignObject[data-marpit-advanced-background=pseudo]{pointer-events:none!important}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background-split]{width:100%;height:100%}
+/* @theme a8h8n70bg0ok9c8o8ybti6dlz0pmul6s5qxel7pi6ol */div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]{columns:initial!important;display:block!important;padding:0!important}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]:after,div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]:before,div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=content]:after,div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=content]:before{display:none!important}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]>div[data-marpit-advanced-background-container]{all:initial;display:flex;flex-direction:row;height:100%;overflow:hidden;width:100%}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]>div[data-marpit-advanced-background-container][data-marpit-advanced-background-direction=vertical]{flex-direction:column}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background][data-marpit-advanced-background-split]>div[data-marpit-advanced-background-container]{width:var(--marpit-advanced-background-split,50%)}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background][data-marpit-advanced-background-split=right]>div[data-marpit-advanced-background-container]{margin-left:calc(100% - var(--marpit-advanced-background-split, 50%))}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]>div[data-marpit-advanced-background-container]>figure{all:initial;background-position:center;background-repeat:no-repeat;background-size:cover;flex:auto;margin:0}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]>div[data-marpit-advanced-background-container]>figure>figcaption{position:absolute;border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;white-space:nowrap;width:1px}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=content],div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=pseudo]{background:transparent!important}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=pseudo],div#\:\$p>svg[data-marpit-svg]>foreignObject[data-marpit-advanced-background=pseudo]{pointer-events:none!important}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background-split]{width:100%;height:100%}

Introduction to ASP.NET

-
+

Getting started with ASP.NET

-
+

ASP.NET

  • ASP.NET is a server-side framework developed by Microsoft
  • @@ -35,7 +35,7 @@
-
+

ASP.NET Core

  • ASP.NET Core is a complete redesign & rewrite of ASP.NET @@ -50,7 +50,7 @@
  • We will be focusing on Web APIs and won't be covering the frontend development tools of ASP.NET
-
+

Why use ASP.NET Core?

  • As a .NET application, supports NuGet packages that can be added to your projects modularly
  • @@ -77,7 +77,7 @@
-
+

Swagger & Swagger UI

@@ -93,7 +93,7 @@
-
+

Exercise 1: Creating an ASP.NET Core Web Application

    @@ -109,7 +109,7 @@
-
+
  1. Give a Project name and set a Location for the repository, and check Place solution and project in the same directory. Click Next in the bottom right corner.
  2. Select .NET 9.0 under Framework. Authentication type should be None for now. Uncheck Configure for HTTPS. Click Create in the bottom right corner.
  3. @@ -126,7 +126,7 @@
-
+
  1. Add Swagger to your project. Go to View > Other Windows > Package Manager Console and run the following command:
    Install-Package Swashbuckle.AspNetCore -Version 6.6.2
     
    @@ -134,7 +134,7 @@

-
+
  1. Make sure Program.cs includes the following lines:
    builder.Services.AddControllers();
     
    @@ -153,11 +153,11 @@ builder.Services.AddSwaggerGen();           // add th
     
-
+
  1. Start debugging from the top (the â–¶ button with the text http).
      -
    • Click OK to trust the sertificates.
    • +
    • Click Yes twice to trust the certificates.
@@ -171,7 +171,7 @@ builder.Services.AddSwaggerGen(); // add th
-
+
  1. A web page should open, showing SwaggerUI for a weather forecast API. Click it open đź”˝.
  2. Click Try it out, and 11. Execute the GET request and see what it returns.
  3. @@ -186,7 +186,7 @@ builder.Services.AddSwaggerGen(); // add th
-
+
  1. Close the window. Browse through the source files on Solution Explorer on the right and check where the weather forecasts come from.
@@ -194,7 +194,7 @@ builder.Services.AddSwaggerGen(); // add th

-
+

ASP.NET Core Web API contents

  • In the previous exercise, we chose an API template for our new project, which have some files and dependencies already added
  • @@ -207,10 +207,10 @@ builder.Services.AddSwaggerGen(); // add th
  • You can use the API template for the assignments, though
-
+

The heart of the server: Program.cs

-
+

A default server program

  • The Program.cs file in ASP.NET 7 is where the services for the web application are configured and the middleware is defined
  • @@ -225,7 +225,7 @@ builder.Services.AddSwaggerGen(); // add th
-
+

Services

  • @@ -245,7 +245,7 @@ builder.Services.AddSwaggerGen();
-
+

Middlewares

  • Handling of each HTTP request is defined as a set of middlewares
  • @@ -262,10 +262,10 @@ app.Run();
-
+

An example controller: WeatherController.cs

-
+

Routing and endpoints

  • Routing is how web APIs match the requested URI to a corresponding action
  • @@ -298,7 +298,7 @@ app.Run();
-
+

Attributes

  • Attributes (see C# Basics: Lecture 15) are a way of attaching metadata to entities (classes, methods, properties, etc.)
  • @@ -319,7 +319,7 @@ app.Run();
-
+

Attribute Routing

@@ -353,7 +353,7 @@ app.Run();
-
+

Exercise 2: Setting up Routes

    @@ -364,7 +364,7 @@ instead of

You can see the route change in the Swagger UI GET method.

-
+

Handling HttpGet Requests

  • We have now established how to call methods with HTTP requests
  • @@ -385,7 +385,7 @@ instead of

-
+
  • The URI parameters can be made optional with '?'
  • A default value must be then set for the method parameter:
    [Route("api")]
    @@ -405,7 +405,7 @@ instead of

-
+
  • Apply constraints for the parameters by setting them after :
  • If the URI doesn't fit the constraints, the response will hold a 404 status code
    [HttpGet("products/{id:int}")]              // Required type: int
    @@ -417,7 +417,7 @@ instead of

-
+

Exercise 3: Returning Your Own List

    @@ -433,10 +433,10 @@ instead of
-
+

Sending requests with Postman

-
+

Postman

  • HTTP POST requests cannot be made with the browser's address bar, only GET!
  • @@ -449,7 +449,7 @@ instead of

-
+

Benefits of using Postman

  • When developing APIs, tools like Postman will almost always surface in the development cycle
  • @@ -458,7 +458,7 @@ instead of
  • Supports all the necessary HTTP requests, like GET, POST, PUT and DELETE
-
+

Exercise 4. Creating requests with Postman

Run the Weather API program, and test both methods with Postman.

@@ -473,7 +473,7 @@ instead of

-
+
  1. Create your request by selecting the method and entering the URL
  2. diff --git a/1-aspnet-introduction.md b/1-aspnet-introduction.md index 9735e14..bb8e352 100644 --- a/1-aspnet-introduction.md +++ b/1-aspnet-introduction.md @@ -149,7 +149,7 @@ Use of NuGet packages add modularity and decrease the minimum memory footprint o 7) Start debugging from the top (the â–¶ button with the text *http*). - * Click OK to trust the sertificates. + * Click *Yes* twice to trust the certificates. ![](imgs/2-aspnet-core-basics_5.png) diff --git a/3-mvc-pattern-and-repositories-slides.html b/3-mvc-pattern-and-repositories-slides.html new file mode 100644 index 0000000..9b03779 --- /dev/null +++ b/3-mvc-pattern-and-repositories-slides.html @@ -0,0 +1,426 @@ +N. MVP Pattern and Repositories
    +

    MVP Pattern and Repositories

    +
    +
    +

    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 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
        • +
        +
      • +
      • It's easier to scale the application in complexity
      • +
      +
    • +
    • ASP.NET Core includes an MVC Framework for implementing this design pattern
    • +
    +
    +
    +

    Model, view and controller

    +
    +
    +

    Model

    +
      +
    • Representation of data in code
    • +
    • Can also include some logic to retrieve and save the data
    • +
    +
    +
    +

    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
    • +
    +
    +
    +

    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
    • +
    +
    +
    +
    +
    +

    MVC implementation

    +
      +
    • In an ASP.NET Core API, the pattern is implemented like this:
    • +
    +
    +
    +

    Model

    +

    Models/User.cs

    +
    namespace MyApi.Models
    +{
    +  public class User
    +  {
    +    public int Id {get; set;}
    +    public string Name {get; set;}
    +  }
    +}
    +
    +
    +
    +

    View

    +
    {
    +  "id": 1
    +  "name": "Sonja"
    +}
    +
    +

    (Basically just JSON data that ASP.NET can show automatically)

    +
    +
    +

    Controller

    +

    Controllers/UserController.cs

    +
    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)
    +    // ...
    +  }
    +}
    +
    +
    +
    +
    +
    +

    The controller

    +
    +
    +
    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);
    +    }
    +  }
    +}
    +
    +
    +
    +
      +
    • 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
    • +
    +
    +
    +
    +
    +

    Exercise 1. Setting up the Project

    + +
      +
    1. Create a new ASP.NET Core Web API template project. Name it CourseAPI. Delete WeatherForecastController.cs and WeatherForecast.cs.
    2. +
    3. Add a new controller CoursesController by right-clicking the Controllers folder and selecting Add > Controller... > API Controller > Empty
    4. +
    5. Add a new folder Models. Inside, add a new class file ( Add > Class... ) named Course.cs
    6. +
    +
      +
    • To the Course class, create the properties int Id, string Name and int Credits
    • +
    +
    +
    +

    Exercise 2. Creating GET endpoints

    + +
      +
    1. Inside the Controller class, initialize a list of courses with a couple of test courses
    2. +
    3. 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
    4. +
    +
    +
    +

    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

    +
    +
    +
    // 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
    +  // ...
    +}
    +
    +
    +
    +
    // 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
    • +
    +
    +
    +
    +
    +

    Exercise 3: Using a Repository

    + +

    Continue working on CourseAPI.

    +
      +
    1. Within the solution, create a folder called Repositories
    2. +
    3. 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.
    4. +
    5. Modify the controller so that it uses the repository for getting courses.
    6. +
    +
    +
    +

    Services

    +
    +
    +

    Repository, as a service

    +
      +
    • Services and dependency injection (DI) have been introduced earlier in this training + +
    • +
    • 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

    +
    +
    +
      +
    • +

      Create a service interface and a class implementing it:
      +

      +
    • +
    • +

      A rule of thumb: Create a separate repository for all tightly coupled models, not necessarily every model

      +
    • +
    +
    +
    +
      +
    • Add all methods of the service to the interface:
      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...
      +}
      +
      +
    • +
    +
    +
    +
      +
    • Next, we'll create a class that implements this interface.
    • +
    +
    +
    +

    Implementing the interface

    +
    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:
      // ...
      +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:
      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!
      [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

    + +

    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. +
    3. Add CourseRepository as a service, and refactor the controller to use methods for ICourseRepository
    4. +
    +
    +
    +

    Wrapping Things Up

    +

    At this point, the flow of your API should be in line with this chart:

    +

    +
    +
    +

    Project hierarchy

    +
    +
    +
      +
    • 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!
      • +
      +
    • +
    +
    +
    +

    +
    +
    +
    +
    +

    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
      • +
      +
    • +
    +
    +
    \ No newline at end of file diff --git a/3-mvc-pattern-and-repositories.md b/3-mvc-pattern-and-repositories.md index 26d7618..c86c064 100644 --- a/3-mvc-pattern-and-repositories.md +++ b/3-mvc-pattern-and-repositories.md @@ -1,151 +1,244 @@ -# MVC Pattern & Repositories +--- +marp: true +paginate: true +math: mathjax +theme: buutti +title: N. MVP Pattern and Repositories +--- + +# MVP Pattern and Repositories + + + -# The MVC Pattern +## 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, your API should follow the __MVC pattern__ +* In order to write production level code with ASP.NET, your API should follow the *__MVC pattern__* -# The MVC Pattern (continued) +### What is the MVC Pattern? -* Helps to enforce __separation of concerns__ , and induce code readability and testability -* Acronym for Model - View - Controller - * Model - * Representation of data in code - * Could also include some logic to retrieve and save the data - * View - * The data that is shown to the client, e.g. web page - * More often than not different from the Model - client does not need to (and often shouldn't) see all possible data - * 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 +* [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 -In an API, the pattern is implemented like this: - -class User - -{ +### Model, view and controller -public int Id {get; set;} +
    +
    -public string Name {get; set;} +#### Model +* Representation of data in code +* Can also include some logic to retrieve and save the data +
    +
    -} +#### 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 +
    +
    -Get() +#### 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 +
    +
    -Post() +### MVC implementation -... +* In an ASP.NET Core API, the pattern is implemented like this: -{ +
    +
    -"id": 1 +#### Model -"name": "Sonja" +`Models/User.cs` +```csharp +namespace MyApi.Models +{ + public class User + { + public int Id {get; set;} + public string Name {get; set;} + } } +``` ---- - -The "view" in APIs is basically just JSON data - -# Exercise 1. Setting up the Project - -Create a new ASP.NET Core Web API template project. Name it CourseAPI. Delete WeatherForecastController.cs and WeatherForecast.cs. +
    +
    -Add a new controller CoursesController by right-clicking the Controllers folder and selecting _Add_ > _Controller… > API Controller > Empty_ +#### View -Add a new folder Models. Inside, add a new class file ( _Add > Class…_ ) Course.cs - -To the Course class, create the properties int Id, string Name and int Credits - -# Exercise 2: Creating GET Endpoints +```csharp +{ + "id": 1 + "name": "Sonja" +} +``` +(Basically just JSON data that ASP.NET can show automatically) -Inside the Controller class, initialize a list of courses with a couple of test courses +
    +
    -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 +#### Controller -# Repositories +`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) + // ... + } +} +``` -* 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 -* 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 +### The controller -public class MockRepo +
    +
    +```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); + } + } +} +``` -// Replace this later on with a database context +
    +
    -private List Students { get; set; } +* 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*** -// Constructor to initialize student list +
    +
    -private MockRepo(){ +### Exercise 1. Setting up the Project + -Students = new List(); +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 + -// Replace this later on with dependency injection +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 -public static MockRepo Instance { get; } = new MockRepo(); +## Repositories -public List GetStudents() => Students; +### Accessing data with repositories -// Update database here later +* 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*** -// Other methods to read/write from database +### Repository Example -// ... +
    +
    +```csharp +// Repositories/ContactRepository.cs +public class MockRepo +{ + // Replace this later on with a database context + private List Contacts { get; set; } + // Constructor to initialize contact list + private MockRepo() + { + Contacts = new List(); + } + // Replace this later on with dependency injection + public static MockRepo Instance { get; } = + new MockRepo(); + + public List GetContacts() => Contacts; + + // Update database here later + // Other methods to read/write from database + // ... } +``` ---- - -At this point, implementation of the db is up to you -Later, real database is added - -# Repository Example (continued) - -In the controller class: +
    +
    +```csharp +// Controllers/ContactsController.cs +// ... [HttpGet] - -public List Get() - +public List Get() { - -return MockRepo.Instance.GetStudents(); - + return MockRepo.Instance.GetContacts(); } +// ... +``` ---- +* At this point, implementation of the "database" is up to you +* Later, a real database is added -At this point, implementation of the db is up to you -Later, real database is added +
    +
    -# Exercise 3: Using a Repository +### Exercise 3: Using a Repository + Continue working on CourseAPI. -Within the solution, create a folder called Repositories +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. -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. +## Services -Modify the controller so that it uses the repository for getting courses. +### Repository, as a service -# Services - -* [Services and dependency injection](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1) (DI) have been mentioned during this course - * Recap: DI allows for loose coupling between classes and their dependencies +* 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 @@ -153,148 +246,138 @@ Modify the controller so that it uses the repository for getting courses. * Repositories should be accessible within the API from multiple controllers * Logical step is to make it a service! -# Services (continued) - -Create a service interface and a class implementing it: - -![](imgs/4-mvc-pattern-and-repositories_0.png) +### Service interface ---- - -Repository for each model? -1 repo for all tightly coupled models +
    +
    -Add _all _ methods of the service to the interface: +* Create a ***service interface*** and a class implementing it: + ![](imgs/4-mvc-pattern-and-repositories_0.png) -public interface IContactRepository - -{ +* ***A rule of thumb:*** Create a separate repository for all ***tightly coupled*** models, not necessarily every model -Contact GetContact(int id); +
    +
    -List GetContacts(); +* Add **_all_** methods of the service to the interface: + ```csharp + public interface IContactRepository + { + Contact GetContact(int id); + List GetContacts(); + void AddContact(Contact contact); + void UpdateContact(int id, Contact contact); + void DeleteContact(int id); + + // UpdateDataBase() later on... + } + ``` -void AddContact(Contact contact); +
    +
    -void UpdateContact(int id, Contact contact); +* Next, we'll create a class that implements this interface. -void DeleteContact(int id); - -// UpdateDataBase() later on... - -} - -Create the class which implements the interface: +### Implementing the interface +```csharp public class ContactRepository : IContactRepository - -{ - -// Replace this with database context in a real life application - -private static List Contacts = new List - { - -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 GetContacts() => Contacts; - -public void UpdateContact(int id, Contact newContact) => - -Contacts = Contacts.Select(c => c.Id != id ? c : newContact).ToList(); - -} - -The service is now ready to be added to the container in the Program.cs file: - -// ... - -builder.Services.AddSingleton(); - -builder.Services.AddControllers().AddNewtonsoftJson(); - -// ... - -Add the service to your controller by creating a constructor and passing it as a parameter: - -public class ContactsController : ControllerBase - -{ - -private readonly IContactRepository _contactRepository; - -public ContactsController(IContactRepository contactRepository) - -{ - -_contactRepository = contactRepository; - + // Replace this with database context in a real life application + private static List Contacts = new List + { + 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 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(); + 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 + -} - -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! - -That's it! Your service is now ready to be consumed by the controller (no need to use the Instance anymore!) - -[HttpGet("{id}")] - -public IActionResult GetContactById(int id) - -{ - -Contact contact = _contactRepository.GetContact(id); - -if (contact == null) - -{ - -return NotFound(); - -} +Continue working on CourseAPI. -return Ok(contact); +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 -# Exercise 4: +At this point, the flow of your API should be in line with this chart: -Continue working on CourseAPI. +![](imgs/api-chart.svg) -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. +### Project hierarchy -Add CourseRepository as a service, and refactor the controller to use methods for ICourseRepository -# Wrapping Things Up +
    +
    -At this point, the flow of your APIs should be in lines with this chart: +* 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! -Client (e.g. Postman) +
    +
    ![](imgs/4-mvc-pattern-and-repositories_1.png) -* 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! +
    +
    -# What the Heck Does This Do? +### What the Heck Does This Do? * A lot of the functionality in ASP.NET comes from the base class library -* At beginning, trying to remember all the methods and what did what can feel overwhelming -* If you ever end up getting confused about what did what, Ctrl + click Type name to go to definition +* 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 diff --git a/imgs/api-chart.svg b/imgs/api-chart.svg new file mode 100644 index 0000000..2c37cc3 --- /dev/null +++ b/imgs/api-chart.svg @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + Client + (e.g. Postman) + + + Controller + + + + + + Repository + + + View + + + Model + + + HTTP request + + + HTTP response + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Database + + + + + + + + + + + + 1. + + + + + + + + + + + + 2. + + + + + + + + + + + + 3. + + + + + + + + + + + + 4. + + + + + + + + + + + + 5. + + + + + + + + + API + +