DependencyInjection.md - brainchildservices/curriculum GitHub Wiki

SLIDE-1

Dependency injection in ASP.NET Core

SLIDE-2

Dependency injection (services)

ASP.NET Core includes a built-in dependency injection (DI) framework that makes configured services available throughout an app. For example, a logging component is a service.

Code to configure (or register) services is added to the Startup.ConfigureServices method. For example:

 public void ConfigureServices(IServiceCollection services)
 {
     services.AddDbContext<RazorPagesMovieContext>(options =>
         options.UseSqlServer(Configuration.GetConnectionString("RazorPagesMovieContext")));

     services.AddControllersWithViews();
     services.AddRazorPages();
 }

Services are typically resolved from DI using constructor injection. With constructor injection, a class declares a constructor parameter of either the required type or an interface. The DI framework provides an instance of this service at runtime.

SLIDE-3

The following example uses constructor injection to resolve a RazorPagesMovieContext from DI:

 public class IndexModel : PageModel
 {
     private readonly RazorPagesMovieContext _context;

     public IndexModel(RazorPagesMovieContext context)
     {
         _context = context;
     }

     // ...

     public async Task OnGetAsync()
     {
         Movies = await _context.Movies.ToListAsync();
     }
 }

SLIDE-4

If the built-in Inversion of Control (IoC) container doesn't meet all of an app's needs, a third-party IoC container can be used instead.

ASP.NET Core supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies.

For more information specific to dependency injection within MVC controllers, see Dependency injection into controllers in ASP.NET Core.

For information on using dependency injection in applications other than web apps, see Dependency injection in .NET.

For more information on dependency injection of options, see Options pattern in ASP.NET Core.

This topic provides information on dependency injection in ASP.NET Core. The primary documentation on using dependency injection is contained in Dependency injection in .NET.

View or download sample code (how to download)

SLIDE-5

Overview of dependency injection

A dependency is an object that another object depends on. Examine the following MyDependency class with a WriteMessage method that other classes depend on:

  public class MyDependency
  {
      public void WriteMessage(string message)
      {
          Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
      }
  }

A class can create an instance of the MyDependency class to make use of its WriteMessage method. In the following example, the MyDependency class is a dependency of the IndexModel class:

  public class IndexModel : PageModel
  {
      private readonly MyDependency _dependency = new MyDependency();

      public void OnGet()
      {
          _dependency.WriteMessage("IndexModel.OnGet created this message.");
      }
  }

SLIDE-6

The class creates and directly depends on the MyDependency class. Code dependencies, such as in the previous example, are problematic and should be avoided for the following reasons:

  • To replace MyDependency with a different implementation, the IndexModel class must be modified.
  • If MyDependency has dependencies, they must also be configured by the IndexModel class. In a large project with multiple classes depending on MyDependency, the configuration code becomes scattered across the app.
  • This implementation is difficult to unit test. The app should use a mock or stub MyDependency class, which isn't possible with this approach.

Dependency injection addresses these problems through:

  • The use of an interface or base class to abstract the dependency implementation.
  • Registration of the dependency in a service container. ASP.NET Core provides a built-in service container, IServiceProvider. Services are typically registered in the app's Startup.ConfigureServices method.
  • Injection of the service into the constructor of the class where it's used. The framework takes on the responsibility of creating an instance of the dependency and disposing of it when it's no longer needed.

SLIDE-6(DOWNWARDS)

In the sample app, the IMyDependency interface defines the WriteMessage method:

  public interface IMyDependency
  {
      void WriteMessage(string message);
  }

This interface is implemented by a concrete type, MyDependency:

  public class MyDependency : IMyDependency
  {
      public void WriteMessage(string message)
      {
          Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
      }
  }

SLIDE-7

The sample app registers the IMyDependency service with the concrete type MyDependency. The AddScoped method registers the service with a scoped lifetime, the lifetime of a single request. Service lifetimes are described later in this topic.

  public void ConfigureServices(IServiceCollection services)
  {
      services.AddScoped<IMyDependency, MyDependency>();

      services.AddRazorPages();
  }

In the sample app, the IMyDependency service is requested and used to call the WriteMessage method:

  public class Index2Model : PageModel
  {
      private readonly IMyDependency _myDependency;

      public Index2Model(IMyDependency myDependency)
      {
          _myDependency = myDependency;            
      }

      public void OnGet()
      {
          _myDependency.WriteMessage("Index2Model.OnGet");
      }
  }

SLIDE-8

By using the DI pattern, the controller:

  • Doesn't use the concrete type MyDependency, only the IMyDependency interface it implements. That makes it easy to change the implementation that the controller uses without modifying the controller.
  • Doesn't create an instance of MyDependency, it's created by the DI container.

The implementation of the IMyDependency interface can be improved by using the built-in logging API:

  public class MyDependency2 : IMyDependency
  {
      private readonly ILogger<MyDependency2> _logger;

      public MyDependency2(ILogger<MyDependency2> logger)
      {
          _logger = logger;
      }

      public void WriteMessage(string message)
      {
          _logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
      }
  }

The updated ConfigureServices method registers the new IMyDependency implementation:

  public void ConfigureServices(IServiceCollection services)
  {
      services.AddScoped<IMyDependency, MyDependency2>();

      services.AddRazorPages();
  }

SLIDE-9

MyDependency2 depends on ILogger, which it requests in the constructor. ILogger<TCategoryName> is a framework-provided service.

It's not unusual to use dependency injection in a chained fashion. Each requested dependency in turn requests its own dependencies. The container resolves the dependencies in the graph and returns the fully resolved service. The collective set of dependencies that must be resolved is typically referred to as a dependency tree, dependency graph, or object graph.

The container resolves ILogger<TCategoryName> by taking advantage of (generic) open types, eliminating the need to register every (generic) constructed type.

In dependency injection terminology, a service:

  • Is typically an object that provides a service to other objects, such as the IMyDependency service.
  • Is not related to a web service, although the service may use a web service.

The framework provides a robust logging system. The IMyDependency implementations shown in the preceding examples were written to demonstrate basic DI, not to implement logging. Most apps shouldn't need to write loggers. The following code demonstrates using the default logging, which doesn't require any services to be registered in ConfigureServices:

  public class AboutModel : PageModel
  {
      private readonly ILogger _logger;

      public AboutModel(ILogger<AboutModel> logger)
      {
          _logger = logger;
      }

      public string Message { get; set; }

      public void OnGet()
      {
          Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
          _logger.LogInformation(Message);
      }
  }

Using the preceding code, there is no need to update ConfigureServices, because logging is provided by the framework.

SLIDE-10

Services injected into Startup

Services can be injected into the Startup constructor and the Startup.Configure method.

Only the following services can be injected into the Startup constructor when using the Generic Host (IHostBuilder):

⚠️ **GitHub.com Fallback** ⚠️