© Les Jackson 2020
L. JacksonThe Complete ASP.NET Core 3 API Tutorialhttps://doi.org/10.1007/978-1-4842-6255-9_6

6. Our Model and Repository

Les Jackson1 
(1)
Melbourne, VIC, Australia
 

Chapter Summary

In this chapter we’re going to introduce “data” to our API, so we’ll begin our journey with our Model and Repository classes.

When Done, You Will

  • Understand what a “Model” class is and code one up.

  • Define our Repository Interface, and implement a “mock” instance of it.

  • Understand how we use Dependency Injection to decouple interfaces from implementation.

Our Model

OK so we’ve done the “Controller” part of the MVC pattern (well a bit of it; it’s still not fully complete – but the groundwork is in), so let’s turn our attention to the Model part of the equation.

Just like our Controller, the first thing we want to do is create a Models folder in our main project directory.

Once you’ve done that, create a file in that folder, and name it Command.cs; your directory and file structure should look like this.
../images/501438_1_En_6_Chapter/501438_1_En_6_Fig1_HTML.jpg
Figure 6-1

Model Folder and Command Class

Once created, lets code up our “Command” model – it’s super simple and when done should look like this:
namespace CommandAPI.Models
{
    public class Command
    {
        public int Id {get; set;}
        public string HowTo {get; set;}
        public string Platform {get; set;}
        public string CommandLine {get; set;}
    }
}
As promised, very simple; just be sure that you’ve specified the correct namespace at the top of the code:
CommandAPI.Models

The rest of the class is a simplistic model that we’ll use to “model” our command-line snippets. Possibly the only thing really of note is the Id attribute.

This will form the Primary Key when we eventually create a table in our PostgreSQL DB (noting this is required by Entity Framework Core.)

Additionally, it conforms to the concept of “Convention over Configuration.” That is, we could have named this attribute differently, but it would potentially require further configuration so that Entity Framework could work with it as a primary key attribute. Naming it this way, however, means that we don’t need to do this.

Data Annotations

We could leave our model class like that, but when we come to working with it, especially for creation and update actions, we want to ensure that we specify the properties of our Model that are mandatory and those that are not. For example, would there be any value in adding a command-line snippet to our solution without specifying some data for our CommandLine property? Probably not. We solve this by adding some Data Annotations to our class.

We can decorate our class properties with Data Annotations to specify things like
  • Is it required or not?

  • Maximum length of our strings

  • Whether the property should be defined as Primary Key

  • And so on.

In order to use them in the Command class, make the following updates to our code, making sure to include the using directive as shown:
using System.ComponentModel.DataAnnotations;
namespace CommandAPI.Models
{
    public class Command
    {
        [Key]
        [Required]
        public int Id {get; set;}
        [Required]
        [MaxLength(250)]
        public string HowTo {get; set;}
        [Required]
        public string Platform {get; set;}
        [Required]
        public string CommandLine {get; set;}
    }
}
The Data Annotations added should be self-explanatory:
  • All Properties are required (they cannot be null).

  • Our Id property is a primary key.

  • In addition, the HowTo property can only have a maximum of 250 characters.

With our annotations in place, when we come to creating an instance of our Model later, a validation error (or errors) will be thrown if any of them are not adhered to. They also provide a means by which to generate our database schema, which we’ll come onto in Chapter 7.

As we have made a simple, yet significant, change to our code, let’s add the file to source control, commit it, then push up to GitHub; to do so, issue the following commands in order (make sure you’re in the Solution folder CommandAPISolution):
git add .
git commit -m “Added Command Model to API Project”
git push origin master
You have used these all before, but to reiterate
  • First command adds all files to our local Git repo (this means our new Command.cs file).

  • Second command commits the code with a message.

  • Third command pushes the commit up to GitHub.

If all worked correctly, you should see the commit has been pushed up to GitHub; see the following.
../images/501438_1_En_6_Chapter/501438_1_En_6_Fig2_HTML.jpg
Figure 6-2

Our Committed Model Class

Learning Opportunity

../images/501438_1_En_6_Chapter/501438_1_En_6_Figa_HTML.jpgLooking at the GitHub page presented earlier, how can you tell which parts of our solution we included in the last commit and which were only included in the initial commit?

Our Repository

Taking a quick look back at our application architecture, I’ve outlined the components we’ve either started or, in the case of our Model, completed.
../images/501438_1_En_6_Chapter/501438_1_En_6_Fig3_HTML.jpg
Figure 6-3

