Design Patterns: Asp.Net Core Web API, services, and repositories

Part 2: Dependency Injection

Posted by Carl-Hugo Marcotte on August 14, 2017
Design Patterns: Asp.Net Core Web API, services, and repositories

In the first part, we visited a few concepts that we will start applying in the next article. But before that, we will do a little interlude about Dependency Injection.

If you are already familiar with Dependency Injection in Asp.Net Core, feel free to jump right to the next article of the series.

In this article, we will cover:

  • Dependency Injection Basics
  • Constructor Injection
  • Scopes (dependencies’ lifetime)
  • Asp.Net Core default DI container

Skip the shared part

The series (shared section)

In the series, we will create an Asp.Net Core 2.0 Web API, and we will focus on the following major concerns:

  1. The web part; the HTTP request and response handling.
  2. The business logic; the domain.
  3. The data access logic; reading and writing data.

During the article, I will try to include the thinking process behind the code.

Technology-wise, we will use Asp.Net Core, Azure Table Storage and ForEvolve Framework to build the Web API.

To use the ForEvolve Framework (or let’s say toolbox), you will need to install packages from a custom NuGet feed. If you dont know How to use a custom NuGet feed in Visual Studio 2017, feel free to take a look at this article. If you do, the ForEvolve NuGet feed URI is https://www.myget.org/F/forevolve/api/v3/index.json.

We will also use XUnit and Moq for both unit and integration testing.

Table of content

Article Source code
Part 1: Introduction 1. NinjaApi - Starting point
Part 2: Dependency Injection DependencyInjection sample
Part 3: Models and Controllers 3. NinjaApi - ClansControllers
Part 4: Services and the ClanService 4. NinjaApi - The ClanService
Part 5: Repositories, the ClanRepository, and integration testing 5. NinjaApi - Clans completed
Part 6: the NinjaController and the ninja sub-system 6. NinjaApi - NinjaController
Part 7: the NinjaService 7. NinjaApi - NinjaService
Part 8: Azure table storage and the data model 8. NinjaApi - NinjaEntity
Part 9: the NinjaMappingService and the Façade pattern 9. NinjaApi - NinjaMappingService
Part 10: the NinjaRepository and ForEvolve.Azure 10. NinjaApi - NinjaRepository
Part 11: Integration testing 11. NinjaApi - IntegrationTesting
More might come someday…  

I will update the table of content as the series progress.

“Prerequisites”

In the series, I will cover multiple subjects, more or less in details, and I will assume that you have a little idea about what a Web API is, that you know C# and that you already have a development environment setup (i.e.: Visual Studio, Asp.Net Core, etc.).

The goal

At the end of this article series, you should be able to program an Asp.Net Core Web API in a structured and testable way using the explained techniques (design patterns). These design patterns offer a clean way to follow the Single Responsibility Principle.

Since design patterns are language-agnostic, you can use them in different applications and languages. In an Angular application, you will most likely use Dependency Injection for example.

This is one of the beauties of design patterns; they are tools to be used, not feared!

Asp.Net Core 2.0

At the time of the writing, Asp.Net Core 2.0 was still in prerelease, and I updated the code samples to use the release version.

You will need the .NET Core 2.0.0 SDK and Visual Studio 2017 update 3 or the IDE/code editor of your choosing.


Dependency Injection

If you are not familiar with dependency injection, don’t worry, we will cover the basics here. Once understood, you will see that Dependency Injection is a major game changer about how you design softwares.

Classes should not depend directly on other classes but instead one should depend upon abstractions.

That last sentence leads to Inversion of Control (IoC) which means that dependencies are managed elsewhere. Instead of creating concrete types that use other types, classes only depends on abstractions (interfaces), and dependencies (implementations) are injected.

Example of an implementation and its interface:

namespace ForEvolve.Blog.Samples.NinjaApi
{
    public class ChildService : IChildService
    {
        public void DoSomething()
        {
            throw new NotImplementedException();
        }
    }

    public interface IChildService
    {
        void DoSomething();
    }
}

In that system, no other classes should be aware of ChildService; but many could use IChildService. Only depend on abstractions (interfaces).

Composition Root

