模型类(实体)中的dependency injection

我正在使用Entity Framework Code-First构建ASP.NET Core MVC应用程序。 我选择实现一个简单的存储库模式,为我创建的所有模型类提供基本的CRUD操作。 我选择遵循http://docs.asp.net中提供的所有建议,DI就是其中之一。

在.NET 5中,dependency injection非常适用于我们不直接实例化的任何类(例如:控制器,数据存储库,……)。

我们只需通过构造函数注入它们,并在应用程序的Startup类中注册映射:

// Some repository class public class MyRepository : IMyRepository { private readonly IMyDependency _myDependency; public MyRepository(IMyDependency myDependency) { _myDependency = myDependency; } } // In startup.cs : services.AddScoped(); services.AddScoped(); 

我遇到的问题是,在我的一些模型类中,我想注入一些我声明的依赖项。

但我认为我不能使用构造函数注入模式,因为模型类通常是明确地实现的,因此,我需要为自己提供依赖项,我不能。

所以我的问题是:是否有另一种方式比构造函数注入注入依赖项,以及如何? 我是在考虑属性模式或类似的东西。

正如我在评论中已经解释的那样,在使用new创建对象时,进程中涉及的dependency injection框架没有任何内容。 因此,DI框架不可能神奇地将东西注入该对象,它根本不知道它。

由于让DI框架创建模型实例(模型不是依赖项 )没有任何意义,如果您希望模型具有它们,则必须明确地传入依赖项。 你如何做到这一点取决于你的模型用于什么,以及这些依赖是什么。

简单明了的情况是让你的模型期望构造函数的依赖性。 这样,如果您不提供它们,则是编译时错误,并且模型可以立即访问它们。 因此,无论如何,创建模型都需要具有模型类型所需的依赖关系。 但在该级别,这可能是一个服务或控制器,可以访问DI并可以请求依赖本身。

当然,根据依赖项的数量,这可能会变得有点复杂,因为您需要将它们全部传递给构造函数。 因此,一种替代方案是拥有一些负责创建模型对象的“模型工厂”。 另一种方法是使用服务定位器模式 ,将IServiceCollection传递给模型,然后模型可以请求它需要的任何依赖项。 请注意,这通常是一种不好的做法,而不再是真正的控制反转。

这两个想法都存在修改对象创建方式的问题。 有些模型,特别是那些由Entity Framework处理的模型,需要一个空构造函数才能使EF能够创建对象。 因此,在这一点上,您可能会遇到一些情况 ,其中模型的依赖关系得到解决(并且您没有简单的方法)。

通常更好的方法,也是更明确的方式,将传递您需要它的依赖项,例如,如果您在模型上有一些计算某些东西但需要一些配置的方法,那么该方法需要该配置。 这也使得方法更容易测试。

另一种解决方案是将逻辑移出模型。 例如, ASP.NET身份模型真的很愚蠢。 他们什么都不做。 所有逻辑都在UserStore中完成, 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; } } 

是否有另一种方式比构造函数注入注入依赖项,以及如何?

答案是“不”,这不能用“dependency injection”来完成。 但是,“是”您可以使用“服务定位器模式”来实现您的最终目标。

您可以使用下面的代码来解析依赖项,而无需使用构造函数注入或FromServices属性。 此外,您可以根据需要new一个类的实例,它仍然可以工作 – 假设您已在Startup.cs添加了依赖项。

 public class MyRepository : IMyRepository { public IMyDependency { get; } = CallContextServiceLocator.Locator .ServiceProvider .GetRequiredService(); } 

CallContextServiceLocator.Locator.ServiceProvider是全局服务提供者,所有东西都存在。 建议不要使用它。 但如果你别无选择。 建议改为一直使用DI ,而不是手动实例化对象,即; 避免new

内置模型绑定器抱怨他们找不到默认的ctor。 因此,您需要一个自定义的。

您可以在此找到类似问题的解决方案,该方法检查已注册的服务以创建模型。

值得注意的是,下面的代码段提供了稍微不同的function,希望能够满足您的特定需求。 下面的代码要求使用ctor注射模型。 当然,这些模型具有您可能已定义的常用属性。 这些属性完全按预期填充,因此在使用ctor注射绑定模型时 ,奖励是正确的行为

  public class DiModelBinder : ComplexTypeModelBinder { public DiModelBinder(IDictionary propertyBinders) : base(propertyBinders) { } ///  /// Creates the model with one (or more) injected service(s). ///  ///  ///  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则不需要)public ClassConstructor(DeviceHead pDeviceHead){this.DeviceHead = pDeviceHead; }

此属性导致调用此方法以设置DI。 [InjectionMethod] public void Initialize(DeviceHead pDeviceHead){this.DeviceHead = pDeviceHead; }