services.AddMvc().AddControllersAsServices(); //或其他方式,这取决于你构建的Web项目的用途可以是WebApi、Mvc、RazorPage等 //services.AddMvcCore().AddControllersAsServices();
相信大家都看到了,玄机就在AddControllersAsServices方法中,但是它存在于MvcCoreMvcBuilderExtensions类和MvcCoreMvcCoreBuilderExtensions类中,不过问题不大,因为它们的代码是完全一样的。只是因为你可以通过多种方式构建Web项目比如AddMvc或者AddMvcCore,废话不多说直接上代码[]
public static IMvcBuilder AddControllersAsServices(this IMvcBuilder builder) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } var feature = new ControllerFeature(); builder.PartManager.PopulateFeature(feature); //第一将Controller实例添加到IOC容器中 foreach (var controller in feature.Controllers.Select(c => c.AsType())) { //注册的声明周期是Transient builder.Services.TryAddTransient(controller, controller); } //第二替换掉原本DefaultControllerActivator的为ServiceBasedControllerActivator builder.Services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>()); return builder; }
第一点没问题那就是将Controller实例添加到IOC容器中,第二点它替换掉了DefaultControllerActivator为为ServiceBasedControllerActivator。通过上面我们讲述的源码了解到DefaultControllerActivator是默认提供Controller实例的地方是获取Controller实例的核心所在,那么我们看看ServiceBasedControllerActivator与DefaultControllerActivator到底有何不同,直接贴出代码[点击查看源码]
public class ServiceBasedControllerActivator : IControllerActivator { public object Create(ControllerContext actionContext) { if (actionContext == null) { throw new ArgumentNullException(nameof(actionContext)); } //获取Controller类型 var controllerType = actionContext.ActionDescriptor.ControllerTypeInfo.AsType(); //通过Controller类型在容器中获取实例 return actionContext.HttpContext.RequestServices.GetRequiredService(controllerType); } public virtual void Release(ControllerContext context, object controller) { } }
相信大家对上面的代码一目了然了,和我们上面描述的一样,将创建Controller实例的地方改造了在容器中获取的方式。不知道大家有没有注意到ServiceBasedControllerActivator的Release的方法居然没有实现,这并不是我没有粘贴出来,确实是没有代码,之前我们看到的DefaultControllerActivator可是有调用Controller的Disposed的方法,这里却啥也没有。相信聪明的你已经想到了,因为Controller已经托管到了IOC容器中,所以他的生命及其相关释放都是由IOC容器完成的,所以这里不需要任何操作。
我们上面还看到了注册Controller实例的时候使用的是TryAddTransient方法,也就是说每次都会创建Controller实例,至于为什么,我想大概是因为每次请求都其实只会需要一个Controller实例,况且EFCore的注册方式官方建议也是Scope的,而这里的Scope正是对应的一次Controller请求。在加上自带的IOC会提升依赖类型的声明周期,如果将Controller注册为单例的话如果使用了EFCore那么它也会被提升为单例,这样会存在很大的问题。也许正是基于这个原因默认才将Controller注册为Transient类型的,当然这并不代表只能注册为Transient类型的,如果你不使用类似EFCore这种需要作用域为Scope的服务的时候,而且保证使用的主键都可以使用单例的话,完全可以将Controller注册为别的生命周期,当然这种方式个人不是很建议。
Controller结合Autofac有时候大家可能会结合Autofac一起使用,Autofac确实是一款非常优秀的IOC框架,它它支持属性和构造两种方式注入,关于Autofac托管自带IOC的原理咱们在之前的文章浅谈.Net Core DependencyInjection源码探究中曾详细的讲解过,这里咱们就不过多的描述了,咱们今天要说的是Autofac和Controller的结合。如果你想保持和原有的IOC一致的使用习惯,即只使用构造注入的话,你只需要完成两步即可
首先将默认的IOC容器替换为Autofac,具体操作也非常简单,如下所示
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }) //只需要在这里设置ServiceProviderFactory为AutofacServiceProviderFactory即可 .UseServiceProviderFactory(new AutofacServiceProviderFactory());
然后就是咱们之前说的,要将Controller放入容器中,然后修改生产Controller实例的ControllerFactory的操作为在容器中获取,当然这一步微软已经为我们封装了便捷的方法
services.AddMvc().AddControllersAsServices();