[ASP.NET Core開發實戰]基礎篇02 依賴注入
阿新 • • 發佈:2020-07-13
ASP.NET Core的底層機制之一是依賴注入(DI)設計模式,因此要好好掌握依賴注入的用法。
什麼是依賴注入
我們看一下下面的例子:
public class MyDependency { public MyDependency() { } public Task WriteMessage(string message) { Console.WriteLine( $"MyDependency.WriteMessage called. Message: {message}"); return Task.FromResult(0); } } public class IndexModel : PageModel { MyDependency _dependency = new MyDependency(); public async Task OnGetAsync() { await _dependency.WriteMessage( "IndexModel.OnGetAsync created this message."); } }
IndexModel類直接在內部建立了一個MyDependency例項,直接依賴於MyDependency。在開發時避免使用這種方式,原因如下:
- 要替換不同實現的MyDependency時,必須修改類。
- 如果MyDependency也有依賴,必須由類對其進行配置。
- 很難進行單元測試。
可以通過以下方式解決這些問題:
- 使用介面或基類。
- 註冊服務視窗中的依賴關注。
- 通過建構函式注入。
將上面的例子改造為依賴注入方式:
public interface IMyDependency { Task WriteMessage(string message); } public class MyDependency : IMyDependency { private readonly ILogger<MyDependency> _logger; public MyDependency(ILogger<MyDependency> logger) { _logger = logger; } public Task WriteMessage(string message) { _logger.LogInformation( "MyDependency.WriteMessage called. Message: {MESSAGE}", message); return Task.FromResult(0); } } public class IndexModel : PageModel { MyDependency _dependency; public IndexModel(MyDependency dependency){ _dependency = dependency; } public async Task OnGetAsync() { await _dependency.WriteMessage( "IndexModel.OnGetAsync created this message."); } } public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddScoped<IMyDependency, MyDependency>(); } }
在Startup裡註冊服務
服務可以在Startup.Configure註冊:
public void Configure(IApplicationBuilder app, IOptions<MyOptions> options)
{
...
}
使用擴充套件方法註冊服務
當使用服務集合的擴充套件方法來註冊服務,約定使用Add{SERVICE_NAME}擴充套件方法來註冊該服務所需的所有服務。
以下例子是使用擴充套件方法AddDbContext
public void ConfigureServices(IServiceCollection services) { ... services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); ... }
服務生命週期
服務的生命週期有三種:Transient、Scoped、Singleton。
Transient
Transient,意為暫時的,是每次服務容器進行請求時建立的,即服務每次例項化時建立一次。這種生存期適合輕量級、無狀態的服務。
Scoped
Scoped,意為範圍內的,每個客戶端請求(連線)時會建立一次。以Web為例,即一次Http請求內建立一次且請求內有效。
Singleton
Singleton,意為單例的,是在第一次請求時建立的。後面每次請求時使用的是同一個例項。在應用關閉,釋放ServiceProvider時,會釋放。
驗證服務生命週期
下面示例是演示服務生命週期的差異。
public interface IOperation
{
Guid OperationId { get; }
}
public interface IOperationTransient : IOperation
{
}
public interface IOperationScoped : IOperation
{
}
public interface IOperationSingleton : IOperation
{
}
public interface IOperationSingletonInstance : IOperation
{
}
public class Operation : IOperationTransient,
IOperationScoped,
IOperationSingleton,
IOperationSingletonInstance
{
public Operation() : this(Guid.NewGuid())
{
}
public Operation(Guid id)
{
OperationId = id;
}
public Guid OperationId { get; private set; }
}
public class OperationService
{
public OperationService(
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation,
IOperationSingletonInstance instanceOperation)
{
TransientOperation = transientOperation;
ScopedOperation = scopedOperation;
SingletonOperation = singletonOperation;
SingletonInstanceOperation = instanceOperation;
}
public IOperationTransient TransientOperation { get; }
public IOperationScoped ScopedOperation { get; }
public IOperationSingleton SingletonOperation { get; }
public IOperationSingletonInstance SingletonInstanceOperation { get; }
}
在Startup.ConfigureServices中,指定各服務的生命週期。
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddScoped<IMyDependency, MyDependency>();
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));
// OperationService depends on each of the other Operation types.
services.AddTransient<OperationService, OperationService>();
}
示例IndexModel:
public class IndexModel : PageModel
{
private readonly IMyDependency _myDependency;
public IndexModel(
IMyDependency myDependency,
OperationService operationService,
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation,
IOperationSingletonInstance singletonInstanceOperation)
{
_myDependency = myDependency;
OperationService = operationService;
TransientOperation = transientOperation;
ScopedOperation = scopedOperation;
SingletonOperation = singletonOperation;
SingletonInstanceOperation = singletonInstanceOperation;
}
public OperationService OperationService { get; }
public IOperationTransient TransientOperation { get; }
public IOperationScoped ScopedOperation { get; }
public IOperationSingleton SingletonOperation { get; }
public IOperationSingletonInstance SingletonInstanceOperation { get; }
public async Task OnGetAsync()
{
await _myDependency.WriteMessage(
"IndexModel.OnGetAsync created this message.");
}
}
以下是兩次訪問IndexModel的結果:
第一個請求:
控制器操作:
暫時性:d233e165-f417-469b-a866-1cf1935d2518
作用域:5d997e2d-55f5-4a64-8388-51c4e3a1ad19
單一例項:01271bc1-9e31-48e7-8f7c-7261b040ded9
例項:00000000-0000-0000-0000-000000000000
OperationService 操作:
暫時性:c6b049eb-1318-4e31-90f1-eb2dd849ff64
作用域:5d997e2d-55f5-4a64-8388-51c4e3a1ad19
單一例項:01271bc1-9e31-48e7-8f7c-7261b040ded9
例項:00000000-0000-0000-0000-000000000000
第二個請求:
控制器操作:
暫時性:b63bd538-0a37-4ff1-90ba-081c5138dda0
作用域:31e820c5-4834-4d22-83fc-a60118acb9f4
單一例項:01271bc1-9e31-48e7-8f7c-7261b040ded9
例項:00000000-0000-0000-0000-000000000000
OperationService 操作:
暫時性:c4cbacb8-36a2-436d-81c8-8c1b78808aaf
作用域:31e820c5-4834-4d22-83fc-a60118acb9f4
單一例項:01271bc1-9e31-48e7-8f7c-7261b040ded9
例項:00000000-0000-0000-0000-000000000000
從上面的結果,觀察OperationId的變化:
- 暫時性,值始終不同。
- 作用域,同一請求內相同,不同請求不同。
- 單例,不管是同一請求還是不同請求,都一樣。
最佳實踐
最佳做法:
- 設計服務以使用依賴關係注入來獲取其依賴關係。
- 避免有狀態的、靜態類和成員。將應用設計為改用單一例項服務,可避免建立全域性狀態。
- 避免在服務中直接例項化依賴類。 直接例項化將程式碼耦合到特定實現。
- 不在應用類中包含過多內容,確保設計規範,並易於測試。