除Controller类之外的其他类的依赖注入


84

在这一点上,我可以轻松地将其注入Controller中,在某些情况下,可以构建自己的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


8
使用依赖项注入时,您不会调用new。永远不要为应该解决的物体

1
当我尝试创建MyHelper的实例时,我不叫new吗?(1)听起来太简单了,(2)这是语法错误。:-)
罗伯特·保尔森

2
是的,这就是依赖项注入的全部要点(特别是如果使用管理和执行此实例化的控制容器的倒置)。要将实例化推到您的服务/类之外,直到ioc容器在内部完成该操作。在无法通过构造函数注入的情况下,可以创建工厂并将工厂的接口传递给服务。它的实现使用容器来解决它,在ASP.NET Core案例中注入IServiceProvider您的工厂并调用IMyHelper helper = services.RequestService<IMyHelper>()
Tseng

Answers:


42

下面是一个使用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();
        }
    }
}

17
如果我想ShoppingCart在另一个我们不访问该serviceProvider对象的类或方法中实例化该怎么办?
Bagherani

1
在这里有同样的问题
Casey

4
谢谢,我不得不搜索得太宽,无法访问ActivatorUtilities.CreateInstance。
H. Tugkan Kibar

1
如果我没有serviceProvider怎么办?
HelloWorld '18

1
谢谢!我将其与Microsoft.AspNetCore.TestHost一起使用,创建TestServer并调用ActivatorUtilities.CreateInstance <MyCustomerController>(_ server.Host.Services);
Tolga

35

假设MyHelper使用MyService,然后由您的控制器使用。

解决这种情况的方法是:

  • 注册MyServiceMyHelperStartup.ConfigureServices

    services.AddTransient<MyService>();
    services.AddTransient<MyHelper>();
    
  • 控制器MyService在其构造函数中接收的实例。

    public HomeController(MyService service) { ... }
    
  • MyService构造函数将依次接收的实例MyHelper

    public MyService(MyHelper helper) { ... }
    

DI框架将能够毫无问题地解决整个对象图。如果您担心每次解决对象时都会创建新实例,则可以了解不同的生存期和注册选项,例如单例或请求生存期。

当您认为必须手动创建某些服务的实例时,您应该会非常怀疑,因为您可能最终会遇到服务定位器反模式。最好将创建对象留给DI容器。如果您确实处于这种情况下(假设您创建了一个抽象工厂),则可以IServiceProvider直接使用(IServiceProvider在构造函数中请求一个,或使用httpContext中公开的一个)。

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

我建议阅读有关ASP.Net 5 DI框架以及有关依赖项注入的特定文档


我的问题是我使用了与数据库交互的后台服务,因此dbcontext的作用域生存期不起作用,对吗?您如何在EF核心和后台服务中正确使用DI?

6

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

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 2.2核心文档DI在这里。添加此答案,因为它可能会对.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
        }
    }
}

0

您可以使用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()以确定容纳您的类的程序集的名称。该名称在包装函数的第3个参数中使用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.