3.3.2 ASP.NET Core中的依赖注入
ASP.NET Core框架内部集成了自身的依赖注入容器。相比第三方依赖注入容器,它自带的依赖注入容器并不具备第三方容器所具有的高级功能,但它仍然是功能强大、简单,而且容易使用,此外,它的效率要更高。
在ASP.NET Core中,所有被放入依赖注入容器的类型或组件称为服务。容器中的服务有两种类型:第一种是框架服务,它们是ASP.NET Core框架的组成部分,如IApplicationBuilder、IHostingEnvironment和ILoggerFactory等;另一种是应用服务,所有由用户放到容器中的服务都属于这一类。
为了能够在程序中使用服务,首先需要向容器添加服务,然后通过构造函数以注入的方式注入所需要的类中。若要添加服务,则需要使用Startup类的ConfigureServices方法,该方法有一个IServiceCollection类型的参数,它位于Microsoft.Extensions.DependencyInjection命名空间下,如下所示。
public void ConfigureServices(IServiceCollection services) { services.Add(new ServiceDescriptor(typeof(IBook), typeof(Book), ServiceLifetime. Scoped)); }
在上例中,使用了IServiceCollection的Add方法添加了一个ServiceDescriptor对象,事实上,IServiceCollection就是一个ServiceDescriptor类型的集合,它继承自Icollection <ServiceDescriptor>类。ServiceDescriptor类描述一个服务和它的实现,以及其生命周期,正如上例中的构造函数所表明的,前两个参数分别是接口及其实现的类型,而第3个参数则是指明它的生命周期。
在ASP.NET Core内置的依赖注入容器中,服务的生命周期有如下3种类型。
Singleton:容器会创建并共享服务的单例,且一直会存在于应用程序的整个生命周 期内。
Transient:每次服务被请求时,总会创建新实例。
Scoped:在每一次请求时会创建服务的新实例,并在这个请求内一直共享这个实例。
当每次在容器中添加服务时,都需要指明其生命周期类型。当服务的生命周期结束时,它就会被销毁。
ServiceDescriptor除了上面的构造函数以外,还有以下两种形式:
public ServiceDescriptor(Type serviceType, object instance); public ServiceDescriptor(Type serviceType, Func<IServiceProvider, object> factory, ServiceLifetime lifetime);
其中,第一种形式可以直接指定一个实例化的对象,使用这种方式,服务的生命周期将是Singleton,而第二种形式则是以工厂的方式来创建实例,以满足更复杂的创建要求。
除了直接调用Add方法外,IServiceCollection还提供了分别对应以上3种类型生命周期的扩展方法:AddSingleton()、AddTransient()和AddScoped()。
因此,上面添加服务可以修改为这种方式:
services.AddScoped(typeof(IBook), typeof(Book));
或者可以修改为更简单的这种方式:
services.AddScoped<IBook, Book>();
对于一些常用的服务,如MVC、Entity Framework Core的DbContext等,IServiceCollection也提供了相应的扩展方法,能够将对应的服务更方便地添加到容器中,如AddMvc、AddDbContext和AddOptions等。
当服务添加到容器中后,就可以在程序中使用了,例如在Controller中或Startup类的Configure方法中。使用的方式有以下几种:构造函数注入、方法注入和通过HttpContext手工获取。此外,还可以通过IApplicationBuilder接口的ApplicationServices属性来访问服务,此属性的类型为IServiceProvider。另外,需要注意的是,ASP.NET Core内置的容器默认支持构造函数注入,它不支持属性注入。
在3.2.3节介绍的自定义中间件中,我们已经看到了构造函数注入的使用。构造函数注入更为常见的情况是在MVC的Controller中。
public class HomeController : Controller { private readonly IDataService_dataService; public HomeController(IDataService dataService) { _dataService = dataService; } [HttpGet] public IActionResult Index([FromServices] IDataService dataService2) { IDataService dataService = HttpContext.RequestServices. GetService <IDataService>(); … return View(); } }
在上述代码中,除了使用构造函数注入外,在Index方法中,也使用了方法注入,通过[FromServices]特性告诉当前Action该参数应从容器中获取。
构造函数注入与方法注入都能够正确地将所需要的依赖注入进来,那么它们各自用在什么场合呢?如果一个服务仅在一个方法内使用,应使用方法注入;反之,如果一个类的多个方法都会用到某个服务,则应该使用构造函数注入。
最后,还可以通过HttpContext对象的RequestServices属性来获取服务,示例如下。
public class HttpMethodCheckMiddleware { private readonly RequestDelegate_next; public HttpMethodCheckMiddleware(RequestDelegate requestDelegate, IHostingEnvironment environment) { this._next = requestDelegate; } public Task Invoke(HttpContext context) { IDataService dataService = context.RequestServices.GetService<IDataService>(); … } }
RequestServices属性的类型同样是IServiceProvider,它包括GetService()、GetRequiredService()以及它们各自的泛型重载。GetService()和GetRequiredService()的区别是当容器中不存在指定类型的服务时,前者会返回null,而后者则会抛出InvalidOperationException异常。