ASP.NET Core DI 构造函数 vs RequestServices

13

为什么通过HttpContext.RequestServicesIServiceProvider请求服务被认为是不良实践?我可以在各个地方阅读到这种说法:

建议使用构造函数注入而不是使用RequestServices获取它。


我的想法恰恰相反。尽可能使用RequestServices。让我解释一下为什么。 我想尽可能使控制器保持简洁,因此创建单独的服务。这样,我的控制器就很清晰:

public class MyController : Controller
{
    public MyController(IMyServices MyServices){}

    public async Task<IActionResult> GetSomething(int parameter)
    {
       return await this.MyServices.SomeMethod(parameter);
    }
}

所有服务都继承自一个基类,该基类包含有关管理权限、缓存SQL请求等的逻辑...。
通过使用构造函数方法,我得到了一个非常复杂的调用基类系统:

public class MyBaseService
{
    public MyBaseService(ILogger Logger, AppDbContext Context, IMemoryCache Cache) { }

    public bool HasRight(string right) { return true; }

    public bool IsLoggedIn() { return true; }
}

public class MyServices : MyBaseService
{
    public MyServices(ILogger Logger, AppDbContext Context, IMemoryCache Cache) : base(Logger, Context, Cache)
    {    
    }
}

但是通过GetRequiredService,我简化了基于构造函数的调用:

public class MyBaseService2
{
    private ServiceProvider _ServiceProvider;
    public MyBaseService2(IServiceProvider ServiceProvider)
    {

    }

    protected ILogger Logger { get { return this._ServiceProvider.GetRequiredService<ILogger>(); } }

    protected AppDbContext Context { get { return this._ServiceProvider.GetRequiredService<AppDbContext>(); } }

    protected IMemoryCache Cache { get { return this._ServiceProvider.GetRequiredService<IMemoryCache>(); } }

    public bool HasRight(string right) { return true; }

    public bool IsLoggedIn() { return true; }
}

public class MyServices2 : MyBaseService2
{
    public MyServices2(IServiceProvider ServiceProvider) : base(ServiceProvider)
    {    
    }
}

是的,BaseService包含更多的代码,但是当我需要在BaseService中添加其他服务时,无需修复每个类的基本构造函数调用。此外,我所有的服务都有更简单的构造函数(只需IServiceProvider)。

如果我违反构造函数方法,那么如果调用ServiceProvider.GetRequiredService来获取MyServices,是否会有性能损失,如果它们的生命周期是Scoped。

1个回答

15
使用HttpContext.RequestServices或IServiceProvider请求服务被认为是不良实践,因为这是服务定位器反模式的一种实现。
您试图通过将许多常用依赖项和常见逻辑移动到基类中来使您的控制器类保持简洁,但这本身就是一个反模式,因为:
- 即使从基类中解析了依赖项,这些依赖项仍然被隐藏,正如本文所解释的那样。这意味着依赖项对于您的单元测试和DI容器来说都是隐藏的,它们将无法对您的对象图进行分析。 - 将依赖项移动到基类中并没有降低类的复杂性,因为基类始终与其派生类紧密耦合。 - 它会使许多职责挤入基类中,这将导致基类违反单一职责原则。这可能会强制基类成为上帝类并不断更改。
基类使用继承,而在软件开发中普遍共识是应优先选择组合而非继承

由于你应该偏爱组合,这自动导致了依赖注入。没有基类,很容易发现单一职责原则的违规情况,因为构造函数会得到更多的参数。请注意,使用服务定位器时依赖关系的数量不会改变,只是更难计算。

你应该接受构造函数注入很容易导致构造函数过度注入,因为这表明我们的类做了太多的事情,这是一个信号,我们应该重新设计这样的类。

不要在基类中实现横切关注点,如日志记录、缓存和授权,而应该使用组合来实现它们。例如,您可以使用中间件装饰器拦截器将横切关注点应用于请求(或此类请求的特定服务)。


我不确定我是如何错过重复的问题的,但我很高兴我这样做了。这绝对是最好的答案。 - Makla

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接