如何使用 ASP.NET Core MVC 内置依赖注入框架手动解析类型?
设置容器很简单:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddTransient<ISomeService, SomeConcreteService>();
}
但是如何在不执行注入的情况下解决 ISomeService
?例如,我想这样做:
ISomeService service = services.Resolve<ISomeService>();
IServiceCollection
中没有此类方法。
ConfigureServices()
方法(使用 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
(在 3.0 之前的版本中为 IHostingEnvironment
)、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
,但如上所述,这将包含有限的服务子集,因此实用性有限:
public Startup(IServiceProvider serviceProvider)
{
var hostingEnv = serviceProvider.GetService<IWebHostEnvironment>();
}
如果您必须在 ConfigureServices()
方法中解析服务,则需要使用不同的方法。您可以从 IServiceCollection
实例构建一个中间 IServiceProvider
,其中包含到那时已注册的服务:
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
实例来完成此操作(这本质上是选项框架所做的):
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);
}
手动解析服务(又名服务定位器)是 generally considered an anti-pattern。虽然它有其用例(用于框架和/或基础设施层),但您应该尽可能避免使用它。
手动解析实例涉及使用 IServiceProvider
接口:
解决 Startup.ConfigureServices 中的依赖关系
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IMyService, MyService>();
var serviceProvider = services.BuildServiceProvider();
var service = serviceProvider.GetService<IMyService>();
}
解决 Startup.Configure 中的依赖关系
public void Configure(
IApplicationBuilder application,
IServiceProvider serviceProvider)
{
// By type.
var service1 = (MyService)serviceProvider.GetService(typeof(MyService));
// Using extension method.
var service2 = serviceProvider.GetService<MyService>();
// ...
}
在 ASP.NET Core 3 中解决 Startup.Configure 中的依赖关系
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";
GetService
是 Microsoft.Extensions.DependencyInjection
命名空间中的扩展方法。
如果您使用模板生成应用程序,您将在 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/")
);
ISomeService
对我来说仍然是空的。
services.AddScoped<IAnotherService>(ctx => ActivatorUtilities.CreateInstance<AnotherService>(ctx, "https://someservice.com/") );
应该是首选解决方案。
您可以通过这种方式在 AuthorizeAttribute 等属性中注入依赖项
var someservice = (ISomeService)context.HttpContext.RequestServices.GetService(typeof(ISomeService));
var someservice = context.HttpContext.RequestServices.GetService<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>();
}
IServiceCollection
,一些手动创建的类(超出中间件范围),调度程序我的情况,它定期需要一些服务来生成和发送电子邮件。ConfigureServices
中的服务并且该服务是单例,它将是与您的Controller
使用的不同的单例!我认为这是因为它使用了不同的IServiceProvider
- 为避免这种情况,请不要通过BuildServiceProvider
解决,而是将您对单例的查找从ConfigureServices
移动到Startup.cs
中的Configure(..other params, IServiceProvider serviceProvider)
IServiceProvider
实例,所以它将创建一个新的单例实例。您可以通过从ConfigureServices
方法返回服务提供者实例来避免这种情况,这样它也将成为您的应用程序使用的容器。collection.BuildServiceProvider();
,谢谢!