Repository Pattern Implementation in ASP.NET Core

Hello guys, this is the fourth part of a series where we are building a web API using ASP NET Core 7.0. In the previous post, we saw how to add some basic logging to our app to give us useful insight whenever an endpoint from our API was called, today we will talk about an essential design pattern in software development and that is the β€œRepository Pattern”.

The repository pattern is a design pattern that is commonly used in software development to separate the business logic from the data access logic. This pattern provides an abstraction layer between the data access layer and the business logic layer.

What is the Repository Pattern ❓

The repository pattern is a design pattern that provides an abstraction layer between the data access layer and the business logic layer. It defines a set of interfaces for data access, and the implementation of these interfaces is handled by the data access layer. The repository pattern makes it easier to test the business logic layer, as it can be tested in isolation from the data access layer.

Why is it Useful πŸ‘πŸΎ

The repository pattern is useful for several reasons. Firstly, it makes it easier to maintain the codebase, as it separates the concerns of the business logic and data access layers. Secondly, it provides a clean separation of concerns, making it easier to test the business logic layer. Finally, it reduces code duplication, as all data access-related code can be written once and reused across the application.

Clone the repo to follow along πŸ™

If you desire to follow along with the tutorial you can clone the repository on GitHub. Download the β€œSerilogLogging” branch which contains the latest changes up to this point.

GitHub - Osempu/BlogAPI at SerilogLogging

Implementing the Repository pattern into our Web API πŸ•ΈοΈ

Let’s start implementing the repository pattern by adding a folder inside our Data folder called Repositories and inside it, we will create an interface named IPostRepositoryPattern this will be our repository interface for our posts.

The Repository interface πŸ’»

public interface IPostRepository 
{
    IEnumerable<Post> GetPost();
    Post GetPost(int id);
    void Add(Post post);
    void Edit(Post post);
    void Delete(int id);
}

On this interface, we define the CRUD methods that we need to interact with the data that is stored in our database, interfaces don’t define any implementation that is that there is no code inside of them because we do that on the actual repository class which we will be coding right now.

The repository class πŸ›οΈ

public class PostRepository : IPostRepository
{
    private readonly BlogDbContext context;

    public PostRepository(BlogDbContext context)
    {
        this.context = context;
    }

    public void Add(Post post)
    {
        context.Add(post);
        context.SaveChanges();
    }

    public void Delete(int id)
    {
        var post = context.Posts.Find(id);
        context.Remove(post);
        context.SaveChanges();
    }

    public void Edit(Post post)
    {
        context.Entry(post).State = EntityState.Modified;
        context.SaveChanges();
    }

    public IEnumerable<Post> GetPost()
    {
        var allPosts = context.Posts.ToList();
        return allPosts;
    }

    public Post GetPost(int id)
    {
        var post = context.Posts.Find(id);
        return post;
    }
}

This is the repository class where we actually have the implementation to communicate with the database using the BlogDbContext class. The repository class must implement the repository interface in order for the repository pattern to work and then and only then we can code the implementation.

Inject the BlogDbContext via Dependency Injection πŸ’‰

Before adding any code first we need to create a constructor and pass the BlogDbContext as a parameter and then initialize a BlogDbContext readonly field.

private readonly BlogDbContext context;

public PostRepository(BlogDbContext context)
{
    this.context = context;
}

Edit method

All the methods are basically the same as we had them on the controller class except to the Edit method in which we don’t need to pass the postId as a parameter neither we need to look for it to update it.

public void Edit(Post post)
{
    context.Entry(post).State = EntityState.Modified;
    context.SaveChanges();
}

Here we only need to pass the Post as a parameter and then we notify entity framework that the state of the post is modified so it starts to track the changes and after that, we can save the changes.

Modifying the PostsController class βš’οΈ

Now with our brand new repository class ready we need to use it in our controller class.

[ApiController]
[Route("api/post")]
public class PostsController : ControllerBase
{
    private readonly ILogger<PostsController> logger;
    private readonly IPostRepository repository;

    public PostsController(IPostRepository repository, ILogger<PostsController> logger)
    {
        this.logger = logger;
        this.repository = repository;
    }

    [HttpGet]
    public IActionResult GetPost()
    {
        var posts = repository.GetPost();
        logger.LogDebug($"Get method called, got {posts.Count()} results");
        return Ok(posts);
    }

    [HttpGet("{id:int}")]
    public IActionResult GetPost(int id)
    {
        try
        {
            var post = repository.GetPost(id);
            return Ok(post);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, $"Error getting post with id {id}");
            throw;
        }
    }

    [HttpPost]
    public IActionResult CreatePost(Post post)
    {
        try
        {
            repository.Add(post);
            return CreatedAtAction(nameof(GetPost), new { id = post.Id }, null);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Unexpected error on Post method");
            throw;
        }
    }

    [HttpPut]
    public IActionResult EditPost([FromBody] Post post)
    {
        repository.Edit(post);
        return NoContent();
    }

    [HttpDelete("{id:int}")]
    public IActionResult DeletePost(int id)
    {
        try
        {
            repository.Delete(id);

            return NoContent();
        }
        catch (Exception ex)
        {
            logger.LogError(ex, $"Unexpected error on Delete method trying to delete post with Id {id}");
            throw;
        }
    }
}

Again we will inject the IPostRepository interface via dependency injection in the controller constructor to be able to use it.

The code remains almost the same for all of the CRUD endpoints in fact now the code is a little bit more slim and readable thanks to the repository pattern but there is one exception and that is the EditPost method.

The EditPost method now only needs the Post as a parameter with the [FromBody] tag and inside we only make the call to the edit method from the repository we no longer need to look for the post to update or to manually manipulate the properties of the post to update it, we simply need to pass the post to the edit method and that would be all, that is a great improvement for our code.

Register the Repository as a service πŸ•β€πŸ¦Ί

The last part of the puzzle is to register our repository interface and class in the Program class so it can work the expected way. Add this line of code below the services.AddControllers(); line.

builder.Services.AddTransient<IPostRepository, PostRepository>();

Test the API πŸ§ͺ

Now you should be able to test the API and it should work the same as before but if you debug the application you will see how the API calls the Repository class and then it communicates to the database through the BlogDbContext.

Conclusion πŸŒ‡

Using the repository pattern helps us to separate the data access layer from the logic in our controller class(separation of concerns) and also helps us to avoid code duplication in the future if we need to call the blogs from another controller we can make use of the existing repository.

Follow me! 🚢🏾

If you enjoyed this article remember to follow me on my blog Unit Coding where I post at least twice a week about web API development, fun projects with blazor, and other C# and .NET tips and tricks of all kinds, thanks for your time and I hope you have a happy coding!

Did you find this article valuable?

Support Oscar Montenegro πŸ§™πŸΎβ€β™‚οΈπŸŒŸ by becoming a sponsor. Any amount is appreciated!

Β