与 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

71263 次浏览

Let's say MyHelper is used by MyService which in turn is used by your controller.

The way to resolve this situation is:

  • Register both MyService and MyHelper in Startup.ConfigureServices.

    services.AddTransient<MyService>();
    services.AddTransient<MyHelper>();
    
  • The controller receives an instance of MyService in its constructor.

    public HomeController(MyService service) { ... }
    
  • MyService constructor will in turn receive an instance of MyHelper.

    public MyService(MyHelper helper) { ... }
    

The DI framework will be able resolve the whole object graph without problems. If you are worried about new instances being created every time an object is resolved, you can read about the different lifetime and registration options like the singleton or request lifetimes.

You should be really suspicious when you think you have to manually create an instance of some service, as you might end up in the service locator anti-pattern. Better leave creating the objects to the DI Container. If you really find yourself in that situation (let's say you create an abstract factory), then you could use the IServiceProvider directly (Either request an IServiceProvider in your constructor or use the one exposed in the httpContext).

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

I would recommend reading the specific documentation about the ASP.Net 5 DI framework and about dependency injection in general.

Unfortunately there is no direct way. The only way I managed to make it work is by creating a static class and using that everywhere else as below:

public static class SiteUtils
{


public static string AppName { get; set; }


public static string strConnection { get; set; }
 

}

Then in your startup class, fill it in as below:

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");
}

Although this is bad pattern, as this will stay for the whole life cycle of the application and I couldn't find better way to use it outside controller.

Below is a working example of using DI without anything that involves MVC Controllers. This is what I needed to do to understand the process, so maybe it will help somebody else.

The ShoppingCart object gets, via DI, an instance of INotifier (which notifies the customer of their order.)

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();
}
}
}

Here's a more complete example to directly answer the OP's question, based on the current .NET Core 2.2 DI documentation here. Adding this answer since it may help someone that's new to .NET Core DI, and because this question is Google's top search result.

First, add an interface for MyHelper:

public interface IMyHelper
{
bool CheckIt();
}

Second, update the MyHelper class to implement the interface (in Visual Studio, press ctrl-. to implement the interface):

public class MyHelper : IMyHelper
{
private readonly ProfileOptions _options;


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


public bool CheckIt()
{
return _options.SomeBoolValue;
}
}

Third, register the interface as a framework-provided service in the DI service container. Do this by registering the IMyHelper service with the concrete type MyHelper in the ConfigureServices method in Startup.cs.

public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IMyHelper, MyHelper>();
...
}

Fourth, create a private variable to reference an instance of the service. Pass the service as an argument in the constructor (via constructor injection) then initialize the variable with the service instance. Reference any properties or call methods on this instance of the custom class via the private variable.

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
}
}
}

You may use Activator.CreateInstance(). Here is a wrapper function for it. The way you use this is as follows.

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");

Now you have an instance of obj which is instantiated from type determined programmatically. This obj can be injected into non controller classes.

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: You may iterate through System.AppDomain.CurrentDomain.GetAssemblies() to determine the name of the assembly that houses your class. This name is used in the 3rd parameter of the wrapper function.

TL;DR: You can save a singleton in a static var and then access it form other classes, but this an anti-pattern, use with caution.

Long version:

As per this question Resolving instances with ASP.NET Core DI from within ConfigureServices

Any services registered in ConfigureServices() can then be injected into the Configure() method

public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<FooService>();
}


public void Configure(IApplicationBuilder app, FooService fooService)
{
FooServiceInstance = fooService;
}


public static FooService FooServiceInstance { get; private set; }

And then call it from your other code MyStartupClass.FooService.DoStuff()