Net Core非控制器的依赖注入

28
似乎很疯狂,一件像这样的事情居然让我头痛不已。但是问题就在这里:
如何为非控制器类使用Net Core的内置依赖注入?请提供一个包含实例化的示例。
谢谢。

3
与控制器相同,您在类的构造函数中放置一个依赖项,然后在Startup.cs中连接依赖项。就依赖注入而言,控制器并没有什么特别之处。 - Joe Audette
我们能看到您当前的注册吗? - steamrolla
请查看文档:https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/dependency-injection - Nkosi
1
@JoeAudette:仅出于完整性的考虑,控制器(Controller)、视图组件(ViewComponent)和标签助手(TagHelper)在 DI 中是特殊的,因为默认情况下它们不会被 DI 容器本身解析,而是从某些工厂进行解析。通过调用扩展方法,可以改变这种情况。http://stackoverflow.com/documentation/asp.net-core/1949/dependency-injection/23212/resolve-controllers-viewcomponents-and-taghelpers-via-dependency-injection#t=201701121334489459167 在使用第三方容器并对特定类 (ClassA 对 Controller A,ClassB 对 Controller B) 进行每类注入时,这一点尤其值得注意。 - Tseng
@Tseng 非常有趣!谢谢!我不知道那个。 - Joe Audette
显示剩余4条评论
4个回答

8
您可以轻松地定义一个带有一个属性的静态类,例如:
public static class StaticServiceProvider
{
    public static IServiceProvider Provider { get; set; }    
}

在定义类之后,您需要在Startup.ConfigureServices方法中限定服务:

public void ConfigureServices(IServiceCollection services)
{
    //TODO: ...

    services.AddScoped<IUnitOfWork, HttpUnitOfWork>();            
    services.AddSingleton<ISomeInterface, ISomeImplementation>();
}

然后,在 Startup.Configure 方法内部,您可以将提供程序设置为静态类属性:

public void Configure(IApplicationBuilder app, ...)
{
    StaticServiceProvider.Provider = app.ApplicationServices;

    //TODO: ...
}


现在您可以在应用程序的几乎任何地方轻松调用StaticServiceProvider.Provider.GetService方法:
var unitOfWork = (IUnitOfWork)StaticServiceProvider.Provider.GetService(typeof(IUnitOfWork));


这个能在.NET 5隔离应用程序中工作吗?我似乎找不到IApplicationBuilder。 - Ben Sewards

6

只需将类作为服务。

在 startup.cs 中。

services.AddScoped<AccountBusinessLayer>();

然后在控制器中,与您为其他服务做的一样:

private readonly AccountBusinessLayer _ABL;

像其他服务一样,在构造函数中包含:

 public AccountController(
    UserManager<ApplicationUser> userManager,
    SignInManager<ApplicationUser> signInManager,IOptions<IdentityCookieOptions> identityCookieOptions,
    IEmailSender emailSender,
    ISmsSender smsSender,
    ILoggerFactory loggerFactory,
    RoleManager<IdentityRole> roleManager,
    AccountBusinessLayer ABL
  )
{
  _userManager = userManager;
  _signInManager = signInManager;
  _externalCookieScheme = identityCookieOptions.Value.ExternalCookieAuthenticationScheme;
  _emailSender = emailSender;
  _smsSender = smsSender;
  _logger = loggerFactory.CreateLogger<AccountController>();
  _roleManager = roleManager;
  _ABL = ABL;
}

5
尝试在非控制器中使用此方法,但无法正常工作。当尝试注入其他服务时,AccountBusinessLayer的构造函数从未运行。 - Baked Inhalf
16
这并没有回答这个问题。 - johnye2e
1
也许我没有表达清楚。将想要使用注入服务的类也变成一个服务。消费者=服务=>将其他服务注入到消费服务中。 - John Arundell

5
我不确定这是最好的答案,但我决定按照以下方式进行:
1)根据@BrunoLM在此问题Resolving instances with ASP.NET Core DI中提供的答案建议,由@SystemCrash建议,我创建了一个名为UnderstandingDependencyInjection的新项目,并粘贴了代码示例。
重要提示:除非您访问上面引用的链接(#1),否则下面描述的内容将没有意义。下面看到的是另一个SO问题中其他用户提供的答案的部分解决方案。
2)接下来,我创建了另一个名为OtherService的类。我添加了一个名为DoSomething()的方法,它依赖于TestService。
3)在OtherService的构造函数中,我请求IServiceProvider以获取ITestService的具体实现,以便调用其GenerateRandom()方法。
4)回到HomeController.cs,我只需将IServiceProvider引用传递给OtherService的构造函数即可。
所以,这就是我拥有的:

OtherService.cs

using System;
using Microsoft.Extensions.DependencyInjection;

namespace UnderstandingDependencyInjection.Services
{
    public class OtherService
    {
        private readonly ITestService _testService;

        public OtherService(IServiceProvider serviceProvider)
        {
            _testService = serviceProvider.GetService<ITestService>();
        }

        public int DoSomething()
        {
            var rnd = _testService.GenerateRandom();
            return rnd * 2;
        }
    }
}

HomeController.cs

using Microsoft.Extensions.DependencyInjection;
using UnderstandingDependencyInjection.Services;

namespace UnderstandingDependencyInjection.Controllers
{
    public class HomeController : Controller
    {
        private readonly ITestService _testService;
        private readonly IServiceProvider _serviceProvider;

        public HomeController(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
            _testService = serviceProvider.GetService<ITestService>();
        }

        public IActionResult Index()
        {
            // This works!
            // var rnd = _testService.GenerateRandom();

            // What if I need to reference the TestService 
            // from another service? I.e., OtherService?
            var otherService = new OtherService(_serviceProvider);

            var rnd = otherService.DoSomething();

            ViewBag.RandomNumber = rnd;
            return View();
        }

因此,总结一下,这种技术的关键是传递控制器接收到的IServiceProvider的具体引用...将其从控制器传递到任何其他需要在ASP.NET Core的DI框架中注册任何服务的自定义类中。

关于依赖TestService的静态方法怎么办?

但是,我可能不想/需要创建OtherService的实例。我可能只想静态调用一个方法,但是该方法对由ASP.NET Core MVC的依赖注入框架管理的服务有依赖关系。现在怎么办?

在这种情况下,我能想到的最好的方法是,在静态方法调用时传递对该引用。看起来很丑陋,我希望有更优雅的方式...但这是我找出来的。

5)在前面的步骤(上面)基础上,我添加了一个名为StaticService的新类。

6)我创建了一个DoSomething方法,它将IServiceProvider作为参数。

7)我使用IServiceProvider的具体实例获取ITestService的具体实例。我使用这个实例调用GenerateRandom()。

8)从控制器中调用StaticService.DoSomething()方法,并传递我所持有的IServiceProvider的具体实例。

StaticService.cs

using Microsoft.Extensions.DependencyInjection;

namespace UnderstandingDependencyInjection.Services
{
    public class StaticService
    {
        // No constructors

        public static int DoSomething(IServiceProvider serviceProvider)
        {

            var testService = serviceProvider.GetService<ITestService>();
            var rnd = testService.GenerateRandom();
            return rnd * 3;
        }
    }
}

HomeController.cs

    public IActionResult Index()
    {
        // This works!
        // var rnd = _testService.GenerateRandom();

        // What if I need to reference the TestService 
        // from another service? I.e., OtherService?
        //var otherService = new OtherService(_serviceProvider);
        //var rnd = otherService.DoSomething();

        // What if I need to reference the TestService
        // from another service with a STATIC method?
        // Best I can tell, you have to pass the 
        // ServiceProvider in on the method call.
        var rnd = StaticService.DoSomething(_serviceProvider);

        ViewBag.RandomNumber = rnd;
        return View();
    }

但是传递ServiceProvider不是反模式吗?

简而言之,是的。你会发现在代码中到处都在传递ServiceProvider。有人会认为这样做会让每个控制器和类都可以访问ASP.NET Core的DI中注册的所有服务。这是正确的,但似乎不好。

但你有什么其他选择呢?每个依赖于你的服务的类也应该被定义为一个服务并注册到DI中吗?换句话说,我应该创建IOtherService,然后在其构造函数中传递一个具体的ITestService吗?

我可以这样做,但是现在我的控制器构造函数需要同时拥有ITestService和IOtherService。换句话说,为了正确工作,控制器需要知道OtherService如何完成其工作以及它内部使用ITestService。这似乎也不好。

该怎么办?


什么是最好的答案?

坦白地说,我认为最好的答案可以在这里找到:

在ASP.NET中使用依赖注入和工厂模式传递服务

@Steven在他的回答中说:

这确实意味着您可能需要从ASP.NET Core的内置DI容器转向更具功能的DI库,因为内置容器无法为ILogger进行上下文感知注册,同时自动连接其他构造函数依赖项。


0

实际上有很多方法可以注入您的依赖项,您会在控制器上找到最常见的一种。还有这个变体

var someService = (ISomeService)HttpContext.RequestServices.GetService(typeof(ISomeService));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接