ASP.NET Core基礎知識(三)【依賴關係注入(服務)】
在 ASP.NET Core
依賴注入
ASP.NET Core
支援依賴關係注入 (DI) 軟體設計模式,這是一種在類及其依賴關係之間實現控制反轉 (IoC)
的技術。
有關特定於 MVC 控制器中依賴關係注入的詳細資訊,請參閱 在 ASP.NET Core 中將依賴項注入到控制器
。
依賴關係注入概述
依賴項是另一個物件所需的任何物件。 使用應用中其他類所依賴的 WriteMessage
方法檢查以下 MyDependency
類:
public class MyDependency
{
public MyDependency()
{
}
public Task WriteMessage(string message)
{
Console.WriteLine(
$"MyDependency.WriteMessage called. Message: {message}");
return Task.FromResult(0);
}
}
可以建立 MyDependency
類的例項以使 WriteMessage
方法可用於類。 MyDependency
類是 IndexModel
類的依賴項:
public class IndexModel : PageModel
{
MyDependency _dependency = new MyDependency();
public async Task OnGetAsync()
{
await _dependency.WriteMessage(
"IndexModel.OnGetAsync created this message.");
}
}
可以建立 MyDependency
類的例項以使 WriteMessage
方法可用於類。 MyDependency
類是 HomeController
類的依賴項:
public class HomeController : Controller
{
MyDependency _dependency = new MyDependency();
public async Task<IActionResult> Index()
{
await _dependency.WriteMessage(
"HomeController.Index created this message.");
return View();
}
}
該類建立並直接依賴於 MyDependency
例項。 程式碼依賴關係(如前面的示例)存在問題,應該避免使用,原因如下:
- 要用不同的實現替換
MyDependency
,必須修改類。 - 如果
MyDependency
具有依賴關係,則必須由類對其進行配置。 在具有多個依賴於MyDependency
的類的大型專案中,配置程式碼在整個應用中會變得分散。 - 這種實現很難進行單元測試。 應用應使用模擬或存根
MyDependency
類,該類不能使用此方法。
依賴關係注入通過以下方式解決了這些問題:
- 使用介面抽象化依賴關係實現。
- 註冊服務容器中的依賴關係。
ASP.NET Core
提供了一個內建的服務容器IServiceProvider
。 服務已在應用的Startup.ConfigureServices
方法中註冊。 - 將服務注入到使用它的類的建構函式中。 框架負責建立依賴關係的例項,並在不再需要時對其進行處理。
在示例應用
中,IMyDependency
介面定義了服務為應用提供的方法:
public interface IMyDependency
{
Task WriteMessage(string message);
}
此介面由具體型別 MyDependency
實現:
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);
}
}
MyDependency
在其建構函式中請求ILogger<TCategoryName>
。 以鏈式方式使用依賴關係注入並不罕見。 每個請求的依賴關係相應地請求其自己的依賴關係。 容器解析圖中的依賴關係並返回完全解析的服務。 必須被解析的依賴關係的集合通常被稱為“依賴關係樹”、“依賴關係圖”或“物件圖”。
必須在服務容器中註冊 IMyDependency
和 ILogger<TCategoryName>
。 IMyDependency
已在 Startup.ConfigureServices
中註冊。 ILogger<TCategoryName>
由日誌記錄抽象基礎結構註冊,因此它是框架預設註冊的框架提供的服務
。
在示例應用中,使用具體型別 MyDependency
註冊 IMyDependency
服務。 註冊將服務生存期的範圍限定為單個請求的生存期。 本主題後面將介紹服務生存期
。
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
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>();
}
備註
每個services.Add{SERVICE_NAME}
擴充套件方法新增(並可能配置)服務。 例如,services.AddMvc()
新增 Razor Pages 和 MVC 需要的服務。 我們建議應用遵循此約定。 將擴充套件方法置於Microsoft.Extensions.DependencyInjection
名稱空間中以封裝服務註冊的組。
如果服務的建構函式需要基元(如 string
),則可以使用配置
或選項模式
注入基元:
public class MyDependency : IMyDependency
{
public MyDependency(IConfiguration config)
{
var myStringValue = config["MyStringKey"];
// Use myStringValue
}
...
}
通過使用服務並分配給私有欄位的類的建構函式請求服務的例項。 該欄位用於在整個類中根據需要訪問服務。
在示例應用中,請求 IMyDependency
例項並用於呼叫服務的 WriteMessage
方法:
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.");
}
}
框架提供的服務
Startup.ConfigureServices
方法負責定義應用使用的服務,包括 Entity Framework Core
和 ASP.NET Core MVC
等平臺功能。 最初,提供給 ConfigureServices
的 IServiceCollection
定義了以下服務(具體取決於配置主機的方式
):
服務型別 | 生存期 |
---|---|
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory | 暫時 |
Microsoft.AspNetCore.Hosting.IApplicationLifetime |
單例 |
Microsoft.AspNetCore.Hosting.IHostingEnvironment |
單例 |
Microsoft.AspNetCore.Hosting.IStartup |
單例 |
Microsoft.AspNetCore.Hosting.IStartupFilter | 暫時 |
Microsoft.AspNetCore.Hosting.Server.IServer | 單例 |
Microsoft.AspNetCore.Http.IHttpContextFactory | 暫時 |
Microsoft.Extensions.Logging.ILogger<T> |
單例 |
Microsoft.Extensions.Logging.ILoggerFactory | 單例 |
Microsoft.Extensions.ObjectPool.ObjectPoolProvider | 單一例項 |
Microsoft.Extensions.Options.IConfigureOptions<T> |
暫時 |
Microsoft.Extensions.Options.IOptions<T> |
單一例項 |
System.Diagnostics.DiagnosticSource |
單一例項 |
System.Diagnostics.DiagnosticListener |
單例 |
當服務集合擴充套件方法可用於註冊服務(及其依賴服務,如果需要)時,約定使用單個 Add{SERVICE_NAME}
擴充套件方法來註冊該服務所需的所有服務。 以下程式碼是如何使用擴充套件方法 AddDbContext
、AddIdentity
和 AddMvc
向容器新增其他服務的示例:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
}
有關詳細資訊,請參閱 API 文件中的 ServiceCollection 類
。
服務生存期
為每個註冊的服務選擇適當的生存期。 可以使用以下生存期配置 ASP.NET Core
服務:
暫時
暫時生存期服務是每次請求時建立的。 這種生存期適合輕量級、 無狀態的服務。
作用域(Scoped)
作用域生存期服務以每個請求一次的方式建立。
警告
在中介軟體內使用有作用域的服務時,請將該服務注入至Invoke
或InvokeAsync
方法。 請不要通過建構函式注入進行注入,因為它會強制服務的行為與單一例項類似。 有關更多資訊,請參見ASP.NET Core 中介軟體
。
單例
單一例項生存期服務是在第一次請求時(或者在執行 ConfigureServices
並且使用服務註冊指定例項時)建立的。 每個後續請求都使用相同的例項。 如果應用需要單一例項行為,建議允許服務容器管理服務的生存期。不要實現單一例項設計模式並提供使用者程式碼來管理物件在類中的生存期。
警告
從單一例項解析有作用域的服務很危險。 當處理後續請求時,它可能會導致服務處於不正確的狀態。
建構函式注入行為
服務可以通過兩種機制來解析:
IServiceProvider
ActivatorUtilities
允許在依賴關係注入容器中建立沒有服務註冊的物件。ActivatorUtilities
用於面向使用者的抽象,例如標記幫助器、MVC 控制器和模型繫結器。
建構函式可以接受依賴關係注入不提供的引數,但引數必須分配預設值。
當服務由 IServiceProvider
或 ActivatorUtilities
解析時,建構函式注入需要 public 建構函式。
當服務由 ActivatorUtilities
解析時,建構函式注入要求只存在一個適用的建構函式。 支援建構函式過載,但其引數可以全部通過依賴注入來實現的過載只能存在一個。
實體框架上下文
應使用有作用域的生存期將實體框架上下文新增到服務容器中。 在註冊資料庫上下文時,通過呼叫AddDbContext
方法自動處理。 使用資料庫上下文的服務也應使用有作用域的生存期。
生存期和註冊選項
為了演示生存期和註冊選項之間的差異,請考慮以下介面,將任務表示為具有唯一識別符號 OperationId
的操作。 根據為以下介面配置操作服務的生存期的方式,容器在類請求時提供相同或不同的服務例項:
public interface IOperation
{
Guid OperationId { get; }
}
public interface IOperationTransient : IOperation
{
}
public interface IOperationScoped : IOperation
{
}
public interface IOperationSingleton : IOperation
{
}
public interface IOperationSingletonInstance : IOperation
{
}
介面在 Operation
類中實現。 Operation
建構函式將生成一個 GUID(如果未提供):
public class Operation : IOperationTransient,
IOperationScoped,
IOperationSingleton,
IOperationSingletonInstance
{
public Operation() : this(Guid.NewGuid())
{
}
public Operation(Guid id)
{
OperationId = id;
}
public Guid OperationId { get; private set; }
}
註冊 OperationService
取決於,每個其他 Operation
型別。 當通過依賴關係注入請求 OperationService
時,它將接收每個服務的新例項或基於從屬服務的生存期的現有例項。
- 如果在請求時建立了臨時服務,則
IOperationTransient
服務的OperationId
與OperationService
的OperationId
不同。OperationService
將接收IOperationTransient
類的新例項。 新例項將生成一個不同的OperationId
。 - 如果按請求建立有作用域的服務,則
IOperationScoped
服務的OperationId
與請求中OperationService
的該 ID 相同。 在請求中,兩個服務共享不同的OperationId
值。 - 如果單一資料庫和單一例項服務只建立一次並在所有請求和所有服務中使用,則
OperationId
在所有服務請求中保持不變。
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.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
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>();
}
IOperationSingletonInstance
服務正在使用已知 ID 為 Guid.Empty
的特定例項。 此型別在使用時很明顯(其 GUID 全部為零)。
示例應用演示了各個請求中和之間的物件生存期。 示例應用的 IndexModel
請求每種 IOperation
型別和 OperationService
。 然後,頁面通過屬性分配顯示所有頁面模型類和服務的 OperationId
值:
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.");
}
}
以下兩個輸出顯示了兩個請求的結果:
第一個請求:
控制器操作:
暫時性: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
值會在一個請求之內和不同請求之間變化:
- 暫時性物件始終不同。 請注意,第一個和第二個請求的暫時性
OperationId
值對於OperationService
操作和在請求內都是不同的。 為每個服務和請求提供了一個新例項。 - 有作用域的物件在一個請求內是相同的,但在請求之間是不同的。
- 單一例項物件對每個物件和每個請求都是相同的(不管
ConfigureServices
中是否提供Operation
例項)。
從 main
呼叫服務
採用 IServiceScopeFactory.CreateScope
建立IServiceScope
,以解析應用作用域中有作用域的服務。 此方法可以用於在啟動時訪問有作用域的服務以便執行初始化任務。 以下示例演示如何在 Program.Main
中獲取 MyScopedService
的上下文:
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
using (var serviceScope = host.Services.CreateScope())
{
var services = serviceScope.ServiceProvider;
try
{
var serviceContext = services.GetRequiredService<MyScopedService>();
// Use the context here
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
}
host.Run();
}
作用域驗證
如果在開發環境中執行應用,預設的服務提供程式會執行檢查,從而確認以下內容:
- 沒有從根服務提供程式直接或間接解析到有作用域的服務。
- 未將有作用域的服務直接或間接注入到單一例項。
呼叫 BuildServiceProvider
時,會建立根服務提供程式。 在啟動提供程式和應用時,根服務提供程式的生存期對應於應用/服務的生存期,並在關閉應用時釋放。
有作用域的服務由建立它們的容器釋放。 如果作用域建立於根容器,則該服務的生存會有效地提升至單一例項,因為根容器只會在應用/服務關閉時將其釋放。 驗證服務作用域,將在呼叫 BuildServiceProvider
時收集這類情況。
有關更多資訊,請參見ASP.NET Core Web 主機
。
請求服務
來自 HttpContext
的 ASP.NET Core
請求中可用的服務通過HttpContext.RequestServices
集合公開。
請求服務表示作為應用的一部分配置和請求的服務。 當物件指定依賴關係時,RequestServices
(而不是 ApplicationServices
)中的型別將滿足這些要求。
通常,應用不應直接使用這些屬性。 相反,通過類建構函式請求類所需的型別,並允許框架注入依賴關係。 這樣生成的類更易於測試。
備註
與訪問RequestServices
集合相比,以建構函式引數的形式請求依賴項是更優先的選擇。
設計能夠進行依賴關係注入的服務
最佳做法是:
- 設計服務以使用依賴關係注入來獲取其依賴關係。
- 避免有狀態的靜態方法呼叫(稱為靜態粘附的實踐)。
- 避免在服務中直接例項化依賴類。 直接例項化將程式碼耦合到特定實現。
遵循物件導向設計的 SOLID 原則,往往會獲得構造良好且易於測試的小型類。
如果一個類似乎有過多的注入依賴關係,這通常表明該類擁有過多的責任並且違反了單一責任原則 (SRP)
。 嘗試通過將某些職責移動到一個新類來重構類。 請記住,Razor Pages
頁模型類和 MVC 控制器類應關注使用者介面問題。 業務規則和資料訪問實現細節應保留在適用於這些分離的關注點
的類中。
服務處理
容器為其建立的 IDisposable
型別呼叫 Dispose
。 如果通過使用者程式碼將例項新增到容器中,則不會自動處理該例項。
// Services that implement IDisposable:
public class Service1 : IDisposable {}
public class Service2 : IDisposable {}
public class Service3 : IDisposable {}
public interface ISomeService {}
public class SomeServiceImplementation : ISomeService, IDisposable {}
public void ConfigureServices(IServiceCollection services)
{
// The container creates the following instances and disposes them automatically:
services.AddScoped<Service1>();
services.AddSingleton<Service2>();
services.AddSingleton<ISomeService>(sp => new SomeServiceImplementation());
// The container doesn't create the following instances, so it doesn't dispose of
// the instances automatically:
services.AddSingleton<Service3>(new Service3());
services.AddSingleton(new Service3());
}
預設服務容器替換
內建的服務容器旨在滿足框架和大多數消費者應用的需求。 我們建議使用內建容器,除非你需要的特定功能不受它支援。 內建容器中找不到第三方容器支援的某些功能:
- 屬性注入
- 基於名稱的注入
- 子容器
- 自定義生存期管理
- 對遲緩初始化的
Func<T>
支援
有關支援介面卡的部分容器列表,請參閱依賴關係注入 readme.md 檔案。
以下示例將內建容器替換為 Autofac:
-
安裝適當的容器包:
-
在
Startup.ConfigureServices
中配置容器並返回IServiceProvider
:public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddMvc(); // Add other framework services // Add Autofac var containerBuilder = new ContainerBuilder(); containerBuilder.RegisterModule<DefaultModule>(); containerBuilder.Populate(services); var container = containerBuilder.Build(); return new AutofacServiceProvider(container); }
要使用第三方容器,
Startup.ConfigureServices
必須返回IServiceProvider
。 -
在
DefaultModule
中配置Autofac
:public class DefaultModule : Module { protected override void Load(ContainerBuilder builder) { builder.RegisterType<CharacterRepository>().As<ICharacterRepository>(); } }
在執行時,使用 Autofac 來解析型別,並注入依賴關係。 要了解有關結合使用 Autofac 和 ASP.NET Core 的詳細資訊,請參閱 Autofac 文件。
執行緒安全
單例服務需要是執行緒安全的。 如果單例服務依賴於一個瞬時服務,那麼瞬時服務可能也需要是執行緒安全的,具體取決於單例使用它的方式。
單個服務的工廠方法,例如AddSingleton<TService>(IServiceCollection, Func<IServiceProvider,TService>)
的第二個引數,不需要是執行緒安全的。 像型別 (static
) 建構函式一樣,它保證由單個執行緒呼叫一次。
建議
-
不支援基於
async/await
和Task
的服務解析。 C# 不支援非同步建構函式,因此推薦的模式是在同步解析服務後使用非同步方法。 -
避免在服務容器中直接儲存資料和配置。 例如,使用者的購物車通常不應新增到服務容器中。 配置應使用
選項模型
。 同樣,避免"資料持有者"物件,也就是僅僅為實現對某些其他物件的訪問而存在的物件。 最好通過 DI 請求實際專案。 -
避免靜態訪問服務(例如,靜態鍵入
IApplicationBuilder.ApplicationServices
以便在其他地方使用)。 -
避免使用服務定位器模式。 例如,可以改為使用 DI 時,不要呼叫
GetService
來獲取服務例項。 要避免的另一個服務定位器變體是注入可在執行時解析依賴項的工廠。 這兩種做法混合了控制反轉
策略。 -
避免靜態訪問
HttpContext
(例如,IHttpContextAccessor.HttpContext
)。
像任何一組建議一樣,你可能會遇到需要忽略某建議的情況。 例外情況很少見–主要是框架本身內部的特殊情況。
DI
是靜態/全域性物件訪問模式的替代方法。 如果將其與靜態物件訪問混合使用,則可能無法實現 DI
的優點。
相關文章
- ASP.NET Core - 依賴注入(三)ASP.NET依賴注入
- ASP.NET Core依賴注入初識與思考ASP.NET依賴注入
- ASP.NET Core依賴注入(DI)ASP.NET依賴注入
- ASP.NET Core 依賴注入(DI)ASP.NET依賴注入
- ASP.NET Core - 依賴注入(二)ASP.NET依賴注入
- ASP.NET Core - 依賴注入(一)ASP.NET依賴注入
- ASP.NET Core - 依賴注入(四)ASP.NET依賴注入
- Asp.net core依賴注入服務生存期踩坑記錄ASP.NET依賴注入
- .NET Core基礎篇之:依賴注入DependencyInjection依賴注入
- ASP.NET Core 依賴注入基本用法ASP.NET依賴注入
- ASP.NET Core 中的依賴注入ASP.NET依賴注入
- .Net Core中依賴注入服務使用總結依賴注入
- ASP.NET Core 依賴注入中的ScopeASP.NET依賴注入
- .Net Core — 依賴注入依賴注入
- [譯] Node.js 基礎知識:沒有依賴關係的 Web 伺服器Node.jsWeb伺服器
- Asp .Net Core 依賴注入依賴注入
- ASP.NET Core基礎知識(四)【路由】ASP.NET路由
- ASP.NET Core基礎知識(一)【概述】ASP.NET
- 服務容器(自己總結)依賴注入依賴注入
- ASP.NET Core 學習筆記 第三篇 依賴注入框架的使用ASP.NET筆記依賴注入框架
- ASP.NET Core 2.2 基礎知識(六)【Configuration】ASP.NET
- .NET Core ASP.NET Core Basic 1-2 控制反轉與依賴注入ASP.NET依賴注入
- Java基礎:如何在IDEA中檢視依賴關係JavaIdea
- Maven 依賴關係Maven
- .net 溫故知新【13】:Asp.Net Core WebAPI 使用依賴注入DIASP.NETWebAPI依賴注入
- Asp.net core自定義依賴注入容器,替換自帶容器ASP.NET依賴注入
- ASP.NET Core 學習筆記 第二篇 依賴注入ASP.NET筆記依賴注入
- ASP.NET Core 中介軟體的使用(二):依賴注入的使用ASP.NET依賴注入
- Util應用框架基礎(一) - 依賴注入框架依賴注入
- ASP.NET Core 2.2 基礎知識(七)【選項】ASP.NET
- 探索 .NET Core 依賴注入的 IServiceCollection依賴注入
- 探索 .NET Core 依賴注入的 IServiceProvider依賴注入IDE
- 關於依賴注入(typescript)依賴注入TypeScript
- ASP.NET Core基礎知識(二)【應用啟動】ASP.NET
- ASP.NET Core 2.2 基礎知識(十)【中介軟體】ASP.NET
- ASP.NET Core 2.2 基礎知識(十三)【伺服器】ASP.NET伺服器
- build task依賴關係UI
- .NET Core開發日誌——依賴注入依賴注入