Unit of Work, Repositories Pattern, and Fluent Validation in Dotnet Core Web API 🛠️"
Tags: dotnet, api, mssql, csharp
Published: 2023-09-15 06:53:13

Today, we will delve into the concept of the Unit of Work and explore its advantages when integrated into the Repository pattern.

If you're new to developing REST APIs with .NET and Entity Framework Core, be sure to explore this beginner-friendly guide for step-by-step instructions.[Click here to read](https://dev.to/drsimplegraffiti/rest-api-using-c-dotnet-docker-and-azure-data-studio-3dg4)

##### Ingredients
🌻 IDE - I will use Rider
🌻 Dotnet Core SDK
🌻 C# Extension for Visual Studio Code
🌻 Dotnet CLI

##### Prerequisite
🏝️ Basic Knowledge of C#
🏝️ How to use the terminal
🏝️ Create a MSSQL database (refer to the link above).


Agenda
🪢 Introduction: Unit Of Work
🪢 Create a new C# project in Rider.
🪢 Add the necessary references to the .NET Framework and Entity Framework libraries.
🪢 Create a connection string to the database.
🪢 Map the entities to the tables in the database.
🪢 Write code to access the data from the database.
🪢 Run the application and test it.
🪢 Add fluent Validation vs Using Data Annotation.


---

###### Introduction
In simple terms, the Unit of Work in .NET acts as a manager that tracks changes made to data when working with a database. Once you're finished, it saves all those changes in one go. This pattern streamlines the management of multiple repositories by using a shared database context class. Reference: Microsoft [ref:microsoft](https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application). 

It basically helps us to coordinate the work of multiple repositories.

Example in a convention repository pattern having PlayerRepository, BookRepository, StudentRepository and their Interfaces we will have to inject all of them in the Program.cs file and then use them in the main method. But with the help of Unit of Work, we can inject only the Unit of Work in the Program.cs file and then use the repositories in the main method.

Without Unit Of Work

```csharp
//...
// add repositories and their interfaces to the DI container
builder.Services.AddScoped<IPlayerRepository, PlayerRepository>();
builder.Services.AddScoped<IBookRepository, BookRepository>();
builder.Services.AddScoped<IStudentRepository, StudentRepository>();
//...
```

With Unit Of Work

```csharp
//...
// add UnitOfWork and its interface to the DI container
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
//...
```




##### Create A solution and Project Name
Since we have seen some of the benefits of using this pattern, let's create a new Project.
Open Rider or any IDE and create solution name and project name

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k0chp16a5365yj4mx1mv.png)

Choose a name for the Solution and click create
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q24z1mk81zowpw6wi3cj.png) 


You should have something like

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2w6c4wzz6xlqopdbe631.png)

##### Clean up
Let's clean up the project by removing the `WeatherForecast.cs` and `WeatherForecastController.cs` files

##### Now let's create our folder structure

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rcrojx22jk3v0e9q3goi.png)
We will have the following folders
- Controllers
- Data
- Models
- Services
    - Services/Repositories
    - Services/IRepositories
- Utils (For our Fluent Validation)
- DTOs (For our Data Transfer Object)

##### Models 
Models are the internal representation of data. They are the classes that represent the tables in the database. They are also called entities. In the Models folder, create a class called `BaseEntity.cs` and `Player.cs`
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/co3j6szovikv0gx055kt.png)


In `BaseEntity.cs` we will have the following
```csharp
namespace PlayerApi.Models;

public class BaseEntity
{
    public int Id { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime UpdatedAt { get; set; }
}
```

And the Player Entity, we will inherit from the `BaseEntity.cs` class
```csharp
namespace PlayerApi.Models;

public class Player: BaseEntity
{
    public string Name { get; set; } = string.Empty;
    public string Password { get; set; } =  string.Empty;
    public string Email { get; set; } =string.Empty;
}
```

---

##### Data context
- A data context in .NET is a bridge between your application and a database, providing methods to query and manipulate data. 
- In Entity Framework, a data context is represented by a class that inherits from the `DbContext` class. (Highlited in red in the image below)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6qkadt8mhvzswr47095p.png)

We are getting the missing reference Error `DbContext` because we need to install the packages. So I will bring in all packages needed at once for this project
- Microsoft.EntityFrameworkCore
- Microsoft.EntityFrameworkCore.SqlServer
- Microsoft.EntityFrameworkCore.Tools
- FluentValidation.AspNetCore

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wuf9f6ivvv5zs852ow0w.png)

Let's import the missing reference `DbContext` and `DbSet` in the `DataContext.cs` file. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hy1o2rxxhyh7egqjf4fj.png)

```csharp
using Microsoft.EntityFrameworkCore;

namespace PlayerApi.Data;

public class DataContext: DbContext // we inherit from the DbContext class coming from the Microsofy.EntityFrameworkCore we installed earlier
{
    
}
```

⏭️ Now we need to inject our `DbContextOptions` in a Parameterize constructor, so we can pass in the options when we create an instance of the `DataContext` class. 

```csharp
using Microsoft.EntityFrameworkCore;
using PlayerApi.Models;

namespace PlayerApi.Data;

public class DataContext: DbContext
{
    public DataContext(DbContextOptions<DataContext> options): base(options)
    {
        
    }
    
    // DbSets are used to query and save instances of entities to a database
    // Here the DbSet is of type Player and the name of the table in the database will be Players
    public DbSet<Player> Players { get; set; }
}
```


---

##### Interfaces
IGenericRepository: This interface will be used by all the repositories. It will have the basic CRUD operations that will be implemented by the repositories. It will be generic so that it can be used by all the repositories.
```csharp
namespace PlayerApi.Services.IRepositories;

public interface IGenericRepository<T> where T : class // T is a generic type, it means that it can be of any type
{
    Task<IEnumerable<T>> All(); // Task is a type that represents an asynchronous operation that can return a value
    Task<T> GetById(int id);

    Task<bool> Add(T entity); // returns true if successful

    Task<bool> Delete(int id); 

    Task<bool> Upsert(T entity);
}
```

##### Explaination: Task<IEnumerable<T>> All(); 
- Task is a type that represents an asynchronous operation that can return a value
- IEnumerable is used to iterate over a collection of the same type
- T is a generic type, it means that it can be of any type
- All is the name of the method
- So Task<IEnumerable<T>> All(); means that we are returning a Task that will return a collection of type T


###### IPlayerRepository
The IGernericRepository will be implemented by the IPlayerRepository. The IPlayerRepository will have methods that are specific to the Player entity.
```csharp
using PlayerApi.Models;

namespace PlayerApi.Services.IRepositories;

public interface IPlayerRepository: IGenericRepository<Player>
{
    // add methods that are specific to the Player entity
    // e.g Task<Player> GetByEmail(string email);
    // e.g Task<Player> GetByName(string name);
    // e.g Task<Player> GetByEmailAndPassword(string email, string password);
}
```


###### IUnitOfWork 
The IUnitOfWork will be used to coordinate the work of multiple repositories. It will have a property for each repository. It will also have a method that will save all the changes made to the database.
```csharp
namespace PlayerApi.Services.IRepositories;

public interface IUnitOfWork
{
    IPlayerRepository Players { get; } // we have only get because we don't want to set the repository. setting the repository will be done in the UnitOfWork class

    Task CompleteAsync(); // this method will save all the changes made to the database
}
```

---

##### Repositories
The repositories will implement the IGenericRepository and IPlayerRepository interfaces. They will have the implementation of the methods in the interfaces. They will also have a constructor that will take the DataContext and ILoggerFactory as parameters. The DataContext will be used to access the database and the ILoggerFactory will be used to log errors.

###### GenericRepository Implementation
The GenericRepository will implement the IGenericRepository interface. It will have the implementation of the methods in the interface. It will also have a constructor that will take the `DataContext` and `ILoggerFactory` as parameters. The DataContext will be used to access the database and the ILoggerFactory will be used to log errors.
```csharp
using Microsoft.EntityFrameworkCore;
using PlayerApi.Data;
using PlayerApi.Services.IRepositories;

namespace PlayerApi.Services.Repositories;

public class GenericRepository<T> : IGenericRepository<T> where T : class
{
    protected DataContext _context;
    protected DbSet<T> dbSet;
    protected readonly ILogger _logger;

    // constructor will take the context and logger factory as parameters
    public GenericRepository(
        DataContext context,
        ILogger logger
    )
    {
        _context = context;
        _logger = logger;
        this.dbSet = _context.Set<T>();
    }

    public virtual async Task<IEnumerable<T>> All() // virtual means that this method can be overriden by a class that inherits from this class
    {
        return await dbSet.ToListAsync();
    }

    public  virtual async Task<T> GetById(int id)
    {
        try
        {
            return await dbSet.FindAsync(id);
        }
        catch (Exception e)
        {
            _logger.LogError(e, "Error getting entity with id {Id}", id);
            return null;
        }
    }

    public virtual async  Task<bool> Add(T entity)
    {
        try
        {
            await dbSet.AddAsync(entity);
            return true;
        }
        catch (Exception e)
        {
            _logger.LogError(e, "Error adding entity");
            return false;
        }
    }

   public virtual async Task<bool> Delete(int id)
{
    try
    {
        var entity = await dbSet.FindAsync(id);
        if (entity != null)
        {
            dbSet.Remove(entity);
            return true;
        }
        else
        {
            _logger.LogWarning("Entity with id {Id} not found for deletion", id);
            return false;
        }
    }
    catch (Exception e)
    {
        _logger.LogError(e, "Error deleting entity with id {Id}", id);
        return false;
    }
}


    public Task<bool> Upsert(T entity)
    {
        throw new NotImplementedException();
    }
}
```


###### UnitOfWork Implementation
The UnitOfWork will implement the IUnitOfWork interface. It will have a property for each repository. It will also have a constructor that will take the DataContext and ILoggerFactory as parameters. The DataContext will be used to access the database and the ILoggerFactory will be used to log errors.
```csharp
using PlayerApi.Data;
using PlayerApi.Services.IRepositories;

namespace PlayerApi.Services.Repositories;

public class UnitOfWork:IUnitOfWork, IDisposable // IDisposable is used to free unmanaged resources
{
    private readonly DataContext _context;
    private readonly ILogger _logger;

    public IPlayerRepository Players { get; private set; }

    // constructor will take the context and logger factory as parameters
    public UnitOfWork(
        DataContext context,
        ILoggerFactory loggerFactory
    )
    {
        _context = context;
        _logger = loggerFactory.CreateLogger("logs");

        Players = new PlayerRepository(_context, _logger);
    }

    public async Task CompleteAsync()
    {
        await _context.SaveChangesAsync();
    }

    public  void Dispose()
    {
        _context.Dispose();
    }
        
}

```

A quick cheat: To read more about what an interface or a class does, over on the interface or class name.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u6t8rgfxegw3lqrjyjxl.png)



---


##### Controller
The controller will be used to handle requests from the client. Clients can be web applications, mobile applications, or other servers. The controller will have methods that will be called when a request is made to a specific endpoint. The methods will return a response to the client. The response can be a success response or an error response. The controller will also have a constructor that will take the IUnitOfWork as a parameter. The IUnitOfWork will be used to access the repositories.

```csharp
using Microsoft.AspNetCore.Mvc;
using PlayerApi.Models;
using PlayerApi.Services.IRepositories;

namespace PlayerApi.Controllers;

[ApiController]
[Route("api/[controller]")]
public class PlayerController: ControllerBase // ControllerBase is a base class for MVC controller without view support.
    {
        private readonly ILogger<PlayerController> _logger; // ILogger takes the type of the class as a parameter
        private readonly IUnitOfWork _unitOfWork; // readonly means that the variable can only be assigned a value in the constructor

        public PlayerController(
            ILogger<PlayerController> logger,
            IUnitOfWork unitOfWork
            )
        {
            _logger = logger;
            _unitOfWork = unitOfWork;
        }

        // create a new player
        [HttpPost]
        public async Task<IActionResult> CreateUser(Player player) // 
        {
           if(ModelState.IsValid)
            {
              
              
                await _unitOfWork.Players.Add(player); // add the player to the database
                await _unitOfWork.CompleteAsync(); // save the changes to the database

                return CreatedAtAction("GetItem", new { id = player.Id }, player);
            }

            return new JsonResult("Something went wrong"){StatusCode = 500};
        }

        //get a single player
        [HttpGet("{id}")]
        public async Task<IActionResult> GetItem(int id)
        {
            var player = await _unitOfWork.Players.GetById(id);
            if(player == null)
            {
                return NotFound();
            }

            return Ok(player);
        }

        //get all players
        [HttpGet]
        public async Task<IActionResult> GetItems()
        {
            var players = await _unitOfWork.Players.All();
            if(players == null)
            {
                return NotFound();
            }

            return Ok(players);
        }

        //update a player
        [HttpPut("{id}")]
        public async Task<IActionResult> UpdateItem(int id, Player player)
        {
            if(id != player.Id)
            {
                return BadRequest();
            }

            await _unitOfWork.Players.Upsert(player, player.Id);
            await _unitOfWork.CompleteAsync();

            return NoContent();
        }

        //delete a player
        [HttpDelete("{id}")]
        public async Task<IActionResult> DeleteItem(int id)
        {
            var player = await _unitOfWork.Players.GetById(id);
            if(player == null)
            {
                return NotFound();
            }

            await _unitOfWork.Players.Delete(id);
            await _unitOfWork.CompleteAsync();

            return NoContent();
        }
    }


```

##### Connection Strings
A connection string is a string that specifies information about a data source and the means of connecting to it.
 In the below

- `Server` is the name of the server that hosts the database
- `Database` is the name of the database(in our case it is playersdb)
- `User Id` is the username of the user that has access to the database
- `Password` is the password of the user that has access to the database


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1iv9mtk8ajehf23giba9.png)


#### Program.cs
Program.cs is the entry point of the application. It is the first file that is executed when the application is run. It is also used to configure the application. Let's inject the `IUnitOfWork` into the `Program.cs` file. Also let's add the connection string to the configuration builder. The connection string will be used to connect to the database.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i299s72iy6szvri40747.png)




##### Build Application
Run `dotnet build`  command to build the application. In the image below, you can see that the build was successful.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7xldb5oq181sidj5mw8r.png)


##### Run migration
The migration will create the tables in the database. Run the `dotnet ef migrations add InitialCreateclear` command to create the migration. The migration will be created in the Migrations folder. The name of the migration will be <mark>InitialCreateclear</mark>. The migration will have the code to create the tables in the database. Run the `dotnet ef database update` command to run the migration. The tables will be created in the database.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jj481gqc9h6kjcqs3ig1.png)

##### Sync Database

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ikltb8jvqcku9zkl7oql.png)

##### Let's check the Created Migration Table in the Db
In the image below, the part highlihted in orange references the Table in the Db called `Players`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w7epes0a7me5ye8xssja.png)


###### Run the app
Run the `dotnet run` command to run the application. Or click on the run button in the IDE (Highlited in orange in the image below)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a7vf8plvcarwz5o73po6.png)


--


#### Test the APP
Let's test the application using the Http Client in Rider. Create a new file with the `.http` extension.


##### Create a new player
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iwqd9g4pb5cpkh3wjybj.png)

 ⚠️ In the above you can see we have a problem already
1. We are not validating our request, on the get all we see that our appllication has email has string
2. We are taking the entity(internal representaion) instead of a Data transfer Object
3. We didn't hash our password (Not covered in this tutorial).

Get all Player

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zpgpnl4loykk5k22ss0w.png)

##### Check the Db

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d7qsaavx1k8vu121ax92.png)


#### Add fluent Validation
Let's address the ⚠️ we saw earlier
Create a PlayerValidator class in the Utils folder
```csharp
using FluentValidation;
using PlayerApi.Models;

namespace PlayerApi.Utils;

public class PlayerValidator : AbstractValidator<PlayerRequest>
{
    public PlayerValidator()
    {
        RuleFor(x => x)
            .NotNull();

        RuleFor(x => x.Name)
            .NotEmpty()
            .MaximumLength(20);

        RuleFor(x => x.Password)
            .NotEmpty()
            .MaximumLength(20);

        RuleFor(x => x.Email)
            .NotEmpty()
            .EmailAddress()
            .MaximumLength(50);
    }
}
```

##### Create a PlayerRequest class in the DTOs folder
```csharp
namespace PlayerApi.Dtos;

public class PlayerRequest
{
    public string Name { get; set; } = string.Empty;
    public string Password { get; set; } =  string.Empty;
    public string Email { get; set; } =string.Empty;
}
```
So instead of using the entity we will use the DTOs



<!-- ##### Let's add the validation to our controller -->
Lets revamp our Add player method in the controller to use the PlayerRequest
```csharp
// create a new player
[HttpPost]
public async Task<IActionResult> CreateUser(PlayerRequest playerRequest) // 
{
    
    var player = new Player
    {
        Name = playerRequest.Name,
        Password = playerRequest.Password,
        Email = playerRequest.Email
    };
    await _unitOfWork.Players.Add(player); // add the player to the database
    await _unitOfWork.CompleteAsync(); // save the changes to the database

    return CreatedAtAction("GetItem", new { id = player.Id }, player);
}
```

In our Program.cs we add AddFluentValidation to the container.
```csharp

builder.Services.AddFluentValidation(config => 
    config.RegisterValidatorsFromAssembly(Assembly.GetExecutingAssembly()));
```


Now let's test our app again, this time I will use swagger to test the app
Visit the same url as before but this time add `/swagger/index.html` to the url
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dtrwwrow88f6eaohlwmq.png)

The request body no longer has the CreatedAt and UpdatedAt properties
```json
{
  "name": "string",
  "password": "string",
  "email": "string"
}
```

So Issue Number 1 has been addressed

Let's also ensure that the right email format is working as expected
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0tddn4zpl65i1i0ag8b7.png)

##### Conclusion
In this article, we have seen how to implement the Unit of Work and Repository pattern in .NET. We have also seen how to use the Fluent Validation library to validate the request body.  Thanks for reading. If you have any questions or comments, please leave them in the comment section below. You can also reach me on  [LinkedIn](https://www.linkedin.com/in/abayomi-ogunnusi-974826141/).