Open to work
Published on

A Simple Way to Implement CRUD Operations on MongoDB with .NET and Generic Repository

Authors

Header

Prerequisites

Before we start building our API, make sure you have the following installed:

Setting up the Project

Let's begin by creating a new .NET 7 API project. Open your terminal and run the following commands:

dotnet new webapi -n Blog.API
cd Blog.API

This will create a new .NET 7 API project named Blog.API.

Installing MongoDB Driver

To interact with MongoDB, you'll need to install the MongoDB driver for .NET. Add the MongoDB driver to your project using the following command:

dotnet add package MongoDB.Driver

Creating the MongoDB Configuration

In your appsettings.json, add the MongoDB connection string and database name as follows:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "MongoSettings": {
    "MongoConnection": "See user Secret",
    "Password": "See user Secret",
    "DatabaseName": "see user secret",
    "PostCollectionName": "see user secret"
  },
}

Configure Program.cs to read MongoSettings using IOptions, We need to create a class to get the settinng BlogDbSettings.cs

    public class BlogDbSettings
    {
        public string MongoConnection { get; set; } = null!;
        public string DatabaseName { get; set; } = null!;
        public string PostCollectionName { get; set; } = null!;
    }

In Program.cs

builder.Services.Configure<BlogDbSettings>(
    builder.Configuration.GetSection("MongoSettings"));

Creating Data Model

Let's create a simple data model for our API, Add Class library project named 'Blog.Domain`, create enttity folder and add post model

    public class Post
    {
        [BsonId]
        [BsonRepresentation(BsonType.ObjectId)]
        public string? Id { get; set; }
        public string? Title { get; set; }
        public string? Summary { get; set; }
    }

Implementing the Generic Repository

Add Blog.Storage class library project in the solution. In the Storage project create a new class called MongoRepository.cs and Interface IRepository.cs. This class will be our generic repository:

Create collection using MongoSettings

    public class MongoRepository<T> : IRepository<T> where T : class
    {
        private readonly IMongoCollection<T> _collection;

        public MongoRepository(IOptions<BlogDbSettings> blogSettings)
        {
            var client = new MongoClient(blogSettings.Value.MongoConnection);
            var database = client.GetDatabase(blogSettings.Value.DatabaseName);
            _collection = database.GetCollection<T>(blogSettings.Value.PostCollectionName);
        }
    }

Now implement the CRUD funtionalities

 public async Task InsertAsync(T entity)
        {
            await _collection.InsertOneAsync(entity);
        }

        public async Task UpdateAsync(object id, T entity)
        {
            var filter = Builders<T>.Filter.Eq("_id", id);
            await _collection.ReplaceOneAsync(filter, entity);
        }

        public async Task DeleteAsync(T entity)
        {
            var filter = Builders<T>.Filter.Eq("_id", GetId(entity));
            await _collection.DeleteOneAsync(filter);
        }

        public async Task DeleteAsync(Expression<Func<T, bool>> filter)
        {
            await _collection.DeleteManyAsync(filter);
        }

        public async Task<T> GetByIdAsync(object id)
        {
            var filter = Builders<T>.Filter.Eq("_id", id);
            return await _collection.Find(filter).SingleOrDefaultAsync();
        }

        public async Task<IEnumerable<T>> GetAllAsync()
        {
            return await _collection.Find(_ => true).ToListAsync();
        }

So the MongoRepository.cs will look like this

 public class MongoRepository<T> : IRepository<T> where T : class
    {
        private readonly IMongoCollection<T> _collection;

        public MongoRepository(IOptions<BlogDbSettings> blogSettings)
        {
            var client = new MongoClient(blogSettings.Value.MongoConnection);
            var database = client.GetDatabase(blogSettings.Value.DatabaseName);
            _collection = database.GetCollection<T>(blogSettings.Value.PostCollectionName);
        }

        public async Task InsertAsync(T entity)
        {
            await _collection.InsertOneAsync(entity);
        }

        public async Task UpdateAsync(object id, T entity)
        {
            var filter = Builders<T>.Filter.Eq("_id", id);
            await _collection.ReplaceOneAsync(filter, entity);
        }

        public async Task DeleteAsync(T entity)
        {
            var filter = Builders<T>.Filter.Eq("_id", GetId(entity));
            await _collection.DeleteOneAsync(filter);
        }

        public async Task DeleteAsync(Expression<Func<T, bool>> filter)
        {
            await _collection.DeleteManyAsync(filter);
        }

        public async Task<T> GetByIdAsync(object id)
        {
            var filter = Builders<T>.Filter.Eq("_id", id);
            return await _collection.Find(filter).SingleOrDefaultAsync();
        }

        public async Task<IEnumerable<T>> GetAllAsync()
        {
            return await _collection.Find(_ => true).ToListAsync();
        }
    }

Here is the IRepository.cs

    public interface IRepository<T>
    {
        Task InsertAsync(T entity);
        Task UpdateAsync(object id, T entity);
        Task DeleteAsync(T entity);
        Task DeleteAsync(Expression<Func<T, bool>> filter);
        Task<T> GetByIdAsync(object id);
        Task<IEnumerable<T>> GetAllAsync();
    }

Creating the Controller

Now, let's create a controller to handle the API endpoints. In the Controllers folder, add a class named PostController.cs:

    [Route("api/posts")]
    [ApiController]
    public class PostController : ControllerBase
    {
        private readonly IPostService _postService;

        public PostController(IPostService postService)
        {
            _postService = postService;
        }

        [HttpPost]
        public async Task<IActionResult> CreatePost([FromBody] Post post)
        {
            await _postService.InsertPostAsync(post);
            return CreatedAtAction(nameof(GetPostById), new { id = post.Id }, post);
        }

        [HttpGet("{id}")]
        public async Task<IActionResult> GetPostById(string id)
        {
            if (!ObjectId.TryParse(id, out var postId))
                return BadRequest("Invalid ID format");

            var post = await _postService.GetPostByIdAsync(postId);
            if (post == null)
                return NotFound();

            return Ok(post);
        }

        [HttpGet]
        public async Task<IActionResult> GetAllPosts()
        {
            var posts = await _postService.GetAllPostsAsync();
            return Ok(posts);
        }

        [HttpPut("{id}")]
        public async Task<IActionResult> UpdatePost(string id, [FromBody] Post post)
        {
            if (!ObjectId.TryParse(id, out var postId))
                return BadRequest("Invalid ID format");

            var existingPost = await _postService.GetPostByIdAsync(postId);
            if (existingPost == null)
                return NotFound();

            existingPost.Title = post.Title;
            existingPost.Content = post.Content;

            await _postService.UpdatePostAsync(existingPost);

            return NoContent();
        }

        [HttpDelete("{id}")]
        public async Task<IActionResult> DeletePost(string id)
        {
            if (!ObjectId.TryParse(id, out var postId))
                return BadRequest("Invalid ID format");

            await _postService.DeletePostAsync(postId);
            return NoContent();
        }
    }

Dependency Injection

In the Program.cs file, add the following code to configure dependency injection:

builder.Services.AddScoped(typeof(IRepository<>), typeof(MongoRepository<>));

Now run the project

Conclusion

In this article, we have explored how to create a .NET 7 API with CRUD operations using MongoDB and a generic repository. This approach simplifies data access and reduces code duplication, making your API more maintainable and scalable. You can extend this pattern to other data models and build powerful APIs for various applications.