【问题标题】:asp.net core constructor injection with inheritance带有继承的asp.net核心构造函数注入
【发布时间】:2019-03-29 16:04:24
【问题描述】:

在我的 asp.net 核心应用程序中,我有注入到几乎所有服务的依赖类。所以我想构建一个基础服务类来获取这些对属性的依赖关系,并且我的服务继承了这个基础服务类。

public abstract class BaseService
{
    protected Foo Foo { get; set; }
    protected Bar Bar { get; set; }

    public BaseService(Foo foo, Bar bar)
    {
        Foo = foo;
        Bar = bar;
    }
}
public class Service : BaseService
{
    public Service(IOtherDependency otherDependency) { }

    public void Method()
    {
        var value = Bar.value;
        Foo.Do(value);
    }
}

因此,使用给定的代码,它会警告我使用提供的参数调用基本构造函数,但是它们是将在运行时注入的参数,我不想要它。如果我添加一个无参数构造函数,它将不会调用我需要的参数化构造函数。

我不想在继承的服务中调用或定义任何注入基础服务(FooBar)的类,我该怎么做?

顺便说一句,FooBar 类作为单例注入到容器中,以防它们的生命周期很重要。

【问题讨论】:

    标签: c# inheritance asp.net-core dependency-injection


    【解决方案1】:

    This 是我要找的。​​p>


    我修改了我的控制器基础的代码,如上面的帖子所示。

    对于我在问题中提出的服务方面;因为他们没有像内置的Controller 基类那样使用HttpContext,所以我只将IServiceProvider 类注入我的BaseService,所以无论我在所有服务中需要什么,我都会通过provider.GetService() 将其获取到财产。

    public abstract class BaseService
    {
        protected Foo Foo { get; set; }
        protected Bar Bar { get; set; }
    
        public BaseService(IServiceProvider provider)
        {
            Foo = provider.GetService<Foo>();
            Bar = provider.GetService<Bar>();
        }
    }
    public class Service : BaseService, IService
    {
        public Service(IOtherDependency otherDependency, IServiceProvider provider) : base(provider) { }
    
        public void Method()
        {
            var value = Bar.value;
            Foo.Do(value);
        }
    }
    
    public class SomeController : BaseController
    {
        private readonly IService _service;
    
        public SomeController(IService service)
        {
            _service = service;
        }
    
        public IActionResult Index()
        {
            //call method
            _service.Method();
        }
    }
    

    【讨论】:

    • 你将如何调用类Service,因为它的构造函数需要参数?
    • @Amir 我不直接在控制器中调用服务,它们是通过依赖注入调用的,具有接口约定。
    • 那么您能否提供绑定代码。你是如何绑定你的类和接口的
    • 这很好用,谢谢。但我很好奇这是否是一种反模式。我的理解是,这类似于服务定位器反模式。如果是这样,为什么它是一种反模式,我们可以做些什么来让它变得更好?我是如何使用它的,我有一个名为 BaseVerifier 的抽象基类,许多其他类实现了这个基类并提供了自己的业务逻辑。它们都需要通过 DI 访问多个服务,例如 AppDbContextUserManager。我不想到处传递这些参数,所以我在基类中解决它们。
    • @Nexus 我同意你的看法:虽然这个解决方案有效(并且是我现在拥有的最好的)它很痒......这个解决方案暴露了它具有依赖关系(对 IServiceProvider)但隐藏了对 Foo 和 Bar 的依赖。向 BaseService 添加新的依赖项肯定会导致该类的任何没有源代码的消费者(或在企业的另一部分中)进行一些严重的调试。我想我会用(元)设计模式解决我的问题:优先组合而不是继承。
    【解决方案2】:

    很好。让我们有一个基类 BaseService 和一个构造函数,将其依赖项作为参数传递:

    public abstract class BaseService
    {
        protected IFoo Foo { get; private set; }
        protected IBar Bar { get; private set; }
    
        public BaseService(IFoo foo, IBar bar)
        {
            Foo = foo;
            Bar = bar;
        }
    }
    
    public class Service : BaseService
    {
        protected IOtherDependency otherDependency { get; private set; }
    
        public Service(IOtherDependency otherDependency, IFoo foo, IBar bar)
            : base(foo, bar)
        {
            OtherDependency = otherDependency;
        }
    
    }
    

    BaseService 类的开发人员现在发现应该将其某些功能外包给该类应该依赖的外部服务时该怎么办?

    BaseService类构造函数中添加另一个INewDependency参数违反了与派生类开发者的约定,因为他们显式调用BaseService类构造函数并且只期望两个参数,所以当升级到新的版本,找不到对应的签名构造函数,编译失败。在我看来,在派生类的代码中重复基类依赖列表的要求是违反了Single Source Of Truth原则,将部分基类功能分配给依赖后编译失败是由于这种违规行为。

    作为一种变通方法,我建议将基类的所有依赖集中到指定密封类的属性中,通过ConfigureServices注册这个类,并在构造函数参数中传递。

    对派生类执行相同的操作。开发人员使用 IServicesCollection 注册这些类中的每一个。

    public abstract class BaseService
    {
        protected IFoo Foo { get; private set; }
        protected IBar Bar { get; private set; }
    
        public BaseService(Dependencies dependencies)
        {
            Foo = dependencies.Foo;
            Bar = dependencies.Bar;
        }
    
        public sealed class Dependencies
        {
            public IFoo Foo { get; private set; }
            public IBar Bar { get; private set; }
    
            public Dependencies(IFoo foo, IBar bar)
            {
                Foo = foo;
                Bar = bar;
            }
        }
    }
    

    具有父类依赖关系的对象将由提供子类依赖关系的类属性引用。但是,派生类的代码完全是从父类的依赖列表中抽象出来的,封装在BaseService.Dependencies类型中:

    public class Service : BaseService
    {
        protected IOtherDependency OtherDependency { get; private set; }
    
        public Service(Dependencies dependencies) : base(dependencies.BaseDependencies)
        {
            OtherDependency = dependencies.OtherDependency;
        }
    
        public sealed class Dependencies
        {
            public IOtherDependency OtherDependency { get; private set; }
            public BaseService.Depencencies BaseDependencies { get; private set; }
    
            public Dependencies(IOtherDependency otherDependency, BaseService.Dependencies baseDependencies)
            {
                OtherDependency = otherDependency;
                BaseDependencies = baseDependencies;
            }
        }    
    }
    

    在这个设计中,每个类的构造函数都有一个参数,一个密封类的实例及其依赖项,继承的依赖项作为属性传递。类依赖列表封装在Dependencies 类中,与类和默认IServiceCollection 注册一起提供给消费者。

    如果BaseClass 开发人员决定将某些功能外包给新的依赖项,则所有必要的更改都将在提供的包中进行,而其使用者无需更改其代码中的任何内容。

    【讨论】:

    • 很好,但我认为每个派生类中的sealed Dependencies 类太多了。对我来说,只需为BaseClass 提供一个就足够了。现在我意识到这实际上是我在 javascript 中管理方法参数时经常做的事情。 wp
    • 如果Service类没有标记为sealed,可能会再次出现这种情况:同一个或者另一个开发者可能在派生类的构造函数中从它派生出另一个类,会调用Service类的构造函数明确地。然后,如果有人发现 Service 类的部分功能应该外包给依赖项并更改其构造函数的签名,就会出现问题。在我看来,类构造函数的签名应该是标准化的,除非类是密封的或非常简单的。
    【解决方案3】:

    这是使用通用 Base Controller 类的最简单方法:

    public abstract class BaseController<T> : Controller
    {
        private IFoo _fooInstance;
        private IBar _barInstance;
    
        protected IFoo _foo => _fooInstance ??= HttpContext.RequestServices.GetService<IFoo>();
        protected IBar _bar => _barInstance ??= HttpContext.RequestServices.GetService<IBar>();
    }
    

    如果您使用的是 Razor 页面:

    class BasePageModel<T> : PageModel where T : class
    {
        private IFoo _fooInstance;
        private IBar _barInstance;
    
        protected IFoo _foo => _fooInstance ??= HttpContext.RequestServices.GetService<IFoo>();
        protected IBar _bar => _barInstance ??= HttpContext.RequestServices.GetService<IBar>();
    }
    

    【讨论】:

      【解决方案4】:

      你不能那样做。

      如果您从没有默认构造函数的基类派生,则必须将参数传递给派生类构造函数并使用它们调用基类构造函数,例如

      public abstract class BaseService
      {
          protected Foo Foo { get; set; }
          protected Bar Bar { get; set; }
      
          public BaseService(Foo foo, Bar bar)
          {
              Foo = foo;
              Bar = bar;
          }
      }
      
      public class Service : BaseService
      {
          public Service(IOtherDependency otherDependency, Foo foo, Bar bar) : base(foo, bar) { }
      }
      

      编译器想要创建基类,但需要参数来执行此操作。如果您的基类上有 2 个构造函数,请想象一下场景

      public abstract class BaseService
      {
          public BaseService(Foo foo)
          {
              ...
          }
      
          public BaseService(Bar bar)
          {
              ...
          }
      }
      

      在这种情况下,编译器从派生类调用哪个基类构造函数?您需要显式使用参数化构造函数,因此您还需要将它们传递给任何派生构造函数。

      【讨论】:

      • 以上代码就是我目前所做的。其实我是在问这个; - 嘿派生服务!您无需将任何参数传递给基础服务,因为基础服务将通过依赖注入创建并将它们传递给您的属性。因此,您可以提供另一种类结构或方法来实现这一点。
      • 对不起。它无法完成 - 这样做是不好的做法,因为您隐藏了依赖项。请参阅this answer 以获得关于为什么不进行属性注入的详细说明。
      • @SimplyGed 。你说的对。支持 DI 的类不能用作通用类,我们也不能使用很多设计模式。上述代码的问题在于它与基类耦合,难以维护和扩展。
      猜你喜欢
      • 1970-01-01
      • 2021-10-16
      • 1970-01-01
      • 2023-03-18
      • 2018-03-31
      • 2021-05-03
      • 2014-02-03
      • 2013-08-21
      • 1970-01-01
      相关资源
      最近更新 更多