ASP.NET Core使用了大量的依賴注入(Dependency Injection, DI),把控制反轉(Inversion Of Control, IoC)運用的至關巧妙。DI可算是ASP.NET Core最精華的一部分,有用過Autofac或相似的DI Framework對此應該不陌生。
本篇將介紹ASP.NET Core的依賴注入(Dependency Injection)。html
在沒有使用DI Framework 的狀況下,假設在UserController 要調用UserLogic,會直接在UserController 實例化UserLogic,以下:git
public class UserLogic {
public void Create(User user) {
// ...
}
}
public class UserController : Controller {
public void Register(User user){
var logic = new UserLogic();
logic.Create(user);
// ...
}
}
以上程序基本沒什麼問題,可是依賴關係差了點。UserController 必需要依賴UserLogic才能夠運行,拆出接口改爲:github
public interface IUserLogic {
void Create(User user);
}
public class UserLogic : IUserLogic {
public void Create(User user) {
// ...
}
}
public class UserController : Controller {
private readonly IUserLogic _userLogic;
public UserController() {
_userLogic = new UserLogic();
}
public void Register(User user){
_userLogic.Create(user);
// ...
}
}
UserController 與UserLogic 的依賴關係只是從Action 移到構造方法中,依然仍是很強的依賴關係。bash
ASP.NET Core透過DI容器,切斷這些依賴關係,實例的產生不在使用方(指上例UserController構造方法中的new
),而是在DI容器。
DI容器的註冊方式也很簡單,在Startup.ConfigureServices
註冊。以下:spa
Startup.cs.net
// ...
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
}
services就是一個DI容器。
此例把MVC的服務註冊到DI容器,等到須要用到MVC服務時,才從DI容器取得對象實例。 3d
基本上要注入到Service的類沒什麼限制,除了靜態類。
如下示例就只是通常的Class繼承Interface:code
public interface ISample
{
int Id { get; }
}
public class Sample : ISample
{
private static int _counter;
private int _id;
public Sample()
{
_id = ++_counter;
}
public int Id => _id;
}
要注入的Service須要在Startup.ConfigureServices
中註冊實例類型。以下:htm
Startup.cs對象
// ...
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddScoped<ISample, Sample>();
}
}
ASP.NET Core的DI是採用Constructor Injection,也就是說會把實例化的物件從建構子傳入。
若是要取用DI容器內的物件,只要在建構子加入相對的Interface便可。例如:
Controllers\HomeController.cs
using Microsoft.AspNetCore.Mvc;
using MyWebsite.Models;
namespace MyWebsite.Controllers
{
public class HomeController : Controller
{
private readonly ISample _sample;
public HomeController(ISample sample)
{
_sample = sample;
}
public string Index()
{
return $"[ISample]\r\n"
+ $"Id: {_sample.Id}\r\n"
+ $"HashCode: {_sample.GetHashCode()}\r\n"
+ $"Tpye: {_sample.GetType()}";
}
}
}
輸出內容以下:
[ISample]
Id: 1
HashCode: 52582687
Tpye: MyWebsite.Models.Sample
ASP.NET Core 實例化Controller 時,發現構造方法中有ISample 這個類型的參數,就把Sample 的實例注入給該Controller。
每一個Request 都會把Controller 實例化,因此DI 容器會從構造方法注入ISample 的實例,把sample 存到變量_sample 中,就能確保Action 可以使用到被注入進來的ISample 實例。
注入實例過程,情境以下:
註冊在DI 容器的Service 分三種生命週期:
new
一個新的實例。new
一個新的實例,同一個Request無論通過多少個Pipeline都是用同一個實例。上例所使用的就是Scoped。小改一下Sample 類的代碼:
namespace MyWebsite.Models
{
public interface ISample
{
int Id { get; }
}
public interface ISampleTransient : ISample
{
}
public interface ISampleScoped : ISample
{
}
public interface ISampleSingleton : ISample
{
}
public class Sample : ISampleTransient, ISampleScoped, ISampleSingleton
{
private static int _counter;
private int _id;
public Sample()
{
_id = ++_counter;
}
public int Id => _id;
}
}
在Startup.ConfigureServices
中註冊三種不一樣生命週期的服務。以下:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ISampleTransient, Sample>();
services.AddScoped<ISampleScoped, Sample>();
services.AddSingleton<ISampleSingleton, Sample>();
// Singleton 也能夠用如下方法註冊
// services.AddSingleton<ISampleSingleton>(new Sample());
}
}
只要是透過WebHost產生實例的類型,均可以在構造方法注入。
因此Controller、View、Filter、Middleware或自定義的Service等均可以被注入。
此篇我只用Controller、View、Service作爲範例。
在TestController 中注入上例的三個Services:
Controllers\TestController.cs
using Microsoft.AspNetCore.Mvc;
using MyWebsite.Models;
namespace MyWebsite.Controllers
{
public class TestController : Controller
{
private readonly ISample _transient;
private readonly ISample _scoped;
private readonly ISample _singleton;
public TestController(
ISampleTransient transient,
ISampleScoped scoped,
ISampleSingleton singleton)
{
_transient = transient;
_scoped = scoped;
_singleton = singleton;
}
public IActionResult Index()
{
ViewBag.TransientId = _transient.Id;
ViewBag.TransientHashCode = _transient.GetHashCode();
ViewBag.ScopedId = _scoped.Id;
ViewBag.ScopedHashCode = _scoped.GetHashCode();
ViewBag.SingletonId = _singleton.Id;
ViewBag.SingletonHashCode = _singleton.GetHashCode();
return View();
}
}
}
Views\Test\Index.cshtml
<table border="1">
<tr><td colspan="3">Cotroller</td></tr>
<tr><td>Lifetimes</td><td>Id</td><td>Hash Code</td></tr>
<tr><td>Transient</td><td>@ViewBag.TransientId</td><td>@ViewBag.TransientHashCode</td></tr>
<tr><td>Scoped</td><td>@ViewBag.ScopedId</td><td>@ViewBag.ScopedHashCode</td></tr>
<tr><td>Singleton</td><td>@ViewBag.SingletonId</td><td>@ViewBag.SingletonHashCode</td></tr>
</table>
輸出內容以下:
從左到又打開頁面三次,能夠發現Singleton的Id及HashCode都是同樣的,如今還看不太能看出來Transient及Scoped的差別。
Service 實例產生方式:
圖例說明:
View注入Service的方式,直接在*.cshtml
使用@inject
:
Views\Test\Index.cshtml
@using MyWebsite.Models
@inject ISampleTransient transient
@inject ISampleScoped scoped
@inject ISampleSingleton singleton
<table border="1">
<tr><td colspan="3">Cotroller</td></tr>
<tr><td>Lifetimes</td><td>Id</td><td>Hash Code</td></tr>
<tr><td>Transient</td><td>@ViewBag.TransientId</td><td>@ViewBag.TransientHashCode</td></tr>
<tr><td>Scoped</td><td>@ViewBag.ScopedId</td><td>@ViewBag.ScopedHashCode</td></tr>
<tr><td>Singleton</td><td>@ViewBag.SingletonId</td><td>@ViewBag.SingletonHashCode</td></tr>
</table>
<hr />
<table border="1">
<tr><td colspan="3">View</td></tr>
<tr><td>Lifetimes</td><td>Id</td><td>Hash Code</td></tr>
<tr><td>Transient</td><td>@transient.Id</td><td>@transient.GetHashCode()</td></tr>
<tr><td>Scoped</td><td>@scoped.Id</td><td>@scoped.GetHashCode()</td></tr>
<tr><td>Singleton</td><td>@singleton.Id</td><td>@singleton.GetHashCode()</td></tr>
</table>
輸出內容以下:
從左到又打開頁面三次,Singleton的Id及HashCode如前例是同樣的。
Transient及Scoped的差別在此次就有明顯差別,Scoped在同一次Request的Id及HashCode都是同樣的,如紅紫籃框。
簡單創建一個CustomService,注入上例三個Service,代碼相似TestController。以下:
Services\CustomService.cs
using MyWebsite.Models; namespace MyWebsite.Services { public class CustomService { public ISample Transient { get; private set; } public ISample Scoped { get; private set; } public ISample Singleton { get; private set; } public CustomService(ISampleTransient transient, ISampleScoped scoped, ISampleSingleton singleton) { Transient = transient; Scoped = scoped; Singleton = singleton; } } }
註冊CustomService
Startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddScoped<CustomService, CustomService>();
}
}
第一個泛型也能夠是類,不必定要是接口。
缺點是使用方以Class做爲依賴關係,變成強關聯的依賴。
在View 注入CustomService:
Views\Home\Index.cshtml
@using MyWebsite
@using MyWebsite.Models
@using MyWebsite.Services
@inject ISampleTransient transient
@inject ISampleScoped scoped
@inject ISampleSingleton singleton
@inject CustomService customService
<table border="1">
<tr><td colspan="3">Cotroller</td></tr>
<tr><td>Lifetimes</td><td>Id</td><td>Hash Code</td></tr>
<tr><td>Transient</td><td>@ViewBag.TransientId</td><td>@ViewBag.TransientHashCode</td></tr>
<tr><td>Scoped</td><td>@ViewBag.ScopedId</td><td>@ViewBag.ScopedHashCode</td></tr>
<tr><td>Singleton</td><td>@ViewBag.SingletonId</td><td>@ViewBag.SingletonHashCode</td></tr>
</table>
<hr />
<table border="1">
<tr><td colspan="3">View</td></tr>
<tr><td>Lifetimes</td><td>Id</td><td>Hash Code</td></tr>
<tr><td>Transient</td><td>@transient.Id</td><td>@transient.GetHashCode()</td></tr>
<tr><td>Scoped</td><td>@scoped.Id</td><td>@scoped.GetHashCode()</td></tr>
<tr><td>Singleton</td><td>@singleton.Id</td><td>@singleton.GetHashCode()</td></tr>
</table>
<hr />
<table border="1">
<tr><td colspan="3">Custom Service</td></tr>
<tr><td>Lifetimes</td><td>Id</td><td>Hash Code</td></tr>
<tr><td>Transient</td><td>@customService.Transient.Id</td><td>@customService.Transient.GetHashCode()</td></tr>
<tr><td>Scoped</td><td>@customService.Scoped.Id</td><td>@customService.Scoped.GetHashCode()</td></tr>
<tr><td>Singleton</td><td>@customService.Singleton.Id</td><td>@customService.Singleton.GetHashCode()</td></tr>
</table>
輸出內容以下:
從左到又打開頁面三次:
Introduction to Dependency Injection in ASP.NET Core
ASP.NET Core Dependency Injection Deep Dive
老司機發車啦:https://github.com/SnailDev/SnailDev.NETCore2Learning