DEV Community

Cover image for Building a microservice with C#, .NET Core and MongoDB - Part 1
Patricio Ferraggi
Patricio Ferraggi

Posted on

Building a microservice with C#, .NET Core and MongoDB - Part 1

If you are interested in reading this article in Spanish 🇪🇸, check out my blog:
The Developer's Dungeon

Hey guys! how have you been? In the latest weeks, I have been on going some interview process for C# Developer positions.

I work with asp.net core every day which means that when I am at home I don't pick C# as my training language. So the C# code I have in my Github is fairly old and belongs to the early years of my career.

So I decided to use the coding challenges that employers are giving me to create some newer C# code for my Github and at the same time create a nice guide that you can follow to improve your personal development.


What to expect from this tutorial?

At the end of the series, we will have a microservice with the following characteristics:

  • Integrated to MongoDB
  • Layered architecture with clear separation of concerns
  • API documentation
  • Docker support for deployment
  • Unit tested

Introduction

We have a small business, we sell 💻. Every time a new computer is sold we need to record the order with the amount and client who bought it.

We are gonna create a small microservice that connects to a MongoDB and allows us to create/read/update/delete orders and also get some extra computation about users spending.

Our Order model will be the following:

{
  "id": "guid",
  "userId": "guid",
  "amount": "int"
}
Enter fullscreen mode Exit fullscreen mode

Basic Definition

We start by creating an ASP.NET Core 3.1 Web Application with the API template and enabled Docker support in Visual Studio 2019.

ApplicationTemplate

Once that is created you will see a model and a controller for WeatherForecast which is the default template way of showing you that there is an API out of the box.

InitialStatus

After we delete the default code we start by creating our model and a basic OrdersController. We are gonna start by the Get and Post endpoints, that way we will be able to test adding and getting orders.

    [Route("v1/[controller]")]
    [ApiController]
    public class OrdersController : ControllerBase
    {
        /// <summary>
        /// Retrieves all orders
        /// </summary>
        /// <returns>a list with all the orders available</returns>
        [HttpGet]
        public async Task<IEnumerable<Order>> GetAll()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Retrieves the order that matches the id supplied
        /// </summary>
        /// <returns>one order model</returns>
        [HttpGet("{orderId}")]
        public async Task<ActionResult<Order>> Get(Guid orderId)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Creates a new order
        /// </summary>
        /// <returns>the newly created order</returns>
        [HttpPost]
        public async Task<ActionResult<Order>> Post([FromBody]OrderCreateUpdate orderRequest)
        {
            throw new NotImplementedException();
        }
    }
Enter fullscreen mode Exit fullscreen mode

In the previous code, we create the GetAll, Get and Post endpoints, we also include comments for the public API and we already versioned our API by using v1/[controller] naming in the endpoints. If you are paying attention you might have noticed that the Post has a model we haven't defined yet, OrderCreateUpdate.

When you are creating items in a database, you should not provide an OrderId, since the order doesn't exist yet, then it is a good practice to separate the API model in two so they can change independently. This is the model for OrderCreateUpdate:

{
  "userId": "guid",
  "amount": "int"
}
Enter fullscreen mode Exit fullscreen mode
        public Guid UserId { get; set; }
        public int Amount { get; set; }
Enter fullscreen mode Exit fullscreen mode

Connecting to the Database

For now, instead of creating a database or messing with docker from the start, we are gonna use an online database like the ones provided by MongoDBAtlas.

Back in the project, we need to find a way to configure the settings for connecting to our database, we do that by using the Options Pattern. We define the following configuration:

    public class OrdersServiceOptions
    {
        public string DatabaseConnectionString { get; set; }
        public string DatabaseName { get; set; }
        public string CollectionName { get; set; }
    }
Enter fullscreen mode Exit fullscreen mode

Now that we have our configuration ready we can move on to create our first Repository that will have the responsibility of connecting to our MongoDB instance.

This is the code for our OrdersRepository:

    public class OrdersRespository
    {
        private readonly IMongoDatabase database;
        private readonly IMongoCollection<Order> collection;
        private readonly ILogger<OrdersRespository> logger;

        public OrdersRespository(IOptions<OrdersProcessingServiceOptions> options, ILogger<OrdersRespository> logger)
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            var configuration = options.Value;
            var client = new MongoClient(configuration.DatabaseConnectionString);

            this.database = client.GetDatabase(configuration.DatabaseName);
            this.collection = database.GetCollection<Order>(configuration.CollectionName);
            this.logger = logger;
        }
    }
Enter fullscreen mode Exit fullscreen mode

As you can see the Repository creates the connection with the MongoDB by using the configuration options we define previously, it also accepts a logger we are gonna use in our methods. Next, we are gonna create the methods we will use for getting and creating Orders.

        public async Task<Order> GetOrder(Guid orderId) => await collection.Find(x => x.Id == orderId).FirstOrDefaultAsync();

        public async Task<IEnumerable<Order>> GetAll() => await collection.Find(_ => true).ToListAsync();

        public async Task<Order> CreateOrder(Order order)
        {
            try
            {
                await collection.InsertOneAsync(order);

                return order;
            }
            catch (Exception ex)
            {
                logger.LogError(ex.Message, ex);
                return null;
            }
        }
Enter fullscreen mode Exit fullscreen mode

The 3 methods are very simple, we get a single order by id, we get all orders and we create a new order (with the proper logging if the creation fails). After adding these methods we create an interface of this repository so we can start using the Dependency Injection that comes with .NET Core.

   public interface IOrdersRespository
    {
        Task<Order> GetOrder(Guid orderId);
        Task<IEnumerable<Order>> GetAll();
        Task<Order> CreateOrder(Order order);
    }
Enter fullscreen mode Exit fullscreen mode

Connecting the pieces

Let's go back to our Controller and let's make the proper calls to our Repository.

    [ApiController]
    [Route("v1/[controller]")]
    public class OrdersController : ControllerBase
    {
        private readonly IOrdersRepository ordersRepository;

        public OrdersController(IOrdersRepository ordersRepository)
        {
            this.ordersRepository = ordersRepository;
        }

        /// <summary>
        /// Retrieves all orders
        /// </summary>
        /// <returns>a list with all the orders available</returns>
        [HttpGet]
        public async Task<IEnumerable<Order>> GetAll()
        {
            return await ordersRepository.GetAll();
        }

        /// <summary>
        /// Retrieves the order that matches the id supplied
        /// </summary>
        /// <returns>one order model</returns>
        [HttpGet("{orderId}")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task<ActionResult<Order>> Get(Guid orderId)
        {
            var result = await ordersRepository.GetOrder(orderId);

            if (result == null) return NotFound();

            return Ok(result);
        }

        /// <summary>
        /// Creates a new order
        /// </summary>
        /// <returns>the newly created order</returns>
        [HttpPost]
        [ProducesResponseType(StatusCodes.Status201Created)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        public async Task<ActionResult<Order>> Post([FromBody]OrderCreateUpdate orderRequest)
        {
            var order = new Order()
            {
                Amount = orderRequest.Amount,
                UserId = orderRequest.UserId
            };

            var result = await ordersRepository.CreateOrder(order);

            if (result == null) return BadRequest();

            return StatusCode(201, result);
        }
    }
Enter fullscreen mode Exit fullscreen mode

We add the dependency of the IOrdersRepository in the constructor and we use it from the endpoints. We also handle different responses depending on the result of Repository and we explicitly define the possible responses using ProducesResponseType.

Today's result?

POST

POST

GET

GET

GET ALL

GET ALL

If you have some experience building modern API's you are probably in shock. Is this guy really gonna use the same model for the API and database? is he gonna call the repository from the controller? why is everything in the same project?

Don't worry, we will handle everything before this series end, I promise you it is gonna something worth putting in your Github 😄


That was a lot, I know, I also didn't go into detail of all the application patterns used since it would have been impossible, but on every pattern used there is a link to the official guide on how to implement it in ASP.NET Core, Microsoft tutorials are super good and they will give you the details missing.

I hope you liked this first part, in the next one we will add the Update, Delete, and the combined information endpoint and if there is time we will also start separating concerns better and polish our service.

As always, if you liked or not please let me know in the comments, and share it 😄

Top comments (0)