![](/img/trans.png)
[英]How to access a service added to DI container from inside Asp.Net Core's ConfigureServices method
[英]Resolving instances with ASP.NET Core DI from within ConfigureServices
如何使用 ASP.NET Core MVC 內置依賴注入框架手動解析類型?
設置容器很容易:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddTransient<ISomeService, SomeConcreteService>();
}
但是如何在不執行注入的情況下解決ISomeService
? 例如,我想這樣做:
ISomeService service = services.Resolve<ISomeService>();
IServiceCollection
中沒有這樣的方法。
IServiceCollection
接口用於構建依賴注入容器。 完全構建后,它會組合成一個IServiceProvider
實例,您可以使用它來解析服務。 您可以將IServiceProvider
注入任何類。 IApplicationBuilder
和HttpContext
類也可以分別通過它們的ApplicationServices
或RequestServices
屬性提供服務提供者。
IServiceProvider
定義了一個GetService(Type type)
方法來解析服務:
var service = (IFooService)serviceProvider.GetService(typeof(IFooService));
還有幾種方便的擴展方法可用,例如serviceProvider.GetService<IFooService>()
(為Microsoft.Extensions.DependencyInjection
添加一個using
)。
運行時的托管服務提供商可以注入一定的服務到的構造函數Startup
類,如IConfiguration
, IWebHostEnvironment
( IHostingEnvironment
在3.0之前的版本), ILoggerFactory
和IServiceProvider
。 請注意,后者是由托管層構建的實例,僅包含用於啟動應用程序的基本服務。
ConfigureServices()
方法不允許注入服務,它只接受IServiceCollection
參數。 這是有道理的,因為ConfigureServices()
是您注冊應用程序所需服務的地方。 但是,您可以在此處使用在啟動構造函數中注入的服務,例如:
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
// Use Configuration here
}
然后可以將任何在ConfigureServices()
注冊的服務注入到Configure()
方法中; 您可以在IApplicationBuilder
參數后添加任意數量的服務:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IFooService>();
}
public void Configure(IApplicationBuilder app, IFooService fooService)
{
fooService.Bar();
}
如果需要手動解析服務,最好在Configure()
方法中使用IApplicationBuilder
提供的ApplicationServices
:
public void Configure(IApplicationBuilder app)
{
var serviceProvider = app.ApplicationServices;
var hostingEnv = serviceProvider.GetService<IHostingEnvironment>();
}
可以在Startup
類的構造函數中傳遞並直接使用IServiceProvider
,但如上所述,這將包含有限的 services 子集,因此實用性有限:
public Startup(IServiceProvider serviceProvider)
{
var hostingEnv = serviceProvider.GetService<IWebHostEnvironment>();
}
如果您必須在ConfigureServices()
方法中解析服務,則需要不同的方法。 你可以建立一個中間IServiceProvider
從IServiceCollection
其中包含已注冊到該點的服務實例:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IFooService, FooService>();
// Build the intermediate service provider
var sp = services.BuildServiceProvider();
// This will succeed.
var fooService = sp.GetService<IFooService>();
// This will fail (return null), as IBarService hasn't been registered yet.
var barService = sp.GetService<IBarService>();
}
請注意:通常您應該避免在ConfigureServices()
方法內解析服務,因為這實際上是您配置應用程序服務的地方。 有時您只需要訪問IOptions<MyOptions>
實例。 您可以通過將IConfiguration
實例中的值綁定到MyOptions
實例來實現這MyOptions
(這實際上是選項框架所做的):
public void ConfigureServices(IServiceCollection services)
{
var myOptions = new MyOptions();
Configuration.GetSection("SomeSection").Bind(myOptions);
}
或者對AddSingleton/AddScoped/AddTransient
使用重載:
// Works for AddScoped and AddTransient as well
services.AddSingleton<IBarService>(sp =>
{
var fooService = sp.GetRequiredService<IFooService>();
return new BarService(fooService);
}
手動解析服務(又名服務定位器)通常被認為是一種反模式。 雖然它有它的用例(用於框架和/或基礎設施層),但您應該盡可能避免它。
手動解析實例涉及使用IServiceProvider
接口:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IMyService, MyService>();
var serviceProvider = services.BuildServiceProvider();
var service = serviceProvider.GetService<IMyService>();
}
public void Configure(
IApplicationBuilder application,
IServiceProvider serviceProvider)
{
// By type.
var service1 = (MyService)serviceProvider.GetService(typeof(MyService));
// Using extension method.
var service2 = serviceProvider.GetService<MyService>();
// ...
}
public void Configure(
IApplicationBuilder application,
IWebHostEnvironment webHostEnvironment)
{
application.ApplicationServices.GetService<MyService>();
}
某些類型可以作為方法參數注入:
public class Startup
{
public Startup(
IHostingEnvironment hostingEnvironment,
ILoggerFactory loggerFactory)
{
}
public void ConfigureServices(
IServiceCollection services)
{
}
public void Configure(
IApplicationBuilder application,
IHostingEnvironment hostingEnvironment,
IServiceProvider serviceProvider,
ILoggerFactory loggerfactory,
IApplicationLifetime applicationLifetime)
{
}
}
[HttpGet("/some-action")]
public string SomeAction([FromServices] IMyService myService) => "Hello";
如果您使用模板生成一個應用程序,您將在Startup
類中擁有如下內容:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
services.AddMvc();
}
然后您可以在那里添加依賴項,例如:
services.AddTransient<ITestService, TestService>();
如果你想在你的控制器上訪問ITestService
,你可以在構造函數上添加IServiceProvider
,它將被注入:
public HomeController(IServiceProvider serviceProvider)
然后你可以解析你添加的服務:
var service = serviceProvider.GetService<ITestService>();
請注意,要使用通用版本,您必須在擴展名中包含命名空間:
using Microsoft.Extensions.DependencyInjection;
ITestService.cs
public interface ITestService
{
int GenerateRandom();
}
測試服務.cs
public class TestService : ITestService
{
public int GenerateRandom()
{
return 4;
}
}
Startup.cs(配置服務)
public void ConfigureServices(IServiceCollection services)
{
services.AddApplicationInsightsTelemetry(Configuration);
services.AddMvc();
services.AddTransient<ITestService, TestService>();
}
家庭控制器.cs
using Microsoft.Extensions.DependencyInjection;
namespace Core.Controllers
{
public class HomeController : Controller
{
public HomeController(IServiceProvider serviceProvider)
{
var service = serviceProvider.GetService<ITestService>();
int rnd = service.GenerateRandom();
}
如果您只需要解析一個依賴項以將其傳遞給您正在注冊的另一個依賴項的構造函數,則可以執行此操作。
假設您有一個接受字符串和 ISomeService 的服務。
public class AnotherService : IAnotherService
{
public AnotherService(ISomeService someService, string serviceUrl)
{
...
}
}
當您在 Startup.cs 中注冊它時,您需要執行以下操作:
services.AddScoped<IAnotherService>(ctx =>
new AnotherService(ctx.GetService<ISomeService>(), "https://someservice.com/")
);
您可以通過這種方式在 AuthorizeAttribute 等屬性中注入依賴項
var someservice = (ISomeService)context.HttpContext.RequestServices.GetService(typeof(ISomeService));
我知道這是一個老問題,但我很驚訝這里沒有一個相當明顯和令人作嘔的黑客。
您可以利用定義自己的 ctor 函數的能力,在定義服務時從服務中獲取必要的值……顯然,除非您明確刪除/清除並重新添加服務的定義,否則每次請求服務時都會運行這項服務在開發 ctor 的第一個建設中。
此方法的優點是不需要您在配置服務期間構建或使用服務樹。 您仍在定義如何配置服務。
public void ConfigureServices(IServiceCollection services)
{
//Prey this doesn't get GC'd or promote to a static class var
string? somevalue = null;
services.AddSingleton<IServiceINeedToUse, ServiceINeedToUse>(scope => {
//create service you need
var service = new ServiceINeedToUse(scope.GetService<IDependantService>())
//get the values you need
somevalue = somevalue ?? service.MyDirtyHack();
//return the instance
return service;
});
services.AddTransient<IOtherService, OtherService>(scope => {
//Explicitly ensuring the ctor function above is called, and also showcasing why this is an anti-pattern.
scope.GetService<IServiceINeedToUse>();
//TODO: Clean up both the IServiceINeedToUse and IOtherService configuration here, then somehow rebuild the service tree.
//Wow!
return new OtherService(somevalue);
});
}
修復此模式的方法是讓OtherService
顯式依賴IServiceINeedToUse
,而不是隱式依賴它或其方法的返回值……或以其他方式顯式解析該依賴項。
您可以通過這種方式使用 IApplicationBuilder 實例注入依賴項
public void Configure(IApplicationBuilder app)
{
//---------- Your code
using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var resultLogic = serviceScope.ServiceProvider.GetService<IResultLogic>();
resultLogic.YourMethod();
}
//---------- Your code
}
公共無效配置服務(IServiceCollection 服務){
services.AddSingleton<ISelfServiceConfigLoad, SelfServiceConfigLoader>();
var sp = services.BuildServiceProvider();
var configservice = sp.GetServices<ISelfServiceConfigLoad>();
services.AddSingleton<IExtractor, ConfigExtractor>( sp =>
{
var con = sp.GetRequiredService<ISelfServiceConfigLoad>();
var config = con.Load();
return new ConfigExtractor(config.Result);
});
services.AddSingleton<IProcessor<EventMessage>, SelfServiceProcessor>();
services.AddTransient<ISolrPush, SolrDataPush>();
services.AddSingleton<IAPICaller<string, string>, ApiRestCaller<string, string>>();
services.AddSingleton<IDataRetriever<SelfServiceApiRequest, IDictionary<string, object>>, SelfServiceDataRetriever>();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDbContext<ConfigurationRepository>(options =>
options.UseSqlServer(Configuration.GetConnectionString("SqlConnectionString")));
services.AddScoped<IConfigurationBL, ConfigurationBL>();
services.AddScoped<IConfigurationRepository, ConfigurationRepository>();
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.