来源https://docs.asp.net/en/latest/fundamentals/dependency-injection.html
ASP.NET Core 1.0在设计上原生就支持和有效利用依赖注入。在Startup类中,应用可以通过将框架内嵌服务注入到方法中来使用他们;另一方面,你也可以配置服务来注入使用。默认的服务容器只提供了最小的特性集合,所以并不打算取代其他的IoC容器。
依赖注入是为了达到解耦对象和其依赖的一项技术。一个类为了完成自身某些操作所需的对象是通过某种方式提供的,而不是使用静态引用或者直接实例化。通常情况下,类通过构造器来声明其依赖,遵循显式依赖原则。这种方式称作构造器注入。
当以DI思想来设计类时,这些类更加松耦合,因为他们不直接硬编码的依赖其合作者。这遵循了依赖倒置原则,即高层模块不应依赖底层模块,两者都应依赖抽象。类在构建时所需是抽象(如接口interface),而不是具体的实现。把依赖抽离成接口,把这些接口的实现作为参数也是策略设计模式的例子。
当一个系统使用DI来设计时,很多类通过构造器或者属性来添加依赖,这样就很方便有一个专门的类来创建这些类以及他们相关的依赖。这样的类称之为“容器”或者“IoC容器”或“DI容器”。一个容器本质上是一个工厂,来给请求者提供类型实例。如果给定类型声明了自身依赖,容器也配置了来提供这些依赖类型,那么它会创建这些依赖作为请求实例的一部分。通过这种方式可以为 类提供复杂的依赖图,而不需要任何硬编码的对象依赖。除了创建依赖对象外,容器一般还管理应用内的对象生命周期。
ASP.NET Core 1.0提供了一个简单的内置容器(以IServiceProvider为代表),默认支持构造器注入,这样ASP.NET可以通过DI使某些服务可用。ASP.NET把它所管理的类型称之为服务。本文的剩下部分,服务即指ASP.NET IoC容器所管理的类型。你可以在Startup类中的ConfigureServices 方法中配置内置的容器服务。
Note: Martin Fowler写过一篇很详细的依赖反转的文章。微软对此也有很棒的描述连接。
ConfigureServices方法负责定义应用使用的服务,包括平台特性服务如EF和ASP.NET MVC。最初提供给ConfigureServices的IServiceCollection只有少数服务。默认web模板提供了怎么通过扩展方法来添加额外服务到容器的例子,如AddEntityFramework, AddIdentity, 和AddMVC。
public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddEntityFramework() .AddSqlServer() .AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"])); services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); services.AddMvc(); // Add application services. services.AddTransient<IEmailSender, AuthMessageSender>(); services.AddTransient<ISmsSender, AuthMessageSender>(); }ASP.NET提供的特性和中间件遵循使用一个AddService扩展方法的约定,来注册该特性使用的所需的所有服务。
Note:你可以在Startup方法中请求某些framework-provided服务,详见应用启动Application Startup
当然,除了配置框架提供的各种服务,你也可以配置自己定义的服务。
在默认web模板中,有如下两个服务被添加到IServiceCollection中
// Add application services. services.AddTransient<IEmailSender, AuthMessageSender>(); services.AddTransient<ISmsSender, AuthMessageSender>();AddTransient方法将抽象类型映射为实体服务,对于每个请求这都单独实例化,这称作服务的生命周期。额外生命周期选项如下。对于每个注册的服务选择合适的生命周期是很重要的。是对每个请求类都提供一个新的实例化服务?还是在给定web请求内只实例化一次?还是在应用周期内只有单例?
在本文的例子中,有个简单的CharacterController来显示Character姓名,在Index方法中显示已存储的Character(如果没有则创建)。虽然注册了EF服务,但本例持久化没有使用数据库。具体的数据获取服务抽象到了ICharacterRepository接口实现中,这遵从了仓储模式。在构造器中请求ICharacterRepository参数,并将其赋给私有变量,来根据需要获取Character。
using System.Linq; using DependencyInjectionSample.Interfaces; using DependencyInjectionSample.Models; using Microsoft.AspNet.Mvc; namespace DependencyInjectionSample.Controllers { public class CharactersController : Controller { private readonly ICharacterRepository _characterRepository; public CharactersController(ICharacterRepository characterRepository) { _characterRepository = characterRepository; } // GET: /characters/ public IActionResult Index() { var characters = _characterRepository.ListAll(); if (!characters.Any()) { _characterRepository.Add(new Character("Darth Maul")); _characterRepository.Add(new Character("Darth Vader")); _characterRepository.Add(new Character("Yoda")); _characterRepository.Add(new Character("Mace Windu")); characters = _characterRepository.ListAll(); } return View(characters); } }接口ICharacterRepository只简单定义了两个方法,Controller通过其来操作Charcter实例。
using System.Collections.Generic; using DependencyInjectionSample.Models; namespace DependencyInjectionSample.Interfaces { public interface ICharacterRepository { IEnumerable<Character> ListAll(); void Add(Character character); } }接口有具体类型CharacterRepository来实现,在运行时被使用。
Note: CharacterRepository类只是使用DI的普通例子,你可以对应用所有的服务使用DI,而不仅仅是“仓储”和数据获取类。
请注意,CharacterRepository在其构造器中请求了ApplicationDbContext实例。这种链式的依赖注入是很常见的,被依赖本身又有自己的依赖。容器来负责以树形的方式来解析所有这些依赖,并返回解析完成的服务。
Note: 创建请求对象,以及其依赖,其依赖的依赖,有时这被称之为对象图。同样的,需要解析的对象集合称之为依赖树或者依赖图。