【问题标题】:Separation of concerns and n-tiered architecture in ASP.NET 5/ASP.NET Core 1ASP.NET 5/ASP.NET Core 1 中的关注点分离和 n 层架构
【发布时间】:2015-12-12 06:03:08
【问题描述】:

由于我想使用新的内置依赖注入,我正在努力寻找一种优雅的方式将我的 DAL 层与 ASP.NET 5 中的 MVC/UI 层分开。

例如,我有一个 ASP.NET 5 项目、一个业务层项目和一个数据访问项目,其中我有各种实体框架代码,例如实体和上下文。在 ASP.NET 5 中设置上下文并以数据库为目标,主要文档建议我在 StartUp.cs 类中执行类似的操作

services.AddEntityFramework()
    .AddSqlServer()
    .AddDbContext<BookContext>(options =>
    {
        options.UseSqlServer(Configuration.Get("Data:ConnectionString"));
    });

这意味着我现在必须在基本上是我的 UI 层的地方引用我的 DAL,根据周围的各种专家和博客文章,多年来这一直是不好的做法。

我解决此问题的一种方法是创建两个新项目,一个是 CompositeRoot 项目,其中包含工厂类以生成我的业务类,然后访问 DAL 以及一个带有配置类的实用程序项目,其中有一个 @ 987654324@ 属性,我可以将其传递到我的上下文中,然后我使用内置的 DI 将所有内容连接起来并避免在我的 UI 层中引用我的 DAL。但是我遇到了最新版本的实体框架(beta 7)的问题,因为现在似乎无法在上下文的构造函数或可覆盖的OnConfiguration 方法中指定连接字符串。另外,到目前为止,所有文档似乎根本不关心这种混合问题。这就是我们现在做事的方式吗?相信开发人员不会做“坏”的事情,比如直接在 UI 中引用 DAL 类?还是人们正在采用一种模式来通过这种新的内置 DI/ASP.NET 5 配置来保持稳定?

【问题讨论】:

  • “这意味着我现在必须在基本上是我的 UI 层的地方引用我的 DAL”。这是不正确的。这段代码不在你的 UI 层;这是您的组合根层。您将层(逻辑工件)误认为程序集(部署工件),并且您隐式选择在一个程序集中包含两个层。如需更深入的解释,请阅读此答案:stackoverflow.com/a/9505530/264697
  • JK,是的,我认为这是可以接受的,而且不是太宽泛,因为我指的是特定框架的特定问题。谢谢史蒂文,我会调查你的帖子。我认为我的主要问题是 Microsoft 已强制他们的项目模板包含所有 MVC 项(控制器/视图)以及 StartUp 类中的组合,这意味着如果您的 EF 代码,您必须对 DAL 进行项目级别的引用包含在那里,您没有单独的组合根。
  • 为什么说不能分开呢?为什么你不能创建一个名为 Database 的单独项目。然后 BookContext 只是对该项目的引用。最后,options 参数覆盖了你设置的连接字符串。
  • 以防万一您不知道如何设置您的控制台应用程序:docs.asp.net/en/latest/dnx/console.html#creating-a-console-app

标签: asp.net dependency-injection asp.net-core asp.net-core-mvc entity-framework-core


【解决方案1】:

如果您请 10 位土木建筑师为您建造一座桥梁,您最终将拥有 10 种不同的架构。没有一个会是一样的,也没有一个会比另一个更好。

无论他们应用了多少最佳实践和设计模式,每个架构师都会证明他们的想法是正确的。有些人会过分热心,而另一些人会保持简单并完成工作。除其他外,预算、交付日期和工艺将直接影响您决定的架构类型。

同样的规则也适用于软件架构师。

我看到我相当一部分的架构师有着世界上最好的意图,只是意识到 UI 层依赖于 DAL。也许这背后的原因是:

  • 他们没想到
  • 他们并不真正关心它
  • 它有利于 DI,因为你可以看到每一层,这反过来又使它 易于将接口映射到它们的实现。

回到 MVC 5,我有以下几层:

-Contoso.Core (Class Library)
-Contoso.Data (Class Library)
-Contoso.Service (Class Library)
-Contoso.Web (asp.net MVC 5)
-Contoso.Web.Configuration (Class Library)

Web.Configuration 层依赖于CoreDataService。这一层是我配置 DI 的地方。

Web 层不依赖于Data 层,为了开始工作,我使用的是Bootstrapper Nuget Package

也许您可以使用 ASP.NET 5 实现类似的目标

我希望 Microsoft(或任何人)使用解耦方法甚至像 Onion 架构示例一样创建更适合企业级示例的工作项目模板。

最后,我认为合理的解耦架构可能对某些人来说太过分了,而对另一些人来说却不够......

请随时分享您的发现,因为我很想知道您是如何做到的。

【讨论】:

    【解决方案2】:

    这真的是个大问题吗?

    您的 UI 层将依赖于您的数据层,并且在某处会有一些参考。

    StartUp.cs 添加了各种服务和配置,所以这些引用大部分都在一个地方。

    【讨论】:

      【解决方案3】:

      您可以使用Service Locator 来解决这个问题。

      例如,您的 root/core/abstractions 项目中有一个接口:

      public interface IServiceConfiguration
      {
          void ConfigureServices(IServiceCollection services, IConfigurationRoot configuration);
      }
      

      在您的 StartUp.cs 中,找到所有实现 IServiceConfiguration 的类型并使用它来注册您的外部服务。

      public void ConfigureServices(IServiceCollection services)
      {
          var currentAssembly = typeof(Startup).Assembly;
      
          // Limit to project assemblies
          var @namespace = currentAssembly.FullName.Split(',')[0].Split('.')[0];
          var assemblies = currentAssembly.GetReferencedAssemblies()
                                  .Where(a => a.Name.StartsWith(@namespace, StringComparison.Ordinal))
                                  .Select(a => Assembly.Load(a));
      
          foreach (var assembly in assemblies)
          {
              // Assembly.ExportedTypes is not supported in dnxcore50 so you need to take it off your frameworks node in project.json.
              foreach (var config in assembly.ExportedTypes.Where(t => typeof(IServiceConfiguration).IsAssignableFrom(t) && !t.IsAbstract))
              {
                  ((IServiceConfiguration)Activator.CreateInstance(config)).ConfigureServices(services, Configuration);
              }
          }
      
          // More service registrations..
      }
      

      并且在您的 DAL 中,您可以添加一个将注册 EF 的类:

      public sealed class EntityFrameworkServiceConfiguration : IServiceConfiguration
      {
          public void ConfigureServices(IServiceCollection services)
          {
              services.AddEntityFramework()
                          .AddSqlServer()
                          .AddDbContext<BookContext>(options =>
                          {
                              options.UseSqlServer(Configuration.Get("Data:ConnectionString"));
                          });
          }
      } 
      

      虽然不是每个人都需要这种类型的解耦。在某些方面,在 StartUp.cs 中注册所有内容更有意义。无论如何,您都会在 Web 项目中引用您的 DAL(除非一切都通过动态发现),这意味着它已经了解有关您的 DAL 的一些信息。

      【讨论】:

        【解决方案4】:

        我在上面发表了评论,但我认为很多人可能有同样的问题。

        添加全新的 ClassLibraryPackage

        编辑你的 project.json 文件

        {
          "version": "1.0.0-*",
          "description": ":)",
          "authors": [ "JJVC" ],
          "tags": [ "" ],
          "projectUrl": "JJVCblog.com",
          "licenseUrl": "",
        
          "frameworks": {
            "dnx451": {
              "frameworkAssemblies": {
              },
              "dependencies": {
                "EntityFramework.Commands": "7.0.0-rc1-final"
              }
            }
          },
        
          "commands": {
            "ef": "EntityFramework.Commands"
          },
        
          "dependencies": {
            "EntityFramework.MicrosoftSqlServer": "7.0.0-rc1-final",
            "Microsoft.AspNet.Identity": "3.0.0-rc1-final",
            "Microsoft.AspNet.Identity.EntityFramework": "3.0.0-rc1-final"
          }
        }
        

        首先做你的代码

            public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole,string>
                {
        
                    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
                    {
        
                        optionsBuilder.UseSqlServer(@"server=localhost;Database=Whatever;Integrated Security=True;MultipleActiveResultSets=True;Connect Timeout=60;");
                    }
                    DbSet<ForgotPasswordResetHistory> ForgotPasswordResetHistory { get; set; }
        
                    protected override void OnModelCreating(ModelBuilder builder)
                    {
        
                        base.OnModelCreating(builder);
                        base.OnModelCreating(builder);
        
                    }
        
        
        
                }
         public class ForgotPasswordResetHistory
            {
                [Key]
                public int ForgotPasswordResetHistoryId { get; set; }
                public bool UserTriedToResetPassword { get; set; }
                public DateTime LinkExpirationDate { get; set; }
                public string SecretCodeForTheLink { get; set; }
                public DateTime PasswordRecoveryRequestDate { get; set; }
                public ApplicationUser ApplicationUser { get; set; }
                public int Test { get; set; }
            }
        

        在文件夹的上下文中启动 cmd 提示符。 dnu 恢复 dnx ef 迁移 dnx ed 数据库更新

        现在转到您的“网络”项目并引用您的数据库。在您的网络项目“project.json”中,您应该会看到它在依赖项下被调用。

         "ConsoleApp2.Database": "1.0.0-*"
        

        祝你好运!

        【讨论】:

          【解决方案5】:

          我今天在为 MVC 6 做演示时遇到了同样的问题。经过一些研究/反复试验,当我删除时

          “EntityFramework.SqlServer”:“7.0.0-beta8”

          来自 project.json 文件的一切对我来说都很好。

          【讨论】:

            【解决方案6】:

            我最近参加了 2016 年全球 Azure 训练营,我们从事的示例项目(纯粹从 Azure 的角度来看)很好地展示了这些关注点的分离。我没有花很多时间剖析这个项目,但是如果有人想看看这是否是一个合适的 N-Tier 拆分,请回帖。链接是http://opsgilitytraining.blob.core.windows.net/public/modern-cloud-apps-student.zip。分隔方式如下:

            • Contoso.Apps.SportsLeague.Web Contoso Sports League 电子商务应用程序
            • Contoso.Apps.SportsLeague.Admin Contoso Sports League 呼叫中心管理应用程序
            • Contoso.Apps.SportsLeague.WorkerRole 处理订单收据生成
            • Contoso.Apps.SportsLeague.Data 数据层
            • Contoso.Apps.SportsLeague.Offers API 用于返回可用产品列表
            • Contoso.Apps.PaymentGateway 用于支付处理的 API

            【讨论】:

              【解决方案7】:

              之所以会有UI层对数据层的引用,是因为我们需要在IoC容器中注册DbContext(如果你使用EF的话)。

              您可以使用 MEF(管理可扩展性框架)绕过此项目/包参考。检查 .NET Core 中 MEF 的实现,因为它与以前的 .NET Framework 有所不同。

              在 MEF 中,您可以通过约定或配置来实现。

              【讨论】:

                【解决方案8】:

                您可以在业务层和数据访问层实现 IServiceCollection 的扩展方法。

                然后在 Web Startup 中,您可以调用业务层上的扩展方法,进而调用数据层上的扩展方法

                所以这段代码可以在数据层扩展中:

                services.AddEntityFramework()
                                    .AddSqlServer()
                                    .AddDbContext<BookContext>(options =>
                                    {
                                       options.UseSqlServer(Configuration.Get("Data:ConnectionString"));
                                   });
                

                这会将事情分开,网络只需要使用启动文件中的语句来引用业务层

                【讨论】:

                • 我认为他试图表达的观点是 UI/Web 不应该知道或关心 EF。
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2016-11-22
                • 1970-01-01
                • 1970-01-01
                • 2011-05-14
                相关资源
                最近更新 更多