依赖注入一直是asp.net web框架(Web API,SignalR and MVC)中不可或缺的一部分,但是在以前,这个框架都是各自升级,都有各自的依赖注入实现方式,即使Katana项目想通过Owin将这些项目连接起来,你还是要做统一的容器去支持它们,现在事情有所变化了。
抽象
asp.net团队决定提供依赖注入功能通过提炼最流行的IOC容器中最核心的功能,然后让不同的组件实现这些接口来实现依赖注入。
IServiceProvider
这个最主要的接口,开发都可以通过这个接口去检索Ioc容器中已经注册过的服务,这个接口只有一个方法而已:GetService(Type), 类似于Aotufact里的Container.Resolve<service>,或者Ninject里的Kernel.Get<service>.
所有的MiddleWare有两种方式可以获得所要的IserviceProvider.
- 应用程序级别:通过HttpContext.ApplicationServices 属性提供给 MiddleWare
- 请求级别:通过HttpContext.RequestServices属性提供给MiddleWare,这个范围的ServiceProvider通过一个隐式的MIddleWare在最开始的请求管理道中被创建,在请求最后返回响应前被释放。
全部的MiddleWare可通过这些属性去解决他们的服务,例如ASP.NET MVC MIddleWare会通过RequestServices去创建Controllers和他们的依赖
IServiceScope
这个接口是容器的一个包装,主要作用是在请求结束后去释放容器
- IServiceProvider
- Dispose()
IServiceScopeFactory
很简单的一个接口,只有一个返回IServiceScope的方法CreateServiceScope()
所以如果你需要实现一个IOC 容器,就要自己实现以上的接口。
ServiceLifetime
- Singleton 单个实例在整个应用程序中
- Scoped 单个实例在范围窗口内
ServiceDescriptor
注册服务的描述信息,
- ServiceType 用来替换具体实现类的接口,Type类型
- ImplementationType 上面这个接口的具体实现类型,Type类型
- Lifetime 服务的生命周期,Singleon,Scoped 或者 Transient
- ImplementaionFactory 类型Func<IServiceProvider,Object>, 在一些场景中,开发人员希望提供一个工厂方法去创建具体实现类。他们之间是互相独立的,如果你提供了ImplementationType,那不能再提供ImpletentaionFactory.
- ImplementationInstance 具体事例
Registering Services
现在注册您的服务,ASPNET5希望您 的Startup类中有个叫ConfigureServices的方法名,带有一系列ServiceDescriptors包裹在IServiceCollection,并什么都不返回。你全部只要做的只是创建一系列ServiceDescriptor,并添加他们到集合,web应用程序会在稍后加载他们并注册到容器中
publicvoidConfigureServices(IServiceCollection services) { var serviceDescriptor = newServiceDescriptor(typeof(IBankManager), typeof(BankManager), ServiceLifetime.Transient); services.Add(serviceDescriptor); // Add MVC services to the services container. services.AddMvc(); }
下面的伪陈述说明服务启动和ServiceProvider是怎样创建的,相应的代码可以在HostingEngine.Start中找到
应用程序是如何启动的。
- Hosting engine 会创建一个IServiceCollection,是ServiceDescriptor的集合对象
- Hosting engine 会添加全部它所需要的服务
- Hosting engine 会确认在程序集中有个Startup类,并有个ConfigureServices方法
- Hosting engine 会去加载这个方法,并传递IServiceCollection
- Startup类中的ConfigureSerivces会添加应用程序需要的服务至集合中
- Hosting engine 接着会创建 DefaultServiceProvider(ICO容器),并注册服务集合中IServiceCollection中的服务
- Hosting engine 会创建一个应用程序构建器(IApplicationBuilder)并给IApplicationBuilder.ApplicationServices分配一,个新的Service Provider,并进一步的使用它
- Hosting engin 在Startup.Configuer执行前会创建一个RequestServicesContainerMiddleware中间件,我会在稍后介绍它
- Hosting engine 会执行Startup类中 Configure方法,并传递Application Builder去创建中间件。如果需要Service Provider可通过ApplicationServices属性去创建中间件。
运行请求
当第一个请求过来时,Httpcontext会被创建并传给第一个Middleware中的Invokde方法中,并会一直传到整个Middleware.但是在处理第一个中间件之前,Application Builder 's Service Provider 会被分配到HttpContext.ApplicationServices,确保整个中间件都可以通过这个属性获得需要的服务,不过,你应该记住这是一个应用程序级别的服务提供者,并且依赖于你选择的Ioc容器,如果你使用了它,你的对象可能会一直驻留在应用程序整个生活周期中.
Note: in theory, 理论上,作为一个应用开发人员,你不应该直接去使用这个服务提供者,如果要用建议用Service Locator 模式
好吧,这是一个应用程序级别的服务提供者,那有没有每次请求范围里的的服务提供者?
在上面列出来的第8步中,我们提到了Hosting engine 会在管道开始之前会创建一个RequestServicesContainerMiddleware中间件,并会第一时间去执行它
public async Task Invoke(HttpContext httpContext) { using(varcontainer = RequestServicesContainer.EnsureRequestServices(httpContext, _services)) { await_next.Invoke(httpContext); } }
回到上面的请求执行中,服务会创建Httpcontext,并分派给 Application-level Service Provider给HttpContext.ApplicationServices,接着会调用第一个中间件,就是上面的RequestServicesContainerMiddleware,它所做的事情就是创建一个范围内的服务提供者并在请求结束时销毁它
它的伪实现是:
- 请求正在被RequestServiceContainerMiddleware处理
- Invoke方法会通过application-level Service Provider创建一个IServiceScopeFactory.
- IServiceScopeFactory会创建一个范围容器(scoped container)
- scoped container会被指派给属性HttpContext.RequestServices
- Invoke方法调用接下来的Middleware
- 当全部的Middleware都被执行完后,并返回到了RequestServiceContainerMiddleware,Scoped Container会被销毁通过"Using"
Note: RequestServicesContainerMiddleware是通过这个包装/帮助类RequestServicesContainer 去管理创建和销毁Scoped Service Provider.
HttpContext.RequestServices是请求生命周期的范围服务提供者,后来的所有Middle都可以访问它,例如,如果你查看MvcRouteHandler.InvokeActionAsync你会发现它就是通过这个去创建Controller的
private async Task InvokeActionAsync(RouteContext context, ActionDescriptor actionDescriptor) { var services = context.HttpContext.RequestServices; Debug.Assert(services != null); var actionContext = new ActionContext(context.HttpContext, context.RouteData, actionDescriptor); var optionsAccessor = services.GetRequiredService<IOptions<MvcOptions>>(); actionContext.ModelState.MaxAllowedErrors = optionsAccessor.Options.MaxModelValidationErrors; var contextAccessor = services.GetRequiredService<IScopedInstance<ActionContext>>(); contextAccessor.Value = actionContext; var invokerFactory = services.GetRequiredService<IActionInvokerFactory>(); var invoker = invokerFactory.CreateInvoker(actionContext); if (invoker == null) { LogActionSelection(actionSelected: true, actionInvoked: false, handled: context.IsHandled); throw new InvalidOperationException( Resources.FormatActionInvokerFactory_CouldNotCreateInvoker( actionDescriptor.DisplayName)); } await invoker.InvokeAsync(); }