【问题标题】:How to follow SOLID principles in Startup classes?如何在 Startup 课程中遵循 SOLID 原则?
【发布时间】:2018-11-05 21:21:40
【问题描述】:

AspNet Core 中是否有一种本机机制,允许将正在完成的工作拆分在一个单一的 Startup 类中,以从长远来看提高可读性/可维护性/可扩展性?如果是这样,它是如何工作的?

我们有一个有点小的 .Net Core MVC WebAPI 项目,它抽象了一些产品目录问题,但在我看来,Startup 类正在快速增长并且变得难以阅读和维护。

以下是一些统计数据:

  • 244行代码
  • 32 使用命名空间指令
  • ~50 行手动域级容器注册

虽然这听起来可能没什么大不了的,但与项目其余部分中遵循 SOLID 原则的一些类相比,这可能令人生畏(尤其是包含的不同命名空间的数量,这很好地表明了 SRP 违规)。

我可以创建一些额外的 .AddX() 扩展方法来减少手动 DI 注册代码的大部分内容(例如,基于“每个模块”或非常类似于 Autofac 的 Registry/Module 或Structuremap) 就像here 所描述的那样,但即便如此,我也会留下一大堆不相关且有些复杂的逻辑来注册/配置诸如:

  • Mvc(包括自定义过滤器、序列化选项、OData 路由、OData EDM 模型构建器)
  • Swagger(同样包括自定义和各种设置)
  • ApiVersioning
  • Cors 配置
  • 使用外部配置系统的复杂 IConfiguration 构建器
  • 显式IsDevelopment检查配置默认异常页面

这些似乎都是完全孤立的、独立的问题,我觉得我将它们放在同一个类中违反了 SRP。

我是否可以利用一种已知的机制将 Startup 中完成的工作拆分为单独的类,例如更紧密地遵循 SRP?这样做是否可取?

即使 aspnet 核心仅支持单个 Startup 类(我没有找到对此的确认),我想我可以提出某种复合实现,其中子 Startup 类每个都处理这些问题之一,但如果类似的机制已经广泛使用并为此目的构建,我不想重新发明轮子或过度增加复杂性。

类如此之大的事实也使得拥有干净的“每个环境”配置变得更加困难,因为它可能会导致大量代码重复,而这些配置是由约定系统本机支持的。

例如,我们在 Configure 方法中有这个小代码部分:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    // lots of code here

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    // ...and lots of code here
}

如果这个逻辑被抽象成一个完全隔离的配置类,我们可以有这样的东西:

public class ErrorPageConfigurationStartup
{   
    private readonly IApplicationBuilder _app;

    public ErrorPageConfigurationStartup(IApplicationBuilder app)
    {
        _app = app;
    }

    public void Configure()
    {
        app.UseExceptionHandler("/Home/Error");           
    }

    public void ConfigureDevelopment()
    {
        app.UseDeveloperExceptionPage();
    }
}

甚至这个,利用方法级注入:

public class ErrorPageConfigurationStartup
{   
    public void Configure(IApplicationBuilder app)
    {
        app.UseExceptionHandler("/Home/Error");           
    }

    public void ConfigureDevelopment(IApplicationBuilder app)
    {
        app.UseDeveloperExceptionPage();
    }
}

我可以为上面列出的大多数问题提出类似的小类,由于减少了依赖关系/责任,这将导致整体逻辑大大简化。

我正在寻找无需创建大量自定义基础架构代码来支持它的方法。

【问题讨论】:

  • 什么是增长困难?是DI注册吗?这些可以(并且应该)在它们自己的扩展方法中抽象出来。这同样适用于其他一切实际上
  • 是的,DI 注册是一个方面,当然会随着 API 本身的功能而增长@Tseng。当您说所有内容都可以类似地重构为扩展方法时,我不太确定我是否跟随您。你如何重构我提到的 MVC/Cors/OData/ApiVersioning/etc 问题?我的意思是,它们本身已经主要是扩展方法。
  • 看起来我得到了一些反对意见。如果您能分享原因以便我改进问题,我将不胜感激。

标签: c# asp.net-core startup solid-principles maintainability


【解决方案1】:

我们的启动文件已经增长了很多,但其中大部分都被抽象到类和辅助方法后面:

DI > 启动具有配置方法 > 转到 DI 引导程序 > 转到我命名为 IocConfig.cs 的文件,其中包含复合根。上次我用这个作为奖励交换容器花了几个小时。

对于.NET Core,config在容器内置时直接调用,见:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1

Swagger > 当您安装 nuget 时,它应该已经为您提供了一个配置文件,再次在启动时配置 1 行。

如果在 .net Core 中不是这种情况,我仍然会手动创建配置文件并移动我的代码。

过去,它都在做更多相同的事情,并且与语言无关,创建一个方法或提供程序类来抽象出逻辑并让它在启动时触发一两行。

据我所知,这里没有标准,你选择走多远取决于你对代码的抽象。例如,我的 oauth 配置方法是 startup.cs 底部的方法(然后它们会调用更多类),每个方法大约有十几行,因此将它们移动到自己的类中没有多大意义,但是缓存单例有点复杂,所以它得到一个缓存提供者.cs 文件。

【讨论】:

  • 感谢您的回答。尚未标记为实际答案,因为我想等待对此的定义:“据我所知,这里没有标准......”
  • @julealgon 我认为你的意思是澄清......;)但我明白了。与所有设计模式一样,存在相互竞争的问题。例如,SRP 与封装竞争。因此,我在这里所说的只是使用对您手头的问题最有意义的抽象级别。提供程序类 vs 辅助方法 vs 内联启动代码。希望这是有道理的!
  • 感谢您的跟进,但我的意思是我会等待有关通过本机机制支持拆分的 aspnet 核心的明确答复。您的回答似乎暗示您不知道它的存在,只是您不知道它(因为我不知道)。如果不是这种情况,并且您确定确实不存在这种机制,我恳请您更新您的答案(最好使用来源),以便我可以将其标记为已接受。
  • @julealgon 啊,是的,我不知道有一个,我们正在考虑迁移到 .net Core 2 是我们所处的位置,但是请参阅我对差异更新的回答我知道。我认为 .net Core 中没有针对 SOLID 原则的总体技术机制,只有提供者的各种单独实现。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-03-22
  • 1970-01-01
  • 2018-11-13
  • 1970-01-01
  • 2023-04-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多