Blazor项目结构/最佳实践

26

我的公司正在从传统代码库转移到更现代化的平台,我们正在转向Blazor。我们当前只是涉足ORM和最佳实践,并且似乎存在很多关于项目设置的冲突观点(至少根据我收集到的信息)。 我当前的结构如下:

首先是一个名为DAL的类库 - 这是我们的“数据层”。 我们正在使用Dapper,它相对简单。 例如,一个类可能如下所示:

public class Person
{
      public string Id {get; set;}
      public string FirstName {get; set;}
      public string LastName {get; set;}

      public Person() {}
      public Person(DbContext context) {}

      public void GetPerson(int id) {}
      public void DeletePerson(int id) {}


      etc....
}

第二个项目是一个服务器Blazor项目,它引用了项目DAL。该项目分为以下几个部分:

  1. 模型 - 这些是特定于当前正在开发的项目的模型。例如,一个模型可能是几个表格的组合(来自DAL类的模型),或者仅用于网页表单的字段。

例如:

public class EmployeeModel
{
    public int Id {get; set;}
    public int Department{get; set;}
    public DateTime HireDate {get; set;}
    public decimal Salary {get; set;}
    public Person {get; set;}
}
  1. 页面 - Razor 页面/组件,带有页面引用。
  2. 共享 - Razor 组件 - 用于多个页面的内容。例如一个模态框。
  3. 服务 - 这应该是业务层。目前,在“Models”文件夹中,每个模型/类都有一个服务,但还有一些用于共享组件。例如,对于 Models 文件夹中的 EmployeeModel,可能如下所示:
public class EmployeeService
{
    private DbContext _dbContext = dbContext;
    public EmployeeService(DbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public Task<EmployeeModel> Get(int id)
    {
        var personRepository = new Person(_dbContext);
        var person = personRepository.Get(id);
        Id = id;
        if (id > 10)
            Department = "Accounting"
        etc...
    }

    public Task<int>CalculateBonus(DateTime hireDate, string department, decimal salary)
    {
         //logic here...
    }
}

服务和DbContext都是通过startup.cs进行依赖注入生成的。页面类将使用以下内容加载数据:


@code{

    [Parameter]
    int EmployeeId;

    public Employee employee;
    public decimal bonus;

    protected override OnAfterRenderAsync(bool firstRender)
    {
        if (!firstRender)
            return;

        employee = EmployeeService.Get(EmployeeId);
    }

    public void GetBonus()
    {
        if (employee != null)
            bonus = EmployeeService.CalculateBonus(employee.HireDate, employee.Department, employee.Salary) 
    }
}

目前看起来还不错,但是有很多不同的解释。例如,我喜欢使用MVVM模式的想法。我最初关注的示例链接如下:https://itnext.io/a-simple-mvvm-implementation-in-client-side-blazor-8c875c365435

然而,我不明白为什么要像那个例子中那样将Model/ViewModel分离,因为它们似乎只是在应用程序的不同区域完成相同的工作。我也找不到其他关于这种实现方法的示例,所以我认为我可能走错了方向,最初放弃了这种方法,但早期阶段我仍然可以尝试一下。例如,在这种方法中,EmployeeService类将如下所示:

public class EmployeeService
{
    private EmployeeModel _employeeModel;
    public EmployeeService(EmployeeModel employeeModel)
    {
        _employeeModel = employeeModel;
    }

    private EmployeeModel currentEmployee;
    public EmployeeModel CurrentEmployee
    {
        get { return currentEmployee}
    }
    {
        set {currentEmployee = value; }
    }

    public Task<EmployeeModel> Get(int id)
    {
         currentEmployee = EmployeeModel.Get(id);
    }

    public Task<int>CalculateBonus()
    {
         //logic implemented here with properties instead of parameters... 
    }
}

然后在页面上会像以下内容:


@code{

    [Parameter]
    int EmployeeId;
    public decimal bonus;

    protected override OnAfterRenderAsync(bool firstRender)
    {
        if (!firstRender)
            return;

        EmployeeService.Get(EmployeeId); //reference Employee on page with EmployeeService.CurrentEmployee
    }

    public void GetBonus()
    {
        bonus = EmployeeService.CalculateBonus();
    }
}

由于我长期使用遗留代码且没有更高级别的人告诉我正确性,因此我想知道我的做法是否正确。特别是因为这应该是我们业务向前发展的核心,我不希望最终出现混乱不堪的代码或需要进行完全重构。

我想问以下问题:

  1. 我的DAL目前实现得如何?在CRUD操作中直接定义属性是否可以?例如设置一个包含与数据库上下文相关的构造函数以及一个无参构造函数。我看到一些项目只为该类创建了另一个没有CRUD操作的库,但我并没有看到其中的价值。这样做的逻辑是,大多数应用程序只涉及CRUD操作,所以我希望能够在每个应用程序中重用该项目。根据网上查找,这种实现是DAL/BLL的混合体。

    1. 我的Blazor实现目前是否“有效”?还有其他更好的设计实践吗?我喜欢MVVM,但我真的没有看到迄今为止任何实现的价值。如果页面只是调用ViewModel中的函数,而ViewModel只是调用另一个具有相同名称/参数的类中的函数,这有什么意义呢?去掉中间环节难道不是更合理吗?

    2. 是否有任何样例企业项目可以供我参考,以便更好地了解如何做到这一点?正如我所说,公司中没有资深人员可供参考。我只是想让它尽可能适应变化/专业。

提前感谢您的帮助!


这里有很多需要涵盖的内容,大部分都是基于个人意见的。我的唯一建议是,如果你打算在“业务核心”方面采用Blazor技术,那么现在就加入它可能存在很大的风险。在它成熟并具有长期生命周期之前,我不会将其用于任何关键性的事情。已经有许多这样的框架迅速变成了遗留系统,你可能不想从一个遗留系统转移到另一个遗留系统。 - Saeb Amini
不幸的是,我对我们将使用的平台没有最终决定权,所以这就是我必须处理的。我认为它有很大的前途,但肯定还存在不确定性。 - Dan
1
@Dan Model和ViewModel之间的区别确实有些模糊。我教别人的方式是,Models和ViewModels确实很相似。区别在于它们的用途。ViewModels是(在.NET Web应用程序的情况下)表示视图中使用的属性的模型。我们甚至在部分视图后面使用ViewModels。另一方面,Models是我们的自定义类型、DTO和实体(EF Core模型)。这只是我定义这些东西的方式-并不是说这是“正确”的方式。这只是我们最好的工作方式。没有什么重要的观点-只是想分享一下 :) - Derek Foulk
4个回答

7
我刚刚创建了一个新的 ASP .NET Core 3.1 项目,其中包括三个 Web 应用程序:MVC、Razor Pages 和 Blazor。
NetLearner:https://github.com/shahedc/NetLearnerApp 我同时开发这三个应用程序,以便您可以在它们之间看到相似的功能。我将共同的项目提取到一个共享库中,方便共享。
共享库包括:
- 核心项目(模型和服务) - 基础设施项目(数据库上下文和迁移)
这里是相应的博客文章,接下来将推出从 A 到 Z 的周报系列,将在接下来的六个月中探讨 26 个不同的主题。
博客文章:https://wakeupandcode.com/netlearner-on-asp-net-core-3-1/ 希望当前版本能对您有所帮助。请继续关注,并随时提出建议或反馈项目结构。
NetLearner 架构图:NetLearner architecture

非常感谢!这肯定很有帮助...我想我在这里感到困扰的是(与大多数教程一样),我们当前的代码基础太混乱了...数据库是Informix,许多编写的程序都是用C或VB6,即使我们想做EF(我们曾经尝试过),模式也如此糟糕以至于它甚至无法正常工作(没有主键,外键,错误的列名称/类型等)。 这并不理想,这就是为什么我使用DAPPER,并希望我所拥有的东西从长远来看是可以的。 - Dan
@Dan 我知道你去年发布了这篇文章,但我发现自己正处于与你完全相同的情况。我已经阅读了无数的文章、论坛帖子和博客文章,但似乎找不到一个通用的后端“模板”,这些文章都没有达成一致意见。有些文章展示了3个项目:客户端、共享项目和服务器项目,其中客户端通过控制器使用JSON请求来获取数据。我看过使用MVVM的例子,但我不确定是否喜欢这种方法。我很想听听你是否成功解决了这个问题,或者是否找到了更完整的“企业”示例作为起点。 - weblar83
2
@weblar83,看看我在这里发布的答案,那就是我最终采取的方法。Data文件夹是具有业务逻辑的对象,而Services则是数据层。我还有一个授权、页面和组件文件夹。它非常好用。 - Dan

3

我一直在寻找更多的示例项目,然后发现了一个使用SPA服务器端Dapper应用程序(https://www.c-sharpcorner.com/article/create-a-blazor-server-spa-with-dapper/)的示例。从那里、这里以及其他任何地方我所了解到的信息来看,添加一个处理CRUD操作的单独项目似乎比其价值更高。

我将实现类似于该链接中的内容,并查看它的效果如何。如果其他人也在寻找灵感,这里有一些很好的示例:

https://github.com/AdrienTorris/awesome-blazor#sample-projects

值得一提的是,每个示例似乎都遵循这条路径,只是在处理方式上略有不同(ORM使用、文件夹名称等)。这意味着我需要添加更多的服务(至少总共需要20个),但如果这是一个复杂的业务应用程序,我认为这就是它的本质。

愉快的编码!


2
当使用三层架构时,我发现业务层会变得越来越复杂,并且紧密耦合。
我建议尝试洋葱架构,它非常流行,非常类似于干净架构
我相信你正在寻找一些示例应用程序,以了解如何保持每个层分离,最佳实践和最佳库。
尝试下面的链接,您将在YouTube上获得大量视频。

Asp.net boilerplate

ABP Framework

由于Blazor Web Assembly仍处于预览阶段,我开始从asp.netcore 2.0迁移我的个人网站到blazor客户端和Azure函数作为服务器端。
这是我的应用程序结构。

enter image description here

我希望它能有所帮助!

0

从一个现有的WPF项目开始,我想尝试为我的Blazor项目实现MVVM。

我的目标是能够从服务器端控制所有东西的代码,而这些代码完全不知道它们被演示用途所使用。例如菜单项、弹出窗口或导航等。 同时,还能够在没有任何用户交互的情况下监视来自Azure和/或MS-Message队列的事件。

为了更高的灵活性和更好的测试,我决定通过将其作为视图模型注入到代码后面,将常见的代码与Razor页面分离。 视图模型也会注入其数据服务/模型。 数据直接由数据服务处理,采用CQRS方式,但这将在未来转移到grpc服务器上。

到目前为止,这个方法运行得非常好。虽然需要做更多的工作,但这是值得努力的。


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