core控制器属性注入的用处_了解ASP.NET Core 依赖注入,看这篇就够了
DI在.NET Core里面被提到了一個非常重要的位置, 這篇文章主要再給大家普及一下關于依賴注入的概念,身邊有工作六七年的同事還個東西搞不清楚。另外再介紹一下.NET? Core的DI實現以及對實例生命周期的管理(這個是經常面試會問到的問題)。最后再給大家簡單介紹一下在控制臺以及Mvc下如何使用DI,以及如何把默認的Service Container?替換成Autofac。
一、什么是依賴注入
1.1 依賴
1.2 什么注入
為什么反轉
何為容器
二、.NET Core DI
2.1 實例的注冊
2.2 實例生命周期之單例
2.3 實例生命周期之Tranisent
2.4 實例生命周期之Scoped
三、DI在ASP.NET Core中的應用
3.1 在Startup類中初始化
3.2 Controller中使用
3.3 View中使用
3.4 通過HttpContext來獲取
四、如何替換其它的Ioc容器
一、什么是依賴注入(Denpendency Injection)
這也是個老身常談的問題,到底依賴注入是什么? 為什么要用它? 初學者特別容易對控制反轉IOC(Iversion of Control),DI等概念搞暈。
1.1依賴
當一個類需要另一個類協作來完成工作的時候就產生了依賴。比如我們在AccountController這個控制器需要完成和用戶相關的注冊、登錄 等事情。其中的登錄我們由EF結合Idnetity來完成,所以我們封裝了一個EFLoginService。這里AccountController就有一個ILoginService的依賴。
這里有一個設計原則:依賴于抽象,而不是具體的實現。所以我們給EFLoginService定義了一個接口,抽象了LoginService的行為。
1.2 什么是注入
注入體現的是一個IOC(控制反轉的的思想)。在反轉之前 ,我們先看看正轉。
AccountController自己來實例化需要的依賴。
private ILoginService _loginService;
public AccountController()
{
_loginService = new EFLoginService()
}
大師說,這樣不好。你不應該自己創建它,而是應該由你的調用者給你。于是你通過構造函數讓外界把這兩個依賴傳給你。
public AccountController(ILoginService loginService)
{
_loginService = loginService;
}
把依賴的創建丟給其它人,自己只負責使用,其它人丟給你依賴的這個過程理解為注入。
1.3 為什么要反轉?
為了在業務變化的時候盡少改動代碼可能造成的問題。
比如我們現在要把從EF中去驗證登錄改為從Redis去讀,于是我們加了一個 RedisLoginService。這個時候我們只需要在原來注入的地方改一下就可以了。
var controller = new AccountController(new EFLoginService());
controller.Login(userName, password);
// 用Redis來替換原來的EF登錄
var controller = new AccountController(new RedisLoginService());
controller.Login(userName, password);
1.4 何為容器
上面我們在使用AccountController的時候,我們自己通過代碼創建了一個ILoggingServce的實例。想象一下,一個系統中如果有100個這樣的地方,我們是不是要在100個地方做這樣的事情? 控制是反轉了,依賴的創建也移交到了外部。現在的問題是依賴太多,我們需要一個地方統一管理系統中所有的依賴,容器誕生了。
容器負責兩件事情:
綁定服務與實例之間的關系
獲取實例,并對實例進行管理(創建與銷毀)
二、.NET Core DI
2.1 實例的注冊
前面講清楚DI和Ioc的關鍵概念之后,我們先來看看在控制臺中對.NET Core DI的應用。在.NET Core中DI的核心分為兩個組件:IServiceCollection和 IServiceProvider。
IServiceCollection 負責注冊
IServiceProvider 負責提供實例
通過默認的 ServiceCollection(在Microsoft.Extensions.DependencyInjection命名空間下)有三個方法:
var serviceCollection = new ServiceCollection()
.AddTransient()
.AddSingleton()
.AddScoped();
這三個方法都是將我們的實例注冊進去,只不過實例的生命周期不一樣。什么時候生命周期我們下一節接著講。
ServiceCollection的默認實現是提供一個ServiceDescriptor的List
public interface IServiceCollection : IList
{
}
我們上面的AddTransient、AddSignletone和Scoped方法是IServiceCollection的擴展方法, 都是往這個List里面添加ServiceDescriptor。
private static IServiceCollection Add(IServiceCollection collection,
Type serviceType,
Type implementationType,
ServiceLifetime lifetime)
{
var descriptor =
new ServiceDescriptor(serviceType, implementationType, lifetime);
collection.Add(descriptor);
return collection;
}
2.2 實例的生命周期之單例
我們上面看到了,.NET Core DI 為我們提供的實例生命周其包括三種:
Transient: 每一次GetService都會創建一個新的實例
Scoped:? 在同一個Scope內只初始化一個實例 ,可以理解為( 每一個request級別只創建一個實例,同一個http request會在一個 scope內)
Singleton :整個應用程序生命周期以內只創建一個實例
對應了Microsoft.Extensions.DependencyInjection.ServiceLifetime的三個枚舉值
public enum ServiceLifetime
{
Singleton,
Scoped,
Transient
}
為了大家能夠更好的理解這個生命周期的概念我們做一個測試:
定義一個最基本的IOperation里面有一個 OperationId的屬性,IOperationSingleton也是一樣,只不過是另外一個接口。
public interface IOperation
{
Guid OperationId { get; }
}
public interface IOperationSingleton : IOperation { }
public interface IOperationTransient : IOperation{}
public interface IOperationScoped : IOperation{}
我們的 Operation實現很簡單,可以在構造函數中傳入一個Guid進行賦值,如果沒有的話則自已New一個 Guid。
public class Operation :
IOperationSingleton,
IOperationTransient,
IOperationScoped
{
private Guid _guid;
public Operation() {
_guid = Guid.NewGuid();
}
public Operation(Guid guid)
{
_guid = guid;
}
public Guid OperationId => _guid;
}
在程序內我們可以多次調用ServiceProvider的GetService方法,獲取到的都是同一個實例。
var services = new ServiceCollection();
// 默認構造
services.AddSingleton();
// 自定義傳入Guid空值
services.AddSingleton(
new Operation(Guid.Empty));
// 自定義傳入一個New的Guid
services.AddSingleton (
new Operation(Guid.NewGuid()));
var provider = services.BuildServiceProvider();
// 輸出singletone1的Guid
var singletone1 = provider.GetService();
Console.WriteLine($"signletone1: {singletone1.OperationId}");
// 輸出singletone2的Guid
var singletone2 = provider.GetService();
Console.WriteLine($"signletone2: {singletone2.OperationId}");
Console.WriteLine($"singletone1 == singletone2 ? : { singletone1 == singletone2 }");
我們對IOperationSingleton注冊了三次,最后獲取兩次,大家要注意到我們獲取到的始終都是我們最后一次注冊的那個給了一個Guid的實例,前面的會被覆蓋。
2.3 實例生命周期之Tranisent
這次我們獲取到的IOperationTransient為兩個不同的實例。
var services = new ServiceCollection();
services.AddTransient();
var provider = services.BuildServiceProvider();
var transient1 = provider.GetService();
Console.WriteLine($"transient1: {transient1.OperationId}");
var transient2 = provider.GetService();
Console.WriteLine($"transient2: {transient2.OperationId}");
Console.WriteLine($"transient1 == transient2 ? :
{ transient1 == transient2 }");
2.4 實例生命周期之Scoped
.NET Core人IServiceProvider提供CreateScope產生一個新的ServiceProvider范圍,在這個范圍下的Scope標注的實例將只會是同一個實例。換句話來說:用Scope注冊的對象,在同一個ServiceProvider的 Scope下相當于單例。
同樣我們先分別注冊IOperationScoped、IOperationTransient和IOperationSingletone 這三個實例,用對應的Scoped、Transient、和Singleton生命周期。
var services = new ServiceCollection()
.AddScoped()
.AddTransient()
.AddSingleton();
接下來我們用ServiceProvider.CreateScope方法創建一個Scope
var provider = services.BuildServiceProvider();
using (var scope1 = provider.CreateScope())
{
var p = scope1.ServiceProvider;
var scopeobj1 = p.GetService();
var transient1 = p.GetService();
var singleton1 = p.GetService();
var scopeobj2 = p.GetService();
var transient2 = p.GetService();
var singleton2 = p.GetService();
Console.WriteLine(
$"scope1: { scopeobj1.OperationId }," +
$"transient1: {transient1.OperationId}, " +
$"singleton1: {singleton1.OperationId}");
Console.WriteLine($"scope2: { scopeobj2.OperationId }, " +
$"transient2: {transient2.OperationId}, " +
$"singleton2: {singleton2.OperationId}");
}
接下來
scope1: 200d1e63-d024-4cd3-88c9-35fdf5c00956,
transient1: fb35f570-713e-43fc-854c-972eed2fae52,
singleton1: da6cf60f-670a-4a86-8fd6-01b635f74225
scope2: 200d1e63-d024-4cd3-88c9-35fdf5c00956,
transient2: 2766a1ee-766f-4116-8a48-3e569de54259,
singleton2: da6cf60f-670a-4a86-8fd6-01b635f74225
如果再創建一個新的Scope運行,
scope1: 29f127a7-baf5-4ab0-b264-fcced11d0729,
transient1: 035d8bfc-c516-44a7-94a5-3720bd39ce57,
singleton1: da6cf60f-670a-4a86-8fd6-01b635f74225
scope2: 29f127a7-baf5-4ab0-b264-fcced11d0729,
transient2: 74c37151-6497-4223-b558-a4ffc1897d57,
singleton2: da6cf60f-670a-4a86-8fd6-01b635f74225
大家注意到上面我們一共得到了 4個Transient實例,2個Scope實例,1個Singleton實例。
這有什么用?
如果在Mvc中用過Autofac的InstancePerRequest的同學就知道,有一些對象在一個請求跨越多個Action或者多個Service、Repository的時候,比如最常用的DBContext它可以是一個實例。即能減少實例初始化的消耗,還能實現跨Service事務的功能。(注:在ASP.NET Core中所有用到EF的Service 都需要注冊成Scoped )
而實現這種功能的方法就是在整個reqeust請求的生命周期以內共用了一個Scope。
三、DI在ASP.NET Core中的應用
3.1在Startup類中初始化
ASP.NET Core可以在Startup.cs的? ConfigureService中配置DI,大家看到 IServiceCollection這個參數應該就比較熟悉了。
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient,
EFLoginService>();
services.AddMvc();
)
ASP.NET Core的一些組件已經提供了一些實例的綁定,像AddMvc就是Mvc Middleware在 IServiceCollection上添加的擴展方法。
public static IMvcBuilder AddMvc(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
var builder = services.AddMvcCore();
builder.AddApiExplorer();
builder.AddAuthorization();
AddDefaultFrameworkParts(builder.PartManager);
...
}
3.2 Controller中使用
一般可以通過構造函數或者屬性來實現注入,但是官方推薦是通過構造函數。這也是所謂的顯式依賴。
private ILoginService _loginService;
public AccountController(
ILoginService loginService)
{
_loginService = loginService;
}
我們只要在控制器的構造函數里面寫了這個參數,ServiceProvider就會幫我們注入進來。這一步是在Mvc初始化控制器的時候完成的,我們后面再介紹到Mvc的時候會往細里講。
3.3 View中使用
在View中需要用@inject 再聲明一下,起一個別名。
@using MilkStone.Services;
@model MilkStone.Models.AccountViewModel.LoginViewModel
@inject ILoginService loginService
@loginService.GetUserName()
3.4 通過 HttpContext來獲取實例
HttpContext下有一個RequestedService同樣可以用來獲取實例對象,不過這種方法一般不推薦。同時要注意GetService<>這是個范型方法,默認如果沒有添加Microsoft.Extension.DependencyInjection的using,是不用調用這個方法的。
HttpContext.RequestServices.GetService>();
四、如何替換其它的Ioc容器
Autofac也是不錯的選擇,但我們首先要搞清楚為什么要替換掉默認的 DI容器?,替換之后有什么影響?.NET Core默認的實現對于一些小型的項目完全夠用,甚至大型項目麻煩點也能用,但是會有些麻煩,原因在于只提供了最基本的AddXXXX方法來綁定實例關系,需要一個一個的添加。如果項目可能要添加好幾百行這樣的方法。
如果熟悉Autofac的同學可能會這下面這樣的代碼有映象。
builder.RegisterGeneric(typeof(LoggingBehavior)).As(typeof(IPipelineBehavior));
builder.RegisterGeneric(typeof(ValidatorBehavior)).As(typeof(IPipelineBehavior));
這會給我們的初始化帶來一些便利性,我們來看看如何替換Autofac到ASP.NET Core。我們只需要把Startup類里面的 ConfigureService的 返回值從 void改為 IServiceProvider即可。而返回的則是一個AutoServiceProvider。
public IServiceProvider ConfigureServices(IServiceCollection services){
services.AddMvc();
// Add other framework services
// Add Autofac
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterModule();
containerBuilder.Populate(services);
var container = containerBuilder.Build();
return new AutofacServiceProvider(container);
}
4.1 有何變化
其中很大的一個變化在于,Autofac 原來的一個生命周期InstancePerRequest,將不再有效。正如我們前面所說的,整個request的生命周期被ASP.NET Core管理了,所以Autofac的這個將不再有效。我們可以使用 InstancePerLifetimeScope ,同樣是有用的,對應了我們ASP.NET Core DI 里面的Scoped。
轉載原址:http://www.jessetalk.cn/2017/11/06/di-in-aspnetcore/
總結
以上是生活随笔為你收集整理的core控制器属性注入的用处_了解ASP.NET Core 依赖注入,看这篇就够了的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: cad抛物线曲线lisp_曲线的转弯半径
- 下一篇: tomcat启动占了12g_window