Building a Microservice Using C#, ASP.NET Core MVC, and EF

In the previous lessons, we have seen how to isolate the modules and database to convert Sportopia Inc’s monolith application. As part of this lesson, let us build the Product Service in our company’s plan to transition to microservices architecture.

Tools and Technologies

We will be using the following tools and technologies while transitioning our monolithic application to a microservice-style architecture.

  • Visual Studio 2017 or later
  • C# 8.0
  • ASP.NET Core MVC/Web API
  • Entity Framework Core
  • SQL Server 2008 R2 or later

The Database

We have already seen how to isolate and segregate all the tables related to Products into a separate database. Let’s call that the Products Database. We will use this database in our product service, as shown in the following screenshot:

Migrations

Now that we have our database ready, let us now look at how we can migrate both our existing code and database to ensure that they fit right in with our new architectural style.

Code migration does not merely involve pulling out a few lines of code from the existing monolithic application and bundling it with our newly created Product service. That is because, the old application was dealing with a single large database. In our current setup, we have a separate database only for our product service. With that in mind, we need to make appropriate changes to our microcomponents(classes, functions, etc.,).

In our final product service, that we plan to create in ASP.NET Core, we will be working with a model and controller to create our RESTful API.

Let’s quickly define what a Model and Controller are going to be in our Products Service:

Model: This is an object that represents the data in the product service. In our case, the identified models are stacked into product and category fields. In our code, models are nothing but a set of simple C# classes. When we talk in terms of EF Core, they are commonly referred to as Plain Old CLR Objects (POCOs). POCOs are nothing but simple entities without any data access functionality.

Controller: This is a simple C# class that inherits an abstract class controller of Microsoft.AspNetCore.Mvc namespace. It handles HTTP requests and is responsible for the creation of the HTTP response to be sent back. In our product service, we have a product controller that handles everything.

Creating the project

  1. Start Visual Studio.
  2. Create a new project by navigating to File | New | Project.
  3. From the template options available, select ASP.NET Core Web Application
  4. Enter the project name as Sportopia.SportsStore.ProductService, and click OK.
Click to enlarge
  1. From the template screen, select Web Application (Model-View-Controller) and make sure you selected .NET Core and ASP.NET Core from the options, as shown in the following screenshot:
  1. Leave the rest of the options as the default and click Create. The new solution should look like the following screenshot:

Adding the model

Now that we have created the project, let us go ahead and create our models as requrired.

Let’s add two models. One for Product and one for Category.

Right-click on the Models folder and choose Add | New Item | Class and name it Product; repeat this step, add another class, and name it Category.

Now, add the properties that depict our product database column name to the Product and Category tables respectively.

The following code snippet depicts what our Product model class will look like:

using System;
namespace Sportopia.SportsStore.ProductService.Models
{
    public class Product
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public string Image { get; set; }
        public decimal Price { get; set; }
        public Guid CategoryId { get; set; }
        public virtual Category Category { get; set; }
    }
}

The preceding code example represents a Product model, and it contains the following:

  • Id is a Globally Unique Identifier (GUID) and represents a record ID.
  • Name is a string-type property and holds a product name.
  • Description is a string-type property and holds a complete description of a product.
  • Image is a string-type property and holds a Base64 string.
  • Price is a decimal-type property and holds the price of a product.
  • CategoryId is the GUID; it holds a record ID of a category of the product.
  • Category is a virtual property and contains complete information on the category of the product.

The following code snippet shows what our Categorymodel class will look like:

using System;
using System.Collections.Generic;
namespace Sportopia.SportsStore.ProductService.Models
{
    public class Category
    {
        public Category()
        {
            Products = new List<Product>();
        }
        public Guid Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public IEnumerable<Product> Products { get; set; }
    }
}

The preceding code represents our category model, which contains the following:

  • Id is a GUID and represents a record ID.
  • Name is a string type property and holds a category name.
  • Description is a string type property and holds a complete description of the category.
  • Products is a collection of all the products that belong to the category of the current record.

Adding a repository

A repository is nothing but a simple C# class that contains the logic to retrieve data from the database and map it to the model.

  1. Create a new folder, and then name it Persistence.
  2. Add the IProductRepository interface and a ProductRepository class that will implement the IProductRepository interface.
  3. Again, we name the folder Persistence to follow the general principle for easy identification.

The following code snippet provides an overview of the IProductRepository interface:

using System;
using System.Collections.Generic;
using Sportopia.SportsStore.ProductService.Models;

namespace Sportopia.SportsStore.ProductService.Persistence
{
  public interface IProductRepository
 {
 void Add(Product product);
 IEnumerable<Product> GetAll();
 Product GetBy(Guid id);
 void Remove(Guid id);
    void Update(Product product);
  }
}

Our IProductRepository interface has all of the required methods:

  • Add: This method is responsible for adding a new product.
  • GetAll fetches all the product records and returns a collection of products.
  • GetBy fetches one product, based on the given product ID.
  • Remove deletes a specific record.
  • Update is responsible for updating the existing record.

The next code snippet provides an overview of the ProductRepository class (it is still without any implementation, and it does not have any interaction with the database yet):

using Sportopia.SportsStore.ProductService.Contexts;
using Sportopia.SportsStore.ProductService.Models;
using Microsoft.EntityFrameworkCore;

 namespace Sportopia.SportsStore.ProductService.Persistence
 {
 public class ProductRepository : IProductRepository
 {
 public void Add(Product Product)
 {
 throw new NotImplementedException();
 }
 public IEnumerable<Product> GetAll()
 {
 throw new NotImplementedException();
 }
 public Product GetBy(Guid id)
 {
  throw new NotImplementedException();
 }
 public void Remove(Guid id)
 {
 throw new NotImplementedException();
 }
 public void Update(Product Product)
 {
 throw new NotImplementedException();
 }
   }
 }

The preceding code implements the IProductRepository interface. The code example is for demo purposes, so we did not add definitions to the implemented methods.

Next, let’s see how to register our repository using ConfigureServices of Startup.cs.

Registering repositories

For ProductService, we will use built-in dependency injection support with ASP.NET Core. Open Startup.cs and Add the repository to the ConfigureServices method. It should look like this:

using Sportopia.SportsStore.ProductService.Persistence;
public void ConfigureServices(IServiceCollection services)
{
 // Add framework services.
 
  services.AddSingleton<IProductRepository, 
  ProductRepository>();
}

Adding a Product Controller

Controller will be responsible for responding to the incoming HTTP requests with the applicable HTTP response.

Right-click on the controllers folder, choose the Add | New Item option, and select API Controller Class. Name it ProductController. Here, we are going to utilize whatever code and functionality we can from the monolithic application. Go back to the legacy code, and look at the operations you’re performing there; you can borrow them for our ProductController class. 

After we have made the required modifications to ProductController, it should look something similar to this:

using Microsoft.AspNetCore.Mvc;
using Sportopia.SportsStore.ProductService.Persistence;
namespace Sportopia.SportsStore.ProductService.Controllers
 {
   [Route("api/[controller]")]
   public class ProductController : Controller
   {
     private readonly IProductRepository _ProductRepository;
     public ProductController(IProductRepository ProductRepository)
     {
       _ProductRepository = ProductRepository;
     }
   }
 }

In the previous section, we registered ProductRepository and in ProductController, we are using the repository. We used a constructor injection in the preceding code example, and we used a parameterized constructor with a parameter of the IProductRepository type.

The ProductService API

Now, we will create ProductService; we require the following APIs:

API ResourceDescription
GET /api/ProductGets a list of products
GET /api/Product/{id}Gets a product
PUT /api/Product/{id}Updates an existing product
DELETE /api/Product/{id}Deletes an existing product
POST /api/ProductAdds a new product

Let’s see how to add EF Core support to these

Adding EF Core support

Before going further, we need to add EF Core support, so that our service can interact with the product database. So far, we have not added any methods to our repository that could interact with the database. To add EF Core support, we need to add EF Core’s sqlserver package (we are adding the sqlserver package because we are using SQL Server as our DB server). Open the NuGet Package Manager (Tools | NuGet Package Manager | Manage NuGet Package).

Open the NuGet Package and search for Microsoft.EntityFrameworkCore.SqlServer:

Now that we added the EF Core package for SQL Server support; now we need to create a context so our models can interact with our product database. We have the Product and Category models, and you can refer to the following steps:

  1. Add a new folder, and then name it Contexts—it is not compulsory to add a new folder.
  2. In the Contexts folder, add a new C# class and name it ProductContext. We are creating DbContext for ProductDatabase, so to make it similar here, we create ProductContext.
  3. Make sure the ProductContext class inherits the DbContext class.
  4. Make the changes, and our ProductContext class will look like this:
using Sportopia.SportsStore.ProductService.Models;
 using Microsoft.EntityFrameworkCore;
 namespace Sportopia.SportsStore.ProductService.Contexts
 {
   public class ProductContext : DbContext
   {
     public ProductContext(DbContextOptions<
     ProductContext>options): base(options)
     { }
     public ProductContext()
     { }
     public DbSet<Product> Products { get; set; }
     public DbSet<Category> Categories { get; set; }
   }
 }

As a next step, ew need to add a provider and connection string, so that ProductContext can talk with our database.

Once again, open the Startup.cs file and add the SQL Server db provider for our EF Core support, under the ConfigureServcies method. Once you add the provider’s ConfigureServcies method, our Startup.cs file will look like this:

public void ConfigureServices(IServiceCollection services)
 {
   // Add framework services.
    
   services.AddSingleton<IProductRepository, ProductRepository>();
   services.AddDbContext<ProductContext>(o =>o.UseSqlServer
   (Configuration.GetConnectionString("ProductsConnection" )));
 }

Open the appsettings.json file, and then add the required database connection string. In our provider, we have already set the connection key as ProductConnection. Now, add the following code to set the connection string with the same key (change Data Source to your data source):

{
   "ConnectionStrings": 
   {
     "ProductConnection":
     "Data Source=.SQLEXPRESS;Initial Catalog=ProductsDB;
     IntegratedSecurity=True;MultipleActiveResultSets=True"
   }
 }

EF Core migrations

Although we have already created our product database, we should not underestimate the power of EF Core migrations. EF Core migrations will be helpful for us to perform any future modifications to the database. This modification could be in the form of a simple field addition or any other update to the database structure. We can simply rely on these EF Core migration commands every time to make the necessary changes for us. To utilize this capability, follow the steps below:

  1. Go to Tools | NuGet Package Manager | Package Manager Console.
  2. Run the following commands from Package Manager Console:
Install-Package Microsoft.EntityFrameworkCore.Tools
Install-Package Microsoft.EntityFrameworkCore.Design 
  1. To initiate the migration, run this command:
Add-Migration ProductDB

It is important to note that this is to be done only the first time (when we do not yet have a database created by this command). Now, whenever there are any changes in your model, simply execute the following command: 

Update-Database

At this point, we are done with our ProductDatabase creation. Now, it’s time to migrate our existing database.

Database migration

There are many different ways to migrate from the old to the existing database. Our monolithic application, which presently has a huge database, contains a large number of records as well. It is not possible to migrate them by simply using a database SQL Script.

We need to explicitly create a script to migrate the database with all of its data. Another option is to go ahead and create a DB package as required. Depending on the complexity of your data and the records, you might need to create more than one data package to ensure that the data is migrated correctly to our newly created database, ProductDB.

 In general, SQL Script suffice for DB (both schema and data) migration. But if the database is large, even if we have broken it down into small databases per service, we need to take more precautions when we work on DB migrations.

Revisiting repositories and the controller

We are now ready to facilitate interaction between our model and database via our newly created repositories. After making the appropriate changes to ProductRepository, it will look like below:

using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using Sportopia.SportsStore.ProductService.Contexts;
using Sportopia.SportsStore.ProductService.Models;
namespace Sportopia.SportsStore.ProductService.Persistence
 {
   public class ProductRepository : IProductRepository
   {
     private readonly ProductContext _context;
     public ProductRepository(ProductContext context)
     {
       _context = context;
     }
     public void Add(Product Product)
     {
       _context.Add(Product);
       _context.SaveChanges();
     }
     public IEnumerable<Product> GetAll() =>
     _context.Products.Include(c => c.Category).ToList();
     ...
   }
 }

In the preceding code, we are using constructor injection to initialize the _context field. Further, the Add method inserts the new product into our database. Similarly, the GetAll method returns the collections of all available products in our database.

In our application, the model represents our table of the database and our ViewModel is one that provides an output for a View. 

A ViewModel contains the various properties to hold/represent the data. This data is displayed on our View of the application. ViewModel doesn’t need to have all read/write properties. This data is meant to be shown to the end user on the UI page. This is not a domain model; in our case, we have ProductViewModel as our ViewModel and Product as our domain model.

Add a new class to the models folder and name it ProductViewModel. We do this because, in our monolithic application, whenever we search for a product, it should be displayed in its product category. To support this, we need to incorporate the necessary fields into our ViewModel. Our ProductViewModel class will look like below:

using System;
 namespace Sportopia.SportsStore.ProductService.Models
 {
   public class ProductViewModel
   {
     public Guid ProductId { get; set; }
     public string ProductName { get; set; }
     public string ProductDescription { get; set; }
     public string ProductImage { get; set; }
     public decimal ProductPrice { get; set; }
     public Guid CategoryId { get; set; }
     public string CategoryName { get; set; }
     public string CategoryDescription { get; set; }
   }
 }

ProductViewModel consists of the following:

  • ProductId contains the GUID of the product.
  • ProductName contains the product name.
  • ProductImage contains the product image.
  • ProductPrice contains the product price.
  • CategoryId represents the GUID of the category of the current product.
  • CategoryName represents the category name.
  • CategoryDescription gives the complete description of the category.

Revisiting ProductController

Finally, we are ready to create a RESTful API for ProductService. After the changes are made, here is what ProductController will look like:

using System.Linq;
 using Sportopia.SportsStore.ProductService.Models;
 using Sportopia.SportsStore.ProductService.Persistence;
 using Microsoft.AspNetCore.Mvc;
 namespace Sportopia.SportsStore.ProductService.Controllers
 {
   [Route("api/[controller]")]
   public class ProductController : Controller
   {
     private readonly IProductRepository _productRepository;
     public ProductController(IProductRepository 
     productRepository) => _productRepository = productRepository;

    [HttpGet]
    [Route("productlist")]
    public IActionResult GetList() => new
    OkObjectResult(_productRepository.GetAll().
    Select(ToProductvm).ToList());

    [HttpGet]
    [Route("product/{productid}")]
    public IActionResult Get(string productId)
 {
 var productModel = _productRepository.GetBy(new Guid(productId));
      return new OkObjectResult(ToProductvm(productModel));
    }

     ...
   }
 }

We have completed all of the tasks that are required for web API creation. Now, we need to tweak a few things so that the client can get information about our web APIs. So, in the upcoming section, we will add Swagger support to our web API documentation.

Adding Swagger support

Swagger is a tool and is built on the OpenAPI Specification, which helps us to easily document our APIs. With the help of Swagger, we can easily create documentation for our various APIs/services. These documents are very useful for the end users who will be using these APIs.

  1. Open NuGet Package Manager.
  2. Search for the Swashbuckle.AspNetCore package.
  3. Select the package and then install the package.
  4. It will install the following:
    Swashbuckle.AspNetCore
    Swashbuckle.AspNetCore.Swagger
    Swashbuckle.AspNetCore.SwaggerGen
    Swashbuckle.AspNetCore.SwaggerUI
  5. Open the Startup.cs file, move to the ConfigureServices method, and add the following lines to register the Swagger generator:
//Register Swagger
services.AddSwaggerGen(swagger =>
{
    swagger.SwaggerDoc("v1", new Info { Title = "Product APIs", Version = "v1" });
});
  1. Next, in the Configure method, add the following code:
app.UseSwagger();

app.UseSwaggerUI(option =>
{
 option.SwaggerEndpoint("/swagger/v1/swagger.json", "Product API V1");
});
  1. Press F5, and then run the application; you’ll get a default page.
  2. Open the Swagger documentation by adding swagger in the URL. So, the URL is http://localhost:44338/swagger/:

With this, we have completed the transition of our monolith .NET application to microservices, and we discussed the step-by-step transition of ProductService. It is complete working Product API, from its foundation to the documentation.

This lesson is part of the course Microservices with C#, .NET Core and Azure. Use navigation on the right.