Progress through our architecture

It’s all still a bit disjointed; to review we have
  • Started our Controller that currently returns hard coded data

  • Created our Model

The next step in our journey is to define our Repository Interface, which will provide our controller with a technology-agnostic way to obtain data.

What Is an Interface?

An interface is just as it sounds; it’s a specification for what functionality we want it to provide (in this case to our Controller), but we don’t detail how it will be provided – that comes later. It’s essentially an agreement, or contract, with the consumer of that Interface.

When we think about what methods our Repository Interface should provide to our Controller (don’t think about how yet), we can look back at out CRUD actions from Chapter 3 for some guidance.

Verb

URI

Operation

Description

GET

/api/commands

Read

Read all command resources

GET

/api/commands/{Id}

Read

Read a single resource (by Id)

POST

/api/commands

Create

Create a new resource

PUT

/api/commands/{Id}

Update (full)

Update all of a single resource (by Id)

PATCH

/api/commands/{Id}

Update (partial)

Update part of a single resource (by Id)

DELETE

/api/commands/{Id}

Delete

Delete a single resource (by Id)

In this case, they almost directly drive what out Repository should provide:
  • Return a collection of all Commands.

  • Return a single Command (based on its Id).

  • Create a new Command Resource.

  • Update an existing Command Resource (this covers PUT and PATCH).

  • Delete an existing Command Resource.

To start implementing our Repository, back in the root our API Project (in the CommandAPI folder), create another folder and call it Data as shown here.
../images/501438_1_En_6_Chapter/501438_1_En_6_Fig4_HTML.png
Figure 6-4

Data Folder will hold our Repository Interface

Inside this folder, create a file and name it ICommandAPIRepo.cs.
../images/501438_1_En_6_Chapter/501438_1_En_6_Fig5_HTML.png
Figure 6-5

Our ICommanderRepo.cs File

Inside the file, add the following code:
using System.Collections.Generic;
using CommandAPI.Models;
namespace CommandAPI.Data
{
    public interface ICommandAPIRepo
    {
        bool SaveChanges();
        IEnumerable<Command> GetAllCommands();
        Command GetCommandById(int id);
        void CreateCommand(Command cmd);
        void UpdateCommand(Command cmd);
        void DeleteCommand(Command cmd);
    }
}
Your file should look like this; make sure you save the file, and let’s take a look at what we have done.
../images/501438_1_En_6_Chapter/501438_1_En_6_Fig6_HTML.jpg
Figure 6-6

ICommandAPIRepo Interface

  1. 1.

    Using directives, noting that we have brought in the namespace for our Models.

     
  2. 2.

    We specify a public interface and give it a name starting with capital “I” to denote it’s an interface.

     
  3. 3.

    We specify that our Repository should provide a “Save Changes” method; stick a pin in that for now, we’ll revisit when we come to talking about Entity Framework Core in Chapter 7.

     
  4. 4.

    Section 4 defines all the other method signatures that consumers of this interface can use to obtain and manipulate data. They also serve another purpose, which I detail in the section below.

     

What About Implementation?

That’s our Repository Interface complete. Yes, that’s right; it’s done, fully complete. So, your next question (well it was my next question when I was learning about interfaces), will be: OK, but where does stuff “get done”?

Great question!

Again, to labor the point, our interface is just a specification (or a contract) for our consumers. We still need to implement that contract with a concrete class. And this is the power and the beauty of using interfaces: we can create multiple implementations (concrete classes) to provide the same interface, but the consumer doesn’t know, or care, about the implementation being used. All they care about is the interface and what it ultimately provides to them.

Still confused? Let’s move to an example.

Our Mock Repository Implementation

We are going to create a concrete class that implements our interface using our model; however, we’ll just be using “mock” data at this stage (we’ll create another implementation of our interface to use “real” data in the next chapter).

So, in the same Data folder where we placed our repository interface definition, create a new file called MockCommandAPIRepo.cs, and add the following code:
using System.Collections.Generic;
using CommandAPI.Models;
namespace CommandAPI.Data
{
    public class MockCommandAPIRepo : ICommandAPIRepo
    {
    }
}
You should see something like this in your editor.
../images/501438_1_En_6_Chapter/501438_1_En_6_Fig7_HTML.jpg
Figure 6-7

Our Concrete Class Definition is complaining

