Blazor Project structure / best practices

Dan picture Dan · Dec 31, 2019 · Viewed 7.1k times · Source

My company is moving from a legacy codebase to a more modern platform and we are moving to Blazor. We are currently just getting involved with ORM's and best practices and there seems to be a lot of conflicting ideas regarding project setup (at least from what I have gathered). My current structure is as follows:

First is a class library called DAL - This is our "data layer". We are using Dapper and it is relatively straightforward. An example class would look like the following:

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....
}

The second project is a server Blazor Project which references the project DAL. The project is divided like such:

  1. Models - These are models SPECIFIC to the current project being worked on. For example, one model might be a combination of several tables (Models from the DAL class) or just fields used for a form on a web page.

An example might be like such:

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. Pages - Razor pages/components with a page reference.
  2. Shared - Razor components - things used on multiple pages. An example would be a modal.
  3. Services - This is I suppose the business layer. As of right now, there is one service per model/class in the folder "Models" but there are also some for the shared components. An example for the EmployeeModel from the Models folder, might be as such:
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...
    }
}

The services and dbcontext are all generated via dependency injection via startup.cs. The page class would load the data with something along these lines:


@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) 
    }
}

This seems to be going okay so far but there are A LOT of different interpretations out there. For instance, I liked the idea of using a MVVM pattern. An example I was originally following was at the following: https://itnext.io/a-simple-mvvm-implementation-in-client-side-blazor-8c875c365435

However, I didn't see the purpose of separating out Model/ViewModel to how they were in that example, as they seemed to be doing the same thing but just at different areas within the application. I also couldn't find any other examples of this implementation online so I thought I was heading down the wrong path and initially scrapped that but am still early enough in the process to give that method a shot as well. For example, the EmployeeService class would look like such in this method:

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... 
    }
}

Then on the page it would be like the following:


@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();
    }
}

Seeing as how I was working with legacy code for so long and no one senior to tell me otherwise, I just want to know that I am doing it right. This is especially true since this is supposed to be the heart of our business moving forward and I don't want to wind up with spaghetti code or doing a complete refactor down the line.

I guess my questions are as follows:

  1. How is my current implementation of a DAL? Is it okay to have the actual properties in line with the CRUD operations? As in having a constructor with the DBContext and one without? I have seen some projects have a separate library just for the class without the CRUD operations and I didn't see the value in that. The logic behind it being this way is most of our applications are just CRUD operations so I'd like to be able to just reuse that project in every application going forward. From looking online this implementation is a hybrid of a DAL/BLL

    1. Does my current implementation of Blazor "work"? Or are there any other better design practices I can follow? I like MVVM, but I really just don't see the value in any implementations I've seen so far. What is the point of having a page call a function on a ViewModel, that just calls a function in another class with the same name/parameters? Wouldn't it make sense to cut out the middle man so to speak?

    2. Are there any sample enterprise projects that I could follow to get a better idea of what to do here? As I've stated, there is no one senior at my company to go to regarding any of this. I am just trying to make it as adaptable to change/professional as possible.

Thanks in advance for the help!

Answer

Shahed C - MSFT picture Shahed C - MSFT · Dec 31, 2019

I just created a new ASP .NET Core 3.1 project with 3 web apps: MVC, Razor Pages and Blazor.

NetLearner: https://github.com/shahedc/NetLearnerApp

I’m developing all 3 in parallel so that you can see similar functionality in all of them. I’ve extracted common items into a Shared Library for easy sharing.

The shared library includes:

  • Core items (Models and Services)
  • Infrastructure items (Db context and Migrations)

Here’s the corresponding blog writeup, which will be followed by an A-Z weekly series, which will explore 26 different topics in the next 6 months.

Hope the current version is useful for what you’re asking for. Stay tuned and feel free to make suggestions or provide feedback on the project structure.

NetLearner architecture