ASP.NET Core与RESTful API 开发实战
上QQ阅读APP看书,第一时间看更新

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异常。