ASP.NET Core的底層設計支援和使用依賴注入。ASP.NET Core 應用程式可以利用內建的框架服務將服務注入到啟動類的方法中,並且應用程式服務也可以配置注入。由ASP.NET Core 提供的預設服務容器提供了最小功能集,並不是取代其他容器。
1.淺談依賴注入
依賴注入(Dependency injection,DI)是一種實現物件和依賴者之間鬆耦合的技術,將類用來執行其操作的這些物件以注入的方式提供給該類,而不是直接例項化依賴項或者使用靜態引用。一般情況,類會通過建構函式宣告器2依賴關係,允許他們遵循顯示依賴原則。這種方法稱為“建構函式注入”。
當類的設計使用DI思想時,他們的耦合更加鬆散,因為他們沒有對他們的合作者直接硬編碼的依賴。這遵循“依賴倒置原則”,其中指出,高層模組不應該依賴於底層模組:兩者都依賴於抽象。
類要求在他們構造時向其提供抽象(通常是介面),而不是引用特定的實現。提取介面的依賴關係和提供介面的實現作為引數也是“策略設計模式”的一個示例。
當一個類被用來建立類及其相關的依賴關係時,這個成為容器(containers),或者稱為控制反轉(Inversion of Control, IoC)容器,或者依賴注入容器。容器本質上是一個工廠,負責提供向它請求的型別的例項。如果一個給定型別宣告它具有依賴關係,並且容器已經被配置為其提供依賴關係,那麼它將把建立依賴關係作為建立請求例項的一部分。除了建立物件的依賴關係外,容器通常還會管理應用程式中物件的生命週期。
ASP.NET Core 包含一個預設支援建構函式注入的簡單內建容器,ASP.NET 的容器指的是它管理的型別services,可以在Startup類的ConfigureServices方法中配置內建容器的服務。
2. 使用ASP.NET Core提供的服務
Startup類的ConfigureServices方法負責定義應用程式將使用的服務,包括平臺自帶的功能,比如,Entity Framework Core 和 ASP.NET Core MVC。除了IServiceCollection提供的幾個服務之外,可以使用一些擴充套件方法(AddDbContext,AddMvc,AddTransient等)向容器新增和註冊額外服務:
public void ConfigureServices(IServiceCollection services) { services.Configure<CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddDbContext<AccessManagementContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), providerOptions => providerOptions.EnableRetryOnFailure())); services.AddTransient<ICompanyServices, CompanyServices>(); }
ASP.NET Core 提供的功能和中介軟體,遵循約定使用一個單一的AddService擴充套件方法來註冊所有該功能所需的服務。
3.註冊自己的服務
我們可以按照 services.AddTransient<ICompanyServices, CompanyServices>(); 這種寫法註冊自己的服務。第一個範型型別表示將要從容器中請求的型別(通常是一個介面)。第二個範型型別表示將由容器例項化並且用於完成請求的具體型別。
AddTransient 方法用於將抽象型別對映到為每一個需要它的物件分別例項化的具體服務。為註冊的每一個服務選擇合適的生命週期很重要,後面會介紹到。
下面是示例是註冊自己的服務:
1.介面
public interface IAccountServices { Task<List<AccountViewModel>> GetList(); }
2.實現類
public class AccountServices:IAccountServices { AccessManagementContext _context; public AccountServices(AccessManagementContext context) { _context = context;//在建構函式中注入 } public async Task<List<Account>> GetList() { try { var query = _context.Account.ToListAsync(); return query ; } catch (Exception ex) { return null; } } }
3.在ConfigureServices中註冊自定義的服務和EF上下文AccessManagementContext
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddDbContext<AccessManagementContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), providerOptions => providerOptions.EnableRetryOnFailure())); services.AddTransient<IAccountServices,AccountServices>(); }
4.在Controller建構函式中依賴注入
public class AccountController : Controller { private IAccountServices _accountServices; public AccountController(IAccountServices accountServices) { _accountServices = accountServices; } // GET: Account public async Task<ActionResult> Index() { var vms = await _accountServices.GetList(); return View(vms); }
4.服務的生命週期和註冊選項
ASP.NET 服務生命週期:
1.Transient 瞬時
Transient 生命週期服務在他們每次請求時被建立。適合輕量級,無狀態的服務。
2.Scoped 作用域
Scoped生命週期在每次請求時建立一次。
3.Singleton 單例
Singleton 生命週期服務在它們第一次請求時建立,並且每個後續請求使用相同的例項。
服務可以用多種方式在容器中註冊,除了之前的註冊方法,還可以指定一個工廠,它將被用來建立需要的例項。後面會詳細介紹其他的註冊方法。
下面用一個簡單的示例介紹每個生命週期:
1.建立介面:
namespace MVCTest.Interfaces { public interface IOperation { /// <summary> /// 唯一標識 /// </summary> Guid OperationId { get; } } public interface IOperationTransient: IOperation { } public interface IOperationScoped : IOperation { } public interface IOperationSingleton : IOperation { } public interface IOperationInstance : IOperation { } }
2.實現類
/// <summary> /// 實現所有介面 /// </summary> public class Operation: IOperation, IOperationTransient, IOperationScoped, IOperationSingleton, IOperationInstance { public Operation() { OperationId = Guid.NewGuid(); } public Operation(Guid operationId) { if (operationId == null) { OperationId = Guid.NewGuid(); } OperationId = operationId; } public Guid OperationId { get; } }
3.註冊到容器
public void ConfigureServices(IServiceCollection services) { services.AddTransient<IOperationTransient, Operation>(); services.AddScoped<IOperationScoped, Operation>(); services.AddSingleton<IOperationSingleton, Operation>(); services.AddSingleton<IOperationInstance, Operation>(); services.AddTransient<OperationServices, OperationServices>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); }
4.上面還註冊了 OperationServices ,用來測試單例模式(單例生命週期服務中所有請求使用第一次例項化的服務)和 作用域生命週期服務在每次請求時只建立一次,不管幾個地方用到例項
public class OperationServices { public IOperationTransient OperationTransient { get; } public IOperationScoped OperationScoped { get; } public IOperationSingleton OperationSingleton { get; } public IOperationInstance OperationInstance { get; } public OperationServices(IOperationTransient operationTransient, IOperationScoped operationScoped, IOperationSingleton operationSingleton, IOperationInstance operationInstance) { OperationTransient = operationTransient; OperationScoped = operationScoped; OperationSingleton = operationSingleton; OperationInstance = operationInstance; } }
5.在Controller中使用
public class OperationController : Controller { public IOperationTransient OperationTransient { get; } public IOperationScoped OperationScoped { get; } public IOperationSingleton OperationSingleton { get; } public IOperationInstance OperationInstance { get; } public OperationServices _operationServices; public OperationController(IOperationTransient operationTransient, IOperationScoped operationScoped, IOperationSingleton operationSingleton, IOperationInstance operationInstance, OperationServices operationServices) { OperationTransient = operationTransient; OperationScoped = operationScoped; OperationSingleton = operationSingleton; OperationInstance = operationInstance; _operationServices = operationServices; } // GET: Operation public ActionResult Index() { ViewBag.OperationTransient = OperationTransient; ViewBag.OperationScoped = OperationScoped; ViewBag.OperationSingleton = OperationSingleton; ViewBag.OperationInstance = OperationInstance; ViewBag._operationServices = _operationServices; return View(); } }
6.Index顯示
@{ ViewData["Title"] = "Index"; } <div> <h1>Controller Operations</h1> <h2>OperationTransient: @ViewBag.OperationTransient.OperationId</h2> <h2>OperationScoped: @ViewBag.OperationScoped.OperationId</h2> <h2>OperationSingleton: @ViewBag.OperationSingleton.OperationId</h2> <h2>OperationInstance: @ViewBag.OperationInstance.OperationId</h2> </div> <div> <h1>Services Operations</h1> <h2>OperationTransient: @ViewBag._operationServices.OperationTransient.OperationId</h2> <h2>OperationScoped: @ViewBag._operationServices.OperationScoped.OperationId</h2> <h2>OperationSingleton: @ViewBag._operationServices.OperationSingleton.OperationId</h2> <h2>OperationInstance: @ViewBag._operationServices.OperationInstance.OperationId</h2> </div>
7.執行結果
可以看到,單例生命週期服務每一次請求的標識一樣。作用域生命週期的服務,在一次請求中使用的同一個例項,第二次請求建立新的例項。
5.請求服務
來自HttpContext的一次ASP.NET 請求中,可用的服務是通過RequestServices集合公開的。
請求服務將你配置的服務和請求描述為應用程式的一部分。在子的物件指定依賴之後,這些滿足要求的物件可通過查詢RequestServices中對應的型別得到,而不是ApplicationServices。
6.設計依賴注入服務
在自定義的服務中,避免使用靜態方法和直接例項化依賴的型別,而是通過依賴注入請求它。(New is Glue)
如果類有太多的依賴關係被注入時,通常表明你的類試圖做的太多(違反了單一職責原則),需要轉移一些職責。
同樣,Controller類應該重點關注UI,因此業務邏輯和資料訪問等細節應該在其他類中。
7.使用Autofac容器