We have created a public class definition and specified that we want it to implement ICommanderRepo, as denoted by
: ICommanderRepo
And we can see that it’s complaining; this is because we haven’t “implemented” anything yet. If you’re using VS Code or Visual Studio, place your cursor in the complaining section and press
CTRL + .
This will bring up some helpful suggestions on resolution; we want to select the first option “Implement Interface,” as shown in the next figure.
../images/501438_1_En_6_Chapter/501438_1_En_6_Fig8_HTML.jpg
Figure 6-8

Help is always appreciated!

This should then generate some placeholder implementation code for our class.
../images/501438_1_En_6_Chapter/501438_1_En_6_Fig9_HTML.jpg
Figure 6-9

Auto-generated Implementation code

As you can see, it has provided all the method signatures for the members of our interface and populated them with a throw new System.NotImpementedException();

In our example we’re only going to update our two “read” methods:
  • GetAllCommands

  • GetCommandById

This is enough to demonstrate the core concepts of using interfaces and by extension Dependency Injection . So, in those two methods, add the following code as shown below, remembering to save your work when done:
.
.
.
public IEnumerable<Command> GetAllCommands(){
  var commands = new List<Command>
  {
    new Command{
      Id=0, HowTo="How to generate a migration",
      CommandLine="dotnet ef migrations add <Name of Migration>",
      Platform=".Net Core EF"},
    new Command{
      Id=1, HowTo="Run Migrations",
      CommandLine="dotnet ef database update",
      Platform=".Net Core EF"},
    new Command{
      Id=2, HowTo="List active migrations",
      CommandLine="dotnet ef migrations list",
      Platform=".Net Core EF"}
  };
  return commands;
}
public Command GetCommandById(int id){
  return new Command{
    Id=0, HowTo="How to generate a migration",
    CommandLine="dotnet ef migrations add <Name of Migration>",
    Platform=".Net Core EF"};
}
.
.
.

What this does is take our Model class and use it to create some simple mock data (again just hard-coded) and return it when these two methods are called. Not earth-shattering, but it is an implementation (of sorts) of our repository interface.

We now need to move on to making use of the ICommandAPIRepo interface (and by extension the MockCommandAPIRepo concrete class) from within our controller.

To do this we use Dependency Injection.

Dependency Injection

Dependency Injection (DI) has struck fear into many a developer getting to grips with it (myself included), but once you grasp the concept, not only is it pretty straightforward, it’s also really powerful and you’ll want to use it.

What makes it even easier in this instance is that DI is baked right into the heart of ASP.NET Core, so we can get up and running with it quickly without much fuss at all. Next, I’ll take you through a quick theoretical overview; then we’ll employ DI practically in our project (indeed, we’ll continue to use it throughout the tutorial).

Again, as with many of the concepts and technologies in this tutorial, you could fill an entire book on DI, which I’m not going to attempt to do here. If you want a deep dive on this subject beyond what I outline below, the MSDN docs are decent1.

Back to the Start (Up)

To talk about DI in .NET Core, we need to move back to our Startup class and in particular the ConfigureServices method .
../images/501438_1_En_6_Chapter/501438_1_En_6_Fig10_HTML.jpg
Figure 6-10

Startup Class Sequence

Casting your mind back, it is in the ConfigureServices method where our “services” are registered (in this case think of a service as both an interface and an implementation of it). But what exactly do we mean by register?

When we talk about registering services, what we are really talking about is something called a Service Container; this is where we “register” our services. Or to put it another way, this is where we tell the DI system to associate an interface to a given concrete class. See the following diagram.
../images/501438_1_En_6_Chapter/501438_1_En_6_Fig11_HTML.jpg
Figure 6-11

Service Container with our Repository Service Registered

Once we have registered our service in the Service Container, whenever we request to make use of a given interface from somewhere else in our app, the DI system will serve up, or “inject,” the concrete class we’ve associated with the interface (aka the “dependency”) in its place.

This means that if we ever need to swap out our concrete class for some other implementation, we only need to make the change in one place (the ConfigureServices method); the rest of our code does not need to change.

We will follow this practice in this tutorial by first registering our mock repository implementation against the ICommandAPIRepo interface; then we’ll swap it out for something more useful in the next chapter without the need to change any other code (except the registration).

This decoupling of interface (contract) from implementation means that our code is infinitely more maintainable as it grows larger.

Enough theory; let’s code.

Applying Dependency Injection

Back over in our API Project, open the Startup class, and add the following code to our ConfigureServices method :
public void ConfigureServices(IServiceCollection services)
{
  services.AddControllers();
  //Add the code below
  services.AddScoped<ICommandAPIRepo, MockCommandAPIRepo>();
}

The code is quite straightforward; it uses the service collection: services, to register our ICommandAPIRepo with MockCommandAPIRepo. The only other novelty is the use of the AddScoped method.

This has to do with something called “Service Lifetimes,” which in essence tells the DI system how it should provision a service requested via DI; there are three methods available:
  • AddTransient: A service is created each time it is requested from the Service Container.

  • AddScoped: A service is created once per client request (connection).

  • AddSingleton: A service is created once and reused.

Beyond what I’ve just outlined, I feel we may get ourselves off track from our core subject matter: building an API! So, we’ll leave it there for now; again refer to Microsoft Docs as mentioned earlier if you want more info.

OK, so now that we have registered our service, the next step is to make use of it from within our Controller – how do we do that?

Constructor Dependency Injection

If I’m being honest, it was this next bit that tripped me up when I was learning DI, so I’ll try and be a as clear as I can when describing how it works.

We can’t just “new-up” an interface in the same way that we can with regular classes; see Figure 6-12.
../images/501438_1_En_6_Chapter/501438_1_En_6_Fig12_HTML.jpg
Figure 6-12

You can't write this code!

You will get an error along the lines of “Can’t create an instance of an abstract class or interface.” You could revert to “newing-up” a concrete instance of our MockCommandAPIRepo class, but that would defeat the entire purpose of what we have just been talking about. So how do we do it?

The answer is that we have to give the DI system an entry point where it can perform the “injection of the dependency,” which in this case, it means creating a class constructor for our Controller and providing ICommandAPIRepo as a required input parameter. We call this Constructor Dependency Injection.

../images/501438_1_En_6_Chapter/501438_1_En_6_Figb_HTML.jpg Pay very careful attention to the Constructor Dependency Injection code pattern that follows; as you’ll see, this pattern is used time and time again throughout our code as well as in other projects.

Let’s implement this. Move back over to our API project, and open our CommandsController class , and add the following constructor code (make sure you add the new using statement too):
// Remember this using statement
using CommandAPI.Data;
.
.
.
namespace CommandAPI.Controllers
{
  [Route("api/[controller]")]
  [ApiController]
  public class CommandsController : ControllerBase
  {
    //Add the following code to our class
    private readonly ICommandAPIRepo _repository;
    public CommandsController(ICommandAPIRepo repository)
    {
      _repository = repository;
    }
.
.
.
Let’s go through what’s happening.
../images/501438_1_En_6_Chapter/501438_1_En_6_Fig13_HTML.jpg
Figure 6-13

Constructor Dependency Injection Pattern

  1. 1.

    Add the new using statement to reference ICommandAPIRepo.

     
  2. 2.

    We create a private read-only field _repository that will be assigned the injected MockCommandAPIRepo object in our constructor and used throughout the rest of our code.

     
  3. 3.

    The Class constructor will be called when we want to make use of our Controller.

     
  4. 4.

    At the point when the constructor is called, the DI system will spring into action and inject the required dependency when we ask for an instance of ICommandAPIRepo. This is Constructor Dependency Injection.

     
  5. 5.

    We assign the injected dependency (in this case MockCommandAPIRepo) to our private field (see point 1).

     

And that’s pretty much it! We can then use _repository to make use of our concrete implementation class, in this case MockCommandAPIRepo.

As I’ve stated earlier, we’ll reuse this pattern multiple times through the rest of the tutorial; you’ll also see it everywhere in code in other projects – take note.

Update Our Controller

We’ll wrap up this chapter by implementing our two “Read” API controller actions using the mock repository implementation we have. So just to be clear we’ll be implementing the following endpoints.

Verb

URI

Operation

Description

GET

/api/commands

Read

Read all command resources

GET

/api/commands/{Id}

Read

Read a single resource (by Id)

We’ll start with implementing the endpoint that returns a collection of all our command resources, so move back into our Controller, and first remove our existing controller action.
../images/501438_1_En_6_Chapter/501438_1_En_6_Fig14_HTML.jpg
Figure 6-14

Removal of our old Controller Action

In its place, add the following code, remembering to add the required using statement at the top of the class too:
// Remember this using statement
using CommandAPI.Models;
.
.
.
namespace CommandAPI.Controllers
{
  [Route("api/[controller]")]
  [ApiController]
  public class CommandsController : ControllerBase
  {
    private readonly ICommandAPIRepo _repository;
    public CommandsController(ICommandAPIRepo repository)
    {
      _repository = repository;
    }
    //Add the following code
    [HttpGet]
    public ActionResult<IEnumerable<Command>> GetAllCommands()
    {
      var commandItems = _repository.GetAllCommands();
      return Ok(commandItems);
    }
.
.
.
I think the code is relatively straightforward but let’s just step through it.
../images/501438_1_En_6_Chapter/501438_1_En_6_Fig15_HTML.jpg
Figure 6-15

New Controller Action using our Repository

  1. 1.

    The controller action responds to the GET verb.

     
  2. 2.

    The controller action should return an enumeration (IEnumerable) of Command objects.

     
  3. 3.

    We call GetAllCommands on our repository and populate a local variable with the result.

     
  4. 4.

    We return a HTTP 200 Result (OK) and pass back our result set.

     
Make sure you save everything, run your code, and call the endpoint from Postman.
../images/501438_1_En_6_Chapter/501438_1_En_6_Fig16_HTML.jpg
Figure 6-16

Successful API Endpoint Result

  1. 1.

    Verb set to GET.

     
  2. 2.

    Our URI is exactly the same as the one we have used before.

     
  3. 3.

    We get a 200 OK status result.

     
  4. 4.

    We have the hardcoded data returned from our mock repository!

     
../images/501438_1_En_6_Chapter/501438_1_En_6_Figc_HTML.jpgCelebration Checkpoint

This is actually a really important checkpoint! We have implemented our repository interface, created and used a concrete (mock) implementation of it, and used it in our Controller via Dependency Injection!

Give yourself five gold stars and a pat on the back.

We have one more controller action to implement in this section: returning a single resource by supplying its Id. Back over in the Controller, add the following code to implement:
.
.
.
[HttpGet]
public ActionResult<IEnumerable<Command>> GetAllCommands()
{
  var commandItems = _repository.GetAllCommands();
  return Ok(commandItems);
}
//Add the following code for our second ActionResult
[HttpGet("{id}")]
public ActionResult<Command> GetCommandById(int id)
{
  var commandItem = _repository.GetCommandById(id);
  if (commandItem == null)
  {
    return NotFound();
  }
  return Ok(commandItem);
}
.
.
.
There’s a bit more going on here; let’s review.
../images/501438_1_En_6_Chapter/501438_1_En_6_Fig17_HTML.jpg
Figure 6-17

GetCommandByID endpoint

  1. 1.

    The route to this controller action includes an additional route parameter, in this case the Id of the resource we want to retrieve; we can specify this in the HttpGet attribute as shown.

     
  2. 2.

    The controller action requires an id to be passed in as a parameter (this comes from our route; see point 1) and returns an ActionResult of type Command.

     
  3. 3.

    We call GetCommandByID on our repository passing in the Id from our route, storing the result in a local variable.

     
  4. 4.

    We check to see if our result is null and, if so, return a 404 Not Found result.

     
  5. 5.

    Otherwise if we have a Command object, we return a 200 OK and the result.

     
Note

Our mock repository will always return a result irrespective of what Id we pass in, so the null check will never return false in this case. That will change when we come to our “real” repository implementation in Chapter 7.

Let’s check our code by testing it in Postman; note that the route we’ll require is
/api/commands/n
where n is an integer value.
../images/501438_1_En_6_Chapter/501438_1_En_6_Fig18_HTML.jpg
Figure 6-18

Single Command Resource Returned

  1. 1.

    We’re still using a GET request.

     
  2. 2.

    Our URI has changed to reflect the route we need to use to hit our endpoint.

     
  3. 3.

    200 OK Status Retrieved.

     
  4. 4.

    Single Resource returned.

     

We’ll wrap this chapter up here for now as we’ve covered a lot of ground, but we will revisit these two controller actions later when we come on to discussing Entity Framework Core, Data Transfer Objects, and Unit Testing.

Before we finish here though, remember to save everything and (ensuring you’re in the main solution folder CommandAPISolution):
  • git add .

  • git commit -m “Added Model and Mock Repository”

  • git push origin master

to update our Git repository (local and remote) with our changes.

In the next chapter, we move on to using “real” data that’s persisted in a database backend rather than relying on hard-coded mock data.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.34.85