在前面的章节(Middleware章节)中,我们提到了依赖注入功能(Dependency Injection),ASP.NET 5正式将依赖注入进行了全功能的实现,以便开发人员能够开发更具弹性的组件程序,MVC6也利用了依赖注入的功能重新对Controller和View的服务注入功能进行了重新设计;未来的依赖注入功能还可能提供更多的API,所有如果还没有开始接触依赖注入的话,就得好好学一下了。
在之前版本的依赖注入功能里,依赖注入的入口有MVC中的IControllerFactory
和Web API中的IHttpControllerActivator
中,在新版ASP.NET5中,依赖注入变成了最底层的基础支撑,MVC、Routing、SignalR、Entity Framrwork等都依赖于依赖注入的IServicePRovider
接口,针对该接口微软给出了默认的实现ServiceProvider
,以及Ninject和AutoFac版本的包装,当然你也可以使用其它第三方的依赖注入容器,如Castle Windsor等;一旦应用了第三方容器,所有的依赖解析都会被路由到该第三方容器上。
针对通用的依赖类型的解析与创建,微软默认定义了4种类别的生命周期,分别如下:
类型 | 描述 |
---|---|
Instance | 任何时间都只能使用特定的实例对象,开发人员需要负责该对象的初始化工作。 |
Transient | 每次都重新创建一个实例。 |
Singleton | 创建一个单例,以后每次调用的时候都返回该单例对象。 |
Scoped | 在当前作用域内,不管调用多少次,都是一个实例,换了作用域就会再次创建实例,类似于特定作用内的单例。 |
依赖注入类型的注册一般是在程序启动的入口中,如Startup.cs中的ConfigureServices中,该类的主要目的就是注册依赖注入的类型。由于依赖注入的主要体现是接口编程,所以本例中,我以接口和实现类的方式来举例。
首先声明一个接口ITodoRepository和实现类TodoRepository1,代码如下:
public interface ITodoRepository{ IEnumerable<TodoItem> AllItems { get; } void Add(TodoItem item); TodoItem GetById(int id); bool TryDelete(int id);}public class TodoItem{ public int Id { get; set; } public string Name { get; set; }}public class TodoRepository : ITodoRepository{ readonly List<TodoItem> _items = new List<TodoItem>(); public IEnumerable<TodoItem> AllItems { get { return _items; } } public TodoItem GetById(int id) { return _items.FirstOrDefault(x => x.Id == id); } public void Add(TodoItem item) { item.Id = 1 + _items.Max(x => (int?)x.Id) ?? 0; _items.Add(item); } public bool TryDelete(int id) { var item = GetById(id); if (item == null) { return false; } _items.Remove(item); return true; }}
为了演示不同的声明周期类型,建议多实现几个类,比如TodoRepository2、TodoRepository3、TodoRepository4等,以便进行演示。
然后在ConfigureServices方法内注册接口ITodoRepository类型和对应的实现类,本例中根据不同的生命周期注册了不同的实现类,具体示例如下:
//注册单例模式,整个应用程序周期内ITodoRepository接口的示例都是TodoRepository1的一个单例实例services.AddSingleton<ITodoRepository, TodoRepository1>();services.AddSingleton(typeof(ITodoRepository), typeof(TodoRepository1)); // 等价形式//注册特定实例模型,整个应用程序周期内ITodoRepository接口的示例都是固定初始化好的一个单例实例TodoRepository2services.AddInstance<ITodoRepository>(new TodoRepository2());services.AddInstance(typeof(ITodoRepository), new TodoRepository2()); // 等价形式//注册作用域型的类型,在特定作用域内ITodoRepository的示例是TodoRepository3services.AddScoped<ITodoRepository, TodoRepository3>();services.AddScoped(typeof(ITodoRepository), typeof(TodoRepository3));// 等价形式//获取该ITodoRepository实例时,每次都要实例化一次TodoRepository4类services.AddTransient<ITodoRepository, TodoRepository4>();services.AddTransient(typeof(ITodoRepository), typeof(TodoRepository));// 等价形式//如果要注入的类没有接口,那你可以直接注入自身类型,比如:services.AddTransient<LoggingHelper>();
依赖注入的在MVC中的使用方式目前有三种,分别是Controller的构造函数、属性以及View中的Inject形式。其中构造函数注入和之前的MVC中的是一样的,示例代码如下:
public class TodoController : Controller{ private readonly ITodoRepository _repository; /// 依赖注入框架会自动找到ITodoRepository实现类的示例,赋值给该构造函数 public TodoController(ITodoRepository repository) { _repository = repository; } [HttpGet] public IEnumerable<TodoItem> GetAll() { return _repository.AllItems; //这里就可以使用该对象了 }}
属性注入,则是通过在属性上加一个[Activate]
属性即可实现自动获取实例。
public class TodoController : Controller{ // 依赖注入框架会自动找到ITodoRepository实现类的示例,赋值给该属性 [Activate] public ITodoRepository Repository { get; set; } [HttpGet] public IEnumerable<TodoItem> GetAll() { return Repository.AllItems; }}
注意:这种方式,目前只适用于Controller以及子类,不适用于普通类同时:通过这种方式,你可以获取到更多的系统实例对象,如
ActionContext
、HttpContext
、HttpRequest
、HttpResponse
、ViewDataDictionary
、以及ActionBindingContext
。
在视图中,则可以通过@inject
关键字来实现注入类型的实例提取,示例如下:
@using Webapplication1@inject ITodoRepository repository<div> @repository.AllItems.Count()</div>
而最一般的使用方式,则是获取IServiceProvider
的实例,获取该IServiceProvider
实例的方式目前有如下几种(但范围不同):
var provider1 = this.Request.HttpContext.ApplicationServices; 当前应用程序里注册的Servicevar provider2 = Context.RequestServices; // Controller中,当前请求作用域内注册的Servicevar provider3 = Resolver; //Controller中
然后通过GetService和GetRequiredService方法来获取指定类型的实例,示例如下:
var _repository1 = provider1.GetService(typeof(ITodoRepository));var _repository2 = provider1.GetService<LoggingHelper>();//等价形式//上述2个对象可能为空var _repository3 = provider1.GetRequiredService(typeof(ITodoRepository));var _repository4 = provider1.GetRequiredService<LoggingHelper>();//等价形式//上述2个对象肯定不为空,因为如果为空的话,会自动抛异常出来
在新版的ASP.NET5中,不仅支持上面我们所说的接口类的依赖注入,还支持普通的类型的依赖注入,比如我们生命一个普通类,示例如下:
public class AppSettings{ public string SiteTitle { get; set; }}
上述普通类要保证有无参数构造函数,那么注册的用法,就应该像如下这样:
services.Configure<AppSettings>(app =>{ app.SiteTitle = "111";});
使用的时候,则需要获取IOptions<AppSettings>
类型的实例,然后其Options属性即是AppSettings的实例,代码如下:
var appSettings = app.ApplicationServices.GetRequiredService<IOptions<AppSettings>>().Options;
当然,我们也可以在视图中,使用@inject
语法来获取实例,示例代码如下:
@inject IOptions<AppSettings> AppSettings<title>@AppSettings.Options.SiteTitle</title>
基于Scope作用域的实例在创建的时候需要先创建作用域,然后在该作用域内再获取特定的实例,我们看看一个示例并对其进行验证。首先,注册依赖注入类型,代码如下:
services.AddScoped<ITodoRepository, TodoRepository>();
然后创建作用域,并在该作用域内获取实例:
var serviceProvider = Resolver;var scopeFactory = serviceProvider.GetService<IServiceScopeFactory>(); //获取Scope工厂类using (var scope = scopeFactory.CreateScope()) // 创建一个Scope作用域{ var containerScopedService = serviceProvider.GetService<ITodoRepository>(); //获取普通的实例 var scopedService1 = scope.ServiceProvider.GetService<ITodoRepository>(); //获取当前Scope的实例 Thread.Sleep(200); var scopedService2 = scope.ServiceProvider.GetService<ITodoRepository>(); //获取当前Scope的实例 Console.WriteLine(containerScopedService == scopedService1); // 输出:False Console.WriteLine(scopedService1 == scopedService2); //输出:True}
另外,Scope也可以进行嵌套,嵌套的内外作用域所获取的实例也是不相同的,实例代码如下:
var serviceProvider = Resolver;var outerScopeFactory = serviceProvider.GetService<IServiceScopeFactory>();using (var outerScope = outerScopeFactory.CreateScope()) //外部Scope作用域{ var innerScopeFactory = outerScope.ServiceProvider.GetService<IServiceScopeFactory>(); using (var innerScope = innerScopeFactory.CreateScope()) //内部Scope作用域 { var outerScopedService = outerScope.ServiceProvider.GetService<ITodoRepository>(); var innerScopedService = innerScope.ServiceProvider.GetService<ITodoRepository>(); Console.WriteLine(outerScopedService == innerScopedService); // 输出:False }}
在之前很多流行的DI容器中,针对每个请求,在该请求作用域内保留一个单实例对象是很流行的,也就是在每次请求期间一个类型的对象实例只会创建一次,这样可以大大提高性能。
在ASP.NET5中,基于HTTP请求的Scope依赖注入
新闻热点
疑难解答