The dependencies must be managed somewhere, and implementations must be associated to abstractions. Where the dependencies are managed is called the “composition root” and should be as close as possible to the program entry point.

In an Asp.Net Core point of view, that would be in Program.cs. But, since most Asp.Net Core applications .UseStartup<Startup>() (see Program.cs), the composition root will be the ConfigureServices(IServiceCollection services) method of the Startup class.

Ok less confusion, here is the VS generated Startup.cs file content:

namespace ForEvolve.Blog.Samples.NinjaApi
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            //
            // Composition Root
            //
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseMvc();
        }
    }

}
Asp.Net Core Composition Root, see Startup.cs.

Container

While Dependency Injection and Inversion of Control are concepts, an Inversion of Control Container (or IoC Container) is the piece of software that does the magic for you. We could also refer to it as a Dependency Injection Container (or DI Container).

The container manages dependencies and their lifecycle.


We could resume the use of a Container as follow:
You register your dependencies in it, and it gives you back what you configured; when needed.


In our case, we will use the default built-in Asp.Net Core Dependency Injection framework (also known as IoC container).

Constructor injection

There are multiple ways to inject dependencies, and the best known and most recommended way is by constructor injection.

This means that instead of creating an object manually (ex.: var service = new NinjaService();) we let our DI Container inject our dependencies for us, as a constructor argument. Then, inside the class, we keep a reference on that dependency for future use (most of the time as a private readonly field).

Example:

namespace ForEvolve.Blog.Samples.NinjaApi
{
    [Route("v1/[controller]")]
    public class SuperCoolController : Controller
    {
        private readonly IChildService _childService;

        public SuperCoolController(IChildService childService)
        {
            _childService = childService ?? throw new ArgumentNullException(nameof(childService));
        }

        [HttpGet]
        public void MySuperCoolMethod()
        {
            // some code here...
            _childService.DoSomething();
            // some more code here...
        }
    }
}

In the sample above, we inject an implementation of IChildService in the SuperCoolController constructor. We also added a “guard clause” to make sure that childService is not null. Then we saved a reference in private readonly IChildService _childService; that is used in MySuperCoolMethod.


Defining a good URI convention

Defining a good URI convention is important when designing Web APIs, it is a big part of the contract between the consumers and the API.

That said, in the example below, the [Route("v1/[controller]")] attribute defines the route that will be used to reach the controller. I prefixed the route with v1 because having a version defined is a good thing. Some will say that versions don’t go into URLs, some will say they do, etc., etc. I will leave the philosophy debate out of the article and simply go for: this is the way I chose for this article series; pick the one you prefer in your APIs.

I could also have prefixed my route with api like this [Route("api/v1/[controller]")], but in this case, we know it is an API, the whole application is an API.

More on that, we could see the full URL convention as follow: [version]/[domain]/[subdomain]/[params] where subdomain and params are optional. This is a good default, in my opinion.


Guard Clause

A guard clause is nothing more than a way to ensure that our class received an implementation upon its construction: no null allowed!

Usually, if a null is passed as an argument, an ArgumentNullException is thrown to notify the system about the error.

The following line uses the new VS 2017 patterns and will be mostly the same for each one of your dependencies: _childService = childService ?? throw new ArgumentNullException(nameof(childService));.


Tools

I have created some Visual Studio snippets to help write guard clause faster. See GitHub if you are interested.


Service lifetimes

Before mapping dependencies in the Composition Root (creating the object graphs), we need some theory about “service lifetimes.”

To be quick, we can see service lifetimes in two ways (they mean the same thing):

  1. The DI Container must know for how long an instance of an object should live.
  2. The DI Container must know how often an instance of a class must be created.

Asp.Net Core lifetimes

Here are the three available Asp.Net Core lifetimes:

Service lifetimes Description
Transient Transient lifetime services are created each time they are requested. This lifetime works best for lightweight, stateless services.
Scoped Scoped lifetime services are created once per request.
Singleton Singleton lifetime services are created the first time they are requested (or when ConfigureServices is run if you specify an instance there) and then every subsequent request will use the same instance. If your application requires singleton behavior, allowing the services container to manage the service’s lifetime is recommended instead of implementing the singleton design pattern and managing your object’s lifetime in the class yourself.
Asp.Net Core service lifetimes, shamelessly copied from the official documentation.

