我正在使用Entity Framework Code-First构建ASP.NET Core MVC应用程序。
我选择实现一个简单的存储库模式,为我创建的所有模型类提供基本的CRUD操作。
我选择遵循提供的所有建议 http://docs.asp.net 而DI就是其中之一。
在.NET 5中,依赖注入非常适用于我们不直接实例化的任何类(例如:控制器,数据存储库,......)。
我们只需通过构造函数注入它们,并在应用程序的Startup类中注册映射:
// Some repository class
public class MyRepository : IMyRepository
{
private readonly IMyDependency _myDependency;
public MyRepository(IMyDependency myDependency)
{
_myDependency = myDependency;
}
}
// In startup.cs :
services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyRepository, MyRepository>();
我遇到的问题是,在我的一些模型类中,我想注入一些我声明的依赖项。
但我认为我不能使用构造函数注入模式,因为模型类通常是明确地实现的,因此,我需要为自己提供依赖项,我不能。
所以我的问题是:是否有另一种方式比构造函数注入注入依赖项,以及如何?我是在考虑属性模式或类似的东西。
正如我在评论中已经解释的那样,在使用时创建对象 new
,进程中涉及的依赖注入框架没有任何内容。因此,DI框架不可能神奇地将东西注入该对象,它根本不知道它。
因为让DI框架没有任何意义 创建 你的模型实例(模型不是 依赖),如果你想让模型拥有它们,你必须明确地传入你的依赖项。你如何做到这一点取决于你的模型用于什么,以及这些依赖是什么。
简单明了的情况是让你的模型期望构造函数的依赖性。这样,如果您不提供它们,则是编译时错误,并且模型可以立即访问它们。因此,无论如何,创建模型都需要具有模型类型所需的依赖关系。但在该级别,这可能是一个服务或控制器,可以访问DI并可以请求依赖本身。
当然,根据依赖项的数量,这可能会变得有点复杂,因为您需要将它们全部传递给构造函数。因此,一种替代方案是拥有一些负责创建模型对象的“模型工厂”。另一种选择也是使用 服务定位器模式通过了 IServiceCollection
到模型,然后可以请求它需要的任何依赖项。请注意,这通常是一种不好的做法,而不再是真正的控制反转。
这两个想法都存在修改对象创建方式的问题。有些模型,特别是那些由Entity Framework处理的模型,需要一个空构造函数才能使EF能够创建对象。那么在那时你可能会最终得到 一些案例 模型的依赖关系 不 解决了(你没有简单的说法)。
一种通常更好的方法,也就是更明确的方法,就是传递你需要的依赖关系,例如:如果您在模型上有一些计算某些东西但需要一些配置的方法,那么让该方法需要该配置。这也使得方法更容易测试。
另一种解决方案是将逻辑移出模型。比如说 ASP.NET身份模型 真的很蠢。他们什么都不做。所有的逻辑都是在 UserStore 这是一种服务,因此可以具有服务依赖性。
域驱动设计中常用的模式(富域模型是特定的)是将所需的服务传递给您调用的方法。
例如,如果您想计算增值税,您可以将增值税服务传递给 CalculateVat
方法。
在你的模型中
public void CalculateVat(IVatCalculator vatCalc)
{
if(vatCalc == null)
throw new ArgumentNullException(nameof(vatCalc));
decimal vatAmount = vatcalc.Calculate(this.TotalNetPrice, this.Country);
this.VatAmount = new Currency(vatAmount, this.CurrencySymbol);
}
你的服务类
// where vatCalculator is an implementation IVatCalculator
order.CalculateVat(vatCalculator);
最后,您的服务可以注入其他服务,例如将获取某个国家/地区的税率的存储库
public class VatCalculator : IVatCalculator
{
private readonly IVatRepository vatRepository;
public VatCalculator(IVatRepository vatRepository)
{
if(vatRepository == null)
throw new ArgumentNullException(nameof(vatRepository));
this.vatRepository = vatRepository;
}
public decimal Calculate(decimal value, Country country)
{
decimal vatRate = vatRepository.GetVatRateForCountry(country);
return vatAmount = value * vatRate;
}
}
是否有另一种方式比构造函数注入注入依赖项,以及如何?
答案是“不”,这不能用“依赖注入”来完成。但是,“是”您可以使用“服务定位器模式”来实现您的最终目标。
您可以使用下面的代码来解决依赖关系,而无需使用构造函数注入或 FromServices
属性。另外你可以 new
你认为合适的类的实例,它仍然可以工作 - 假设你已经添加了依赖项 Startup.cs
。
public class MyRepository : IMyRepository
{
public IMyDependency { get; } =
CallContextServiceLocator.Locator
.ServiceProvider
.GetRequiredService<IMyDependency>();
}
该 CallContextServiceLocator.Locator.ServiceProvider
是一切生活的全球服务提供商。建议不要使用它。但如果你别无选择。建议改为使用 DI 一直以来都没有手动实例化一个对象,即;避免 new
。
内置模型绑定器抱怨他们找不到默认的ctor。因此,您需要一个自定义的。
您可以找到解决类似问题的方法 这里,检查注册的服务以创建模型。
值得注意的是,下面的代码段提供了稍微不同的功能,希望能够满足您的特定需求。下面的代码要求使用ctor注射模型。当然,这些模型具有您可能已定义的常用属性。这些属性完全按预期填充,所以奖金是 使用ctor注射绑定模型时的正确行为。
public class DiModelBinder : ComplexTypeModelBinder
{
public DiModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders) : base(propertyBinders)
{
}
/// <summary>
/// Creates the model with one (or more) injected service(s).
/// </summary>
/// <param name="bindingContext"></param>
/// <returns></returns>
protected override object CreateModel(ModelBindingContext bindingContext)
{
var services = bindingContext.HttpContext.RequestServices;
var modelType = bindingContext.ModelType;
var ctors = modelType.GetConstructors();
foreach (var ctor in ctors)
{
var paramTypes = ctor.GetParameters().Select(p => p.ParameterType).ToList();
var parameters = paramTypes.Select(p => services.GetService(p)).ToArray();
if (parameters.All(p => p != null))
{
var model = ctor.Invoke(parameters);
return model;
}
}
return null;
}
}
此活页夹将由以下人员提供:
public class DiModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) { throw new ArgumentNullException(nameof(context)); }
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType)
{
var propertyBinders = context.Metadata.Properties.ToDictionary(property => property, context.CreateBinder);
return new DiModelBinder(propertyBinders);
}
return null;
}
}
以下是绑定器的注册方式:
services.AddMvc().AddMvcOptions(options =>
{
// replace ComplexTypeModelBinderProvider with its descendent - IoCModelBinderProvider
var provider = options.ModelBinderProviders.FirstOrDefault(x => x.GetType() == typeof(ComplexTypeModelBinderProvider));
var binderIndex = options.ModelBinderProviders.IndexOf(provider);
options.ModelBinderProviders.Remove(provider);
options.ModelBinderProviders.Insert(binderIndex, new DiModelBinderProvider());
});
我不太确定新的活页夹是否必须完全在同一索引上注册,你可以试试这个。
最后,这就是你如何使用它:
public class MyModel
{
private readonly IMyRepository repo;
public MyModel(IMyRepository repo)
{
this.repo = repo;
}
... do whatever you want with your repo
public string AProperty { get; set; }
... other properties here
}
模型类由提供(已注册)服务的活页夹创建,其余模型活页夹提供其常用来源的属性值。
HTH
你可以这样做,查看[InjectionMethod]和container.BuildUp(instance);
例:
典型的DI构造函数(如果您使用InjectionMethod则不需要)公开
ClassConstructor(DeviceHead pDeviceHead){
this.DeviceHead = pDeviceHead; }
此属性导致调用此方法以设置DI。
[InjectionMethod] public void Initialize(DeviceHead pDeviceHead){
this.DeviceHead = pDeviceHead; }