依赖注入与控制器类以外的类

Posted

技术标签:

【中文标题】依赖注入与控制器类以外的类【英文标题】:Dependency Injection with classes other than a Controller class 【发布时间】:2016-09-08 11:15:26 【问题描述】:

此时我可以轻松地将东西注入到我的控制器中,在某些情况下构建我自己的 ResolverServices 类。 生活是美好的

我不知道该怎么做是让框架自动注入到非控制器类中。起作用的是让框架自动注入我的控制器IOptions,这实际上是我项目的配置:

public class MessageCenterController : Controller

    private readonly MyOptions _options;

    public MessageCenterController(IOptions<MyOptions> options)
    
        _options = options.Value;
    

我正在考虑是否可以为我自己的课程做同样的事情。当我模仿控制器时,我假设我已经接近了,如下所示:

public class MyHelper

    private readonly ProfileOptions _options;

    public MyHelper(IOptions<ProfileOptions> options)
    
        _options = options.Value;
    

    public bool CheckIt()
    
        return _options.SomeBoolValue;
    

我认为我失败的地方是当我这样称呼它时:

public void DoSomething()

    var helper = new MyHelper(??????);

    if (helper.CheckIt())
    
        // Do Something
    

我追查到的问题实际上是所有关于 DI 的讨论都是在控制器级别讨论的。我尝试在 Controller 对象源代码中查找它发生的位置,但那里有点疯狂。

我知道我可以手动创建一个 IOptions 实例并将其传递给 MyHelper 构造函数,但似乎我应该能够让框架执行此操作,因为它适用于 Controllers

【问题讨论】:

当你使用依赖注入时,你不会调用new。永远不要用于应该解决的对象 当我尝试创建 MyHelper 的实例时,我不调用 new? (1) 这听起来太容易了,(2) 这是语法错误。 :-) 是的,这就是依赖注入的全部意义(特别是如果使用管理和执行此实例化的控制容器的反转)。将实例化推到您的服务/类之外,直到 ioc 容器在内部执行该操作。如果您无法通过构造函数注入它,您可以创建一个工厂并将工厂的接口传递给您的服务。它的实现使用容器来解决它,在 ASP.NET Core 案例中,在您的工厂中注入 IServiceProvider 并调用 IMyHelper helper = services.RequestService&lt;IMyHelper&gt;() 【参考方案1】:

下面是一个使用 DI 的工作示例,不涉及任何 MVC 控制器。这是我需要做的来理解这个过程,所以也许它会帮助其他人。

ShoppingCart 对象通过 DI 获得一个 INotifier 实例(通知客户他们的订单。)

using Microsoft.Extensions.DependencyInjection;
using System;

namespace DiSample

    // STEP 1: Define an interface.
    /// <summary>
    /// Defines how a user is notified. 
    /// </summary>
    public interface INotifier
    
        void Send(string from, string to, string subject, string body);
    

    // STEP 2: Implement the interface
    /// <summary>
    /// Implementation of INotifier that notifies users by email.
    /// </summary>
    public class EmailNotifier : INotifier
    
        public void Send(string from, string to, string subject, string body)
        
            // TODO: Connect to something that will send an email.
        
    

    // STEP 3: Create a class that requires an implementation of the interface.
    public class ShoppingCart
    
        INotifier _notifier;

        public ShoppingCart(INotifier notifier)
        
            _notifier = notifier;
        

        public void PlaceOrder(string customerEmail, string orderInfo)
        
            _notifier.Send("admin@store.com", customerEmail, $"Order Placed", $"Thank you for your order of orderInfo");
        

    

    public class Program
    
        // STEP 4: Create console app to setup DI
        static void Main(string[] args)
        
            // create service collection
            var serviceCollection = new ServiceCollection();

            // ConfigureServices(serviceCollection)
            serviceCollection.AddTransient<INotifier, EmailNotifier>();

            // create service provider
            var serviceProvider = serviceCollection.BuildServiceProvider();

            // This is where DI magic happens:
            var myCart = ActivatorUtilities.CreateInstance<ShoppingCart>(serviceProvider);

            myCart.PlaceOrder("customer@home.com", "2 Widgets");

            System.Console.Write("Press any key to end.");
            System.Console.ReadLine();
        
    

【讨论】:

如果我想在另一个我们不访问serviceProvider对象的类或方法中实例化ShoppingCart怎么办? 这里有同样的问题 谢谢,我必须搜索太宽才能找到 ActivatorUtilities.CreateInstance。 如果我没有 serviceProvider 怎么办??!! 谢谢!我将它与 Microsoft.AspNetCore.TestHost 一起使用,创建一个 TestServer 并调用 ActivatorUtilities.CreateInstance(_server.Host.Services);【参考方案2】:

假设MyHelperMyService 使用,而MyService 又被您的控制器使用。

解决这种情况的方法是:

Register MyServiceMyHelperStartup.ConfigureServices 中。

services.AddTransient<MyService>();
services.AddTransient<MyHelper>();

控制器在其构造函数中接收MyService 的实例。

public HomeController(MyService service)  ... 

MyService 构造函数将依次接收MyHelper 的实例。

public MyService(MyHelper helper)  ... 

DI 框架将能够毫无问题地解析整个对象图。如果您担心每次解析对象时都会创建新实例,您可以阅读不同的lifetime and registration options,例如单例或请求生命周期。

当您认为必须手动创建某个服务的实例时,您应该非常怀疑,因为您最终可能会出现在 service locator anti-pattern 中。最好将创建对象留给 DI 容器。如果您真的遇到这种情况(假设您创建了一个抽象工厂),那么您可以直接使用IServiceProvider(在构造函数中请求IServiceProvider 或使用exposed in the httpContext)。

var foo = serviceProvider.GetRequiredService<MyHelper>();

我建议阅读有关 ASP.Net 5 DI 框架和一般依赖注入的具体 documentation。

【讨论】:

我遇到的问题是我使用与数据库交互的后台服务,所以使用 dbcontext 的作用域生命周期不起作用,对吗?如何正确使用 DI 与 EF 核心和后台服务?【参考方案3】:

不幸的是,没有直接的方法。我设法使它工作的唯一方法是创建一个静态类并在其他任何地方使用它,如下所示:

public static class SiteUtils


    public static string AppName  get; set; 

    public static string strConnection  get; set; 
 

然后在你的启动类中,填写如下:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)

    //normal as detauls , removed for space 
    // set my variables all over the site

    SiteUtils.strConnection = Configuration.GetConnectionString("DefaultConnection");
    SiteUtils.AppName = Configuration.GetValue<string>("AppName");

虽然这是一个不好的模式,因为这将在应用程序的整个生命周期中保留,我找不到更好的方法在控制器之外使用它。

【讨论】:

对我来说效果很好。有用且非常简单。【参考方案4】:

这是一个更完整的示例,可以直接回答 OP 的问题,基于当前的 .NET Core 2.2 DI 文档here。添加此答案是因为它可能对刚接触 .NET Core DI 的人有所帮助,并且因为此问题是 Google 的热门搜索结果。

首先,为MyHelper添加一个接口:

public interface IMyHelper

    bool CheckIt();

其次,更新MyHelper类实现接口(在Visual Studio中,按ctrl-.实现接口):

public class MyHelper : IMyHelper

    private readonly ProfileOptions _options;

    public MyHelper(IOptions<ProfileOptions> options)
    
        _options = options.Value;
    

    public bool CheckIt()
    
        return _options.SomeBoolValue;
    

第三,将接口注册为框架提供的服务在DI服务容器中。为此,在 Startup.cs 的 ConfigureServices 方法中使用具体类型 MyHelper 注册 IMyHelper 服务。

public void ConfigureServices(IServiceCollection services)

    ...
    services.AddScoped<IMyHelper, MyHelper>();
    ...

第四,创建一个私有变量来引用服务的实例。将服务作为参数传递给构造函数(通过构造函数注入),然后使用服务实例初始化变量。通过私有变量引用自定义类的此实例上的任何属性或调用方法。

public class MessageCenterController : Controller

    private readonly MyOptions _options;
    private readonly IMyHelper _myHelper;

    public MessageCenterController(
        IOptions<MyOptions> options,
        IMyHelper myHelper
    )
    
        _options = options.value;
        _myHelper = myHelper;
    

    public void DoSomething()
    
        if (_myHelper.CheckIt())
        
            // Do Something
        
    

【讨论】:

【参考方案5】:

您可以使用 Activator.CreateInstance()。这是它的包装函数。你的使用方法如下。

var determinedProgrammatically = "My.NameSpace.DemoClass1"; // implements IDemo interface
var obj = CreateInstance<My.NameSpace.IDemo, string>(determinedProgrammatically, "This goes into the parameter of the constructor.", "Omit this parameter if your class lives in the current assembly");

现在你有一个 obj 的实例,它是从以编程方式确定的类型实例化的。这个 obj 可以注入到非控制器类中。

public TInterface CreateInstance<TInterface, TParameter>(string typeName, TParameter constructorParam, string dllName = null)

    var type = dllName == null ? System.Type.GetType(typeName) :
            System.AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName.StartsWith(dllName, System.StringComparison.OrdinalIgnoreCase)).GetType(typeName);
    return (TInterface)System.Activator.CreateInstance(type, constructorParam);


PS:您可以遍历 System.AppDomain.CurrentDomain.GetAssemblies() 以确定包含您的类的程序集的名称。此名称用于包装函数的第三个参数。

【讨论】:

【参考方案6】:

TL;DR:您可以将单例保存在 static var 中,然后从其他类访问它,但这是一种反模式,请谨慎使用。

加长版:

根据这个问题Resolving instances with ASP.NET Core DI from within ConfigureServices

然后可以注入在ConfigureServices() 中注册的任何服务 进入Configure()方法

public void ConfigureServices(IServiceCollection services)

    services.AddSingleton<FooService>();


public void Configure(IApplicationBuilder app, FooService fooService)

    FooServiceInstance = fooService;


public static FooService FooServiceInstance  get; private set; 

然后从你的其他代码MyStartupClass.FooService.DoStuff()调用它

【讨论】:

以上是关于依赖注入与控制器类以外的类的主要内容,如果未能解决你的问题,请参考以下文章

PHP中的服务容器与依赖注入的思想

PHP中的服务容器与依赖注入的思想

spring四种依赖注入方式

spring四种依赖注入方式

SpringIoc依赖注入依赖反转

java控制反转及依赖注入