Registration methods

There are multiple methods and extensions to register services with the Asp.Net Core DI Container. I outlined a few in the following table.


In the following table, most of the time, the TService will be the interface while the TImplementation will be the class.

Example: services.AddSingleton<ISomeService, SomeService>();


Service lifetimes Registration methods
Transient services.AddTransient<TService, TImplementation>();
Scoped services.AddScoped<TService, TImplementation>();
Singleton services.AddSingleton<TService, TImplementation>();

Tips

Use the Singleton lifetime as much as possible, it lowers the number of objects that need to be created, so its speed things up. If Singleton is not possible, see if you can go for Scoped (one instance per HTTP request). If using Singleton or Scoped is impossible, go for Transient.


Linking everything together

Now that we covered all of that, we need to map our dependencies. By using our previous examples, the registration code will look like this:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IChildService, ChildService>();
    services.AddMvc();
}

That’s it, if we visit the /v1/SuperCool URI, our browser should “display” an internal server error! That’s right, a status code 500!

It’s rare that a Web developer is happy to see a status code 500, but well, in our case, that’s because of our ChildService.DoSomething()method; it throw new NotImplementedException(); which leads to an “InternalServerError”.

The end of this article

What have we covered in this article?

We made a quick overview of Dependency injection and now, you know the basics behind it. Obviously, to master a technique you need to practice, practice, and practice even more.

So (surprise!) I recommend you to practice! Maybe update this quick example to something better. For starter replace that NotImplementedException by some useful code. Having your controller action return something could also be more exciting.

Here are the topics we covered:

  • Dependency Injection - the basics
  • The Composition Root - where we compose our object graph
  • DI Containers - the piece of software that manage dependencies for us
  • Constructor injection - the pattern to compose objects and break dependencies
  • Guard Clause - the pattern to make sure we don’t receive null services
  • Service lifetimes - the amount of time a service instance should live
  • Mapping our dependencies (creating object graphs) - us controlling how our software should work

Code recap

Before going to the “What’s next?” section, lets review the whole code:

namespace ForEvolve.Blog.Samples.NinjaApi
{
    public class ChildService : IChildService
    {
        public void DoSomething()
        {
            throw new NotImplementedException();
        }
    }

    public interface IChildService
    {
        void DoSomething();
    }

    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<IChildService, ChildService>();
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseMvc();
        }
    }

    [Route("v1/[controller]")]
    public class SuperCoolController : Controller
    {
        private readonly IChildService _childService;

        public SuperCoolController(IChildService childService)
        {
            _childService = childService ?? throw new ArgumentNullException(nameof(childService));
        }

        [HttpGet]
        public void MySuperCoolMethod()
        {
            // some code here...
            _childService.DoSomething();
            // some more code here...
        }
    }
}

What’s next?

Next, we will start writing some Ninja App code and see all that theory in practice, beginning by the ClansController.

A Ninja need a clan to fight for! (And because it is a very simple controller)


Last word (shared section)

Table of content

Article Source code
Part 1: Introduction 1. NinjaApi - Starting point
Part 2: Dependency Injection DependencyInjection sample
Part 3: Models and Controllers 3. NinjaApi - ClansControllers
Part 4: Services and the ClanService 4. NinjaApi - The ClanService
Part 5: Repositories, the ClanRepository, and integration testing 5. NinjaApi - Clans completed
Part 6: the NinjaController and the ninja sub-system 6. NinjaApi - NinjaController
Part 7: the NinjaService 7. NinjaApi - NinjaService
Part 8: Azure table storage and the data model 8. NinjaApi - NinjaEntity
Part 9: the NinjaMappingService and the Façade pattern 9. NinjaApi - NinjaMappingService
Part 10: the NinjaRepository and ForEvolve.Azure 10. NinjaApi - NinjaRepository
Part 11: Integration testing 11. NinjaApi - IntegrationTesting
More might come someday…  

Resources

Some additional resources used during the article (or not).

Articles & concepts

Tools & technologies

Code samples

Special thanks

I’d like to finish with special thanks to Emmanuel Genest who took the time to read my drafts and give me comments from a reader point of view.





Comments