class 控制器 class 以外的依赖注入
Dependency Injection with classes other than a Controller class
此时我正在轻松地将东西注入我的控制器,在某些情况下构建我自己的 ResolverServices class。 生活是美好的.
我不知道该怎么做是让框架自动注入非控制器 classes。什么工作是让框架自动注入我的控制器IOptions
,这实际上是我项目的配置:
public class MessageCenterController : Controller
{
private readonly MyOptions _options;
public MessageCenterController(IOptions<MyOptions> options)
{
_options = options.Value;
}
}
我在想我是否可以为我自己的 classes 做同样的事情。当我模仿控制器时,我假设我很接近,就像这样:
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
.
假设 MyHelper
被 MyService
使用,而后者又被您的控制器使用。
解决这种情况的方法是:
Register MyService
和 MyHelper
在 Startup.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. 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>();
我建议阅读有关 ASP.Net 5 DI 框架和一般依赖注入的具体 documentation。
很遗憾,没有直接的方法。我设法让它工作的唯一方法是创建一个静态 class 并在其他任何地方使用它,如下所示:
public static class SiteUtils
{
public static string AppName { get; set; }
public static string strConnection { get; set; }
}
然后在你的启动class中,填写如下:
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");
}
虽然这是一个糟糕的模式,因为这将在应用程序的整个生命周期中存在,我找不到更好的方法在控制器之外使用它。
下面是一个在不涉及 MVC 控制器的情况下使用 DI 的工作示例。这是我需要做的来理解这个过程,所以也许它会对其他人有所帮助。
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();
}
}
}
根据当前的 .NET Core 2.2 DI 文档,这里有一个更完整的示例可以直接回答 OP 的问题 here。添加此答案是因为它可能对 .NET Core DI 的新手有所帮助,并且因为此问题是 Google 的热门搜索结果。
首先,为MyHelper添加一个接口:
public interface IMyHelper
{
bool CheckIt();
}
其次,更新MyHelperclass实现接口(在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>();
...
}
第四,创建一个私有变量来引用服务的实例。将服务作为参数传递给构造函数(通过构造函数注入),然后使用服务实例初始化变量。通过私有变量引用此自定义 class 实例的任何属性或调用方法。
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
}
}
}
您可以使用 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 的实例,它是从以编程方式确定的类型实例化的。此对象可以注入非控制器 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:您可以遍历 System.AppDomain.CurrentDomain.GetAssemblies() 以确定容纳您的 class 的程序集的名称。此名称用于包装函数的第三个参数。
TL;DR: 您可以将单例保存在 static var 中,然后从其他 类 访问它,但这是一种反模式,请谨慎使用。
长版:
根据这个问题
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; }
然后从你的其他代码调用它MyStartupClass.FooService.DoStuff()
此时我正在轻松地将东西注入我的控制器,在某些情况下构建我自己的 ResolverServices class。 生活是美好的.
我不知道该怎么做是让框架自动注入非控制器 classes。什么工作是让框架自动注入我的控制器IOptions
,这实际上是我项目的配置:
public class MessageCenterController : Controller
{
private readonly MyOptions _options;
public MessageCenterController(IOptions<MyOptions> options)
{
_options = options.Value;
}
}
我在想我是否可以为我自己的 classes 做同样的事情。当我模仿控制器时,我假设我很接近,就像这样:
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
.
假设 MyHelper
被 MyService
使用,而后者又被您的控制器使用。
解决这种情况的方法是:
Register
MyService
和MyHelper
在Startup.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. 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>();
我建议阅读有关 ASP.Net 5 DI 框架和一般依赖注入的具体 documentation。
很遗憾,没有直接的方法。我设法让它工作的唯一方法是创建一个静态 class 并在其他任何地方使用它,如下所示:
public static class SiteUtils
{
public static string AppName { get; set; }
public static string strConnection { get; set; }
}
然后在你的启动class中,填写如下:
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");
}
虽然这是一个糟糕的模式,因为这将在应用程序的整个生命周期中存在,我找不到更好的方法在控制器之外使用它。
下面是一个在不涉及 MVC 控制器的情况下使用 DI 的工作示例。这是我需要做的来理解这个过程,所以也许它会对其他人有所帮助。
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();
}
}
}
根据当前的 .NET Core 2.2 DI 文档,这里有一个更完整的示例可以直接回答 OP 的问题 here。添加此答案是因为它可能对 .NET Core DI 的新手有所帮助,并且因为此问题是 Google 的热门搜索结果。
首先,为MyHelper添加一个接口:
public interface IMyHelper
{
bool CheckIt();
}
其次,更新MyHelperclass实现接口(在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>();
...
}
第四,创建一个私有变量来引用服务的实例。将服务作为参数传递给构造函数(通过构造函数注入),然后使用服务实例初始化变量。通过私有变量引用此自定义 class 实例的任何属性或调用方法。
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
}
}
}
您可以使用 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 的实例,它是从以编程方式确定的类型实例化的。此对象可以注入非控制器 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:您可以遍历 System.AppDomain.CurrentDomain.GetAssemblies() 以确定容纳您的 class 的程序集的名称。此名称用于包装函数的第三个参数。
TL;DR: 您可以将单例保存在 static var 中,然后从其他 类 访问它,但这是一种反模式,请谨慎使用。
长版:
根据这个问题
Any services registered in
ConfigureServices()
can then be injected into theConfigure()
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; }
然后从你的其他代码调用它MyStartupClass.FooService.DoStuff()