Table of contents
- What is the Repository Pattern β
- Why is it Useful ππΎ
- Clone the repo to follow along π
- Implementing the Repository pattern into our Web API πΈοΈ
- The Repository interface π»
- The repository class ποΈ
- Modifying the PostsController class βοΈ
- Register the Repository as a service πβπ¦Ί
- Test the API π§ͺ
- Conclusion π
- Follow me! πΆπΎ
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!