Select Page
Microservices with ASP.NET Core 3.1

Microservices with ASP.NET Core 3.1

Microservices is a type of Architecture in which application is created as multiple small independent serviceable components. This article will cover in detail how to create microservices with ASP.NET Core, Serilog, Swagger UI, Health Checks & Docker containers.

Microservices Architecture

Microservices with ASP.NET Core

The Microservices architecture style is shown in the figure above. Microservices Architecture is a style in which one large application is developed as a set of small independent services. Here each service implements a specific functionality and has its own data store. Each service functionality should be small enough to implement just one use case and big enough to provide some value. Each service should be deployable separately so that it can be scaled independently. As far as possible these services should be independent of each other and if there is a need for inter-service communication then some lightweight communication protocol can be used.

Identity Provider is used to provide user authentication services to an application.

To know details about Identity Provider & also to know about how to secure your ASP.NET Core based application you can check my series on ASP.NET Core Security

API Gateway is a single entry point for all requests that help in managing the endpoints and coordinates with different services.

Container is a standard unit of software that bundles application or feature and all of its dependencies so that application can be deployed quickly and reliably on any new system that has container host.

Container Orchestration is a piece of software that is used to manage the life-cycle of containers in a large application. This helps to scale application on basis of the load.

Microservice is the actual small independent service which is bundled in a container along with it dependencies

Data Store is used to store microservice data and the basic principle is that each service manages its own data.

Monolithic v/s Microservices

MonolithicMicroservices
Single service/application should contain all the business functionalitySingle service should contains only one business functionality
All service are tightly coupledAll services are loosely coupled
Application is developed in one single programming languageEach service can be in different programming language
Single database for all services.Each service has separate database
All services needs to be deployed together on VMEach service can be deployed on separate VM
All services run in same process so if one service goes down then whole application breaksEach service runs in different process so failure of one service does not affects other services
Difficult to scale a particular service as new instance will have to have all servicesCan be Scaled easily as any single service can be deployed independently
Single large team works on whole applicationSeparate small team work on each Service which are more focused.
Simple to develop & test small applicationsAdd complexity to the application by the fact that its a distributed system

Here is a one of the good books on Rest API with ASP.NET Core

Why microservices with ASP.NET Core?

.NET Core provides following advantages which works for microservices

  • A light-weight framework built from ground up
  • Cross-platform support
  • Optimized for containerization

Implement Microservices with ASP.NET Core

Here we will cover in detail the step by step process to create microservice with ASP.NET Core. We will be creating an order service that will provide endpoints to Add, Cancel, Get Order By Id & Get Order(s) By Customer Id in the application. This demo has been executed in Visual Studio 2019 version 16.6.2

Create Service with CRUD operations

Create ASP.NET Core Project

For microservices demo we will be creating a ASP.NET Core 3.1 Web API project.

ASP.NET Core 3.1 API Project Creation

Implement Order Service

We will be creating order microservice which will contain functionality only related to orders. We will be implementing following endpoints

  • Add – To create a new order
  • Cancel – To cancel an existing order
  • GetById – Get Order by Id
  • GetByCustomerId – Get all orders for Customer Id

Below we will quickly add the entity model for Order & enable entity framework core for order microservice.

If you need further details on how an entity framework works then check my other article on Entity Framework Core in ASP.NET Core 3.1

Add Model

Add an order entity class

public class Order
{
    public string Id { get; set; }
    public string ProductId { get; set; }
    public double Cost { get; set; }
    public DateTime Placed { get; set; }
    public string CustomerId { get; set; }
    public string Status { get; set; }
}
Add CRUD operations to the API with entity framework core

We will make use of Entity Framework Core to implement database operations for the order service.

Install required packages for entity framework core
Install-Package Microsoft.EntityFrameworkCore
Install-Package Microsoft.EntityFrameworkCore.Design
Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Tools
Add database context class

This is the main class that co-ordinates with entity framework functionality for a given model class.

public interface IApplicationDbContext
{
    DbSet<Order> Orders { get; set; }
    Task<int> SaveChanges();
}
public class ApplicationDbContext : DbContext, IApplicationDbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
    {
    }
    public DbSet<Order> Orders { get; set; }
    public new async Task<int> SaveChanges()
    {
        return await base.SaveChangesAsync();
    }
}
Add Order Repository

The repository is a component that encapsulates the objects related to data storage and operations performed over them. DbContext is passed as a parameter in the constructor using dependency injection.

public interface IOrderRepository
{
    Task<string> Add(Order order);
    Task<Order> GetById(string id);
    Task<string> Cancel(string id);
    Task<Order> GetByCustomerId(string custid);
}
public class OrderRepository : IOrderRepository
{
    private IApplicationDbContext _dbcontext;
    public OrderRepository(IApplicationDbContext dbcontext)
    {
        _dbcontext = dbcontext;
    }

    public async Task<string> Add(Order order)
    {
        _dbcontext.Orders.Add(order);
        await _dbcontext.SaveChanges();
        return order.Id;
    }

    public async Task<string> Cancel(string id)
    {
        var orderupt = await _dbcontext.Orders.Where(orderdet => orderdet.Id == id).FirstOrDefaultAsync();
        if (orderupt == null) return "Order does not exists";

        orderupt.Status = "Cancelled";

        await _dbcontext.SaveChanges();
        return "Order Cancelled Successfully";
    }

    public async Task<Order> GetByCustomerId(string custid)
    {
        var order = await _dbcontext.Orders.Where(orderdet => orderdet.CustomerId == custid).FirstOrDefaultAsync();
        return order;
    }

    public async Task<Order> GetById(string id)
    {
        var order = await _dbcontext.Orders.Where(orderdet => orderdet.Id == id).FirstOrDefaultAsync();
        return order;
    }
}
Connect application to the database

Specify the SQL Server connection string in appsettings.json file.

  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\mssqllocaldb;Database=OrderDb;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
Register services in startup class

You need to configure the database context & order repository as a service in method ConfigureServices in the startup class

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
                    options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection"),
                    ef => ef.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)));
    services.AddScoped<IApplicationDbContext>(provider => provider.GetService<ApplicationDbContext>());

    services.AddTransient<IOrderRepository, OrderRepository>(); services.AddControllers();

//Remaining code has been removed
}
Add Migrations

To automate the migrations & create a database we need to run the following commands in the package manager console.

add-migration InitialMigration
update-database
EF Core Migrations
EF Core Generated Database

One thing to note here is that table is created for order details only. Product & Customer tables are not created with foreign key reference as you have to keep microservice small & focussed on one single functionality. Product & Customer will be in a separate database of their own with their own microservice implementation. If you need product details or customer details to be displayed as part of order details then you need to call respective microservice and fetch required details.

Add Order Controller

Here is the code for the order controller which has been added to expose the endpoints of the order microservice. Order Repository has been passed as a constructor parameter using dependency injection

[Route("api/[controller]")]
[ApiController]
public class OrderController : ControllerBase
{
    private IOrderRepository _orderRepository;
    public OrderController(IOrderRepository orderRepository)
    {
        _orderRepository = orderRepository;
    }

    [HttpPost]
    [Route("Add")]
    public async Task<ActionResult> Add([FromBody] Order orderdet)
    {
        string orderid = await _orderRepository.Add(orderdet);
        return Ok(orderid);
    }

    [HttpGet]
    [Route("GetByCustomerId/{id}")]
    public async Task<ActionResult> GetByCustomerId(string id)
    {
        var orders = await _orderRepository.GetByCustomerId(id);
        return Ok(orders);
    }

    [HttpGet]
    [Route("GetById/{id}")]
    public async Task<ActionResult> GetById(string id)
    {
        var orderdet = await _orderRepository.GetById(id);
        return Ok(orderdet);
    }

    [HttpDelete]
    [Route("Cancel/{id}")]
    public async Task<IActionResult> Cancel(string id)
    {
        string resp = await _orderRepository.Cancel(id);
        return Ok(resp);
    }
}

Our order service is ready to perform operations. But to make it a microservice we will have to enable features like Logging, Exception Handling, Documentation, Monitoring, Containerization, etc.

Add Web API versioning

Microservices should be easy to change without breaking existing clients and also should be able to support multiple versions side by side so Web API versioning will help us achieve this.

Web API versioning is a feature using which we can implement multiple versions of the same API so that different clients can work with the required version of API.

Implement Web API versioning using URL

In Web API versioning using URL, the version number is part of the URL i.e. http://server:port/api/v1/order/add

Install Web API versioning package
Install-Package Microsoft.AspNetCore.Mvc.Versioning
Configure Web API versioning in Startup class

Enable support for Web API versioning in ConfigureServices method in the startup.cs file.

public void ConfigureServices(IServiceCollection services)
{
    services.AddApiVersioning(apiVerConfig =>
    {
        apiVerConfig.AssumeDefaultVersionWhenUnspecified = true;
        apiVerConfig.DefaultApiVersion = new ApiVersion(new DateTime(2020, 6, 6));
    });
    //Remaining code has been removed
 }
Add URL Web API versioning to order controller

You need to add parameter v{version:apiVersion} in route attribute like Route(“api/v{version:apiVersion}/[controller]”) so that API version becomes part of URL.

[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class OrderController : ControllerBase
{
//Remaining code has been removed
}

If you need further details on Web API versioning in ASP.NET Core then check my other article on Web API Versioning in ASP.NET Core 3.1

Newsletter Subscription

Stay updated! Instantly get notified about my new articles in your mailbox by subscribing via email

Add logging to microservice

After deploying to production it should be easy to track and analyze issues. Logs help us to analyze complex issues which sometimes might be difficult to simulate. There will always be a need to troubleshoot application issues for which logs will be required for analysis.

Implement logging with Serilog

There are many third-party providers and one of these is Serilog. Serilog is a popular third party logging provider that is supported in ASP.NET Core Logging.

Install required Serilog packages
Install-Package Serilog.AspNetCore
Install-Package Serilog.Extensions.Logging
Install-Package Serilog.Extensions.Hosting
Install-Package Serilog.Sinks.RollingFile
Install-Package Serilog.Sinks.Async
Add Serilog configuration

Serilog RollingFile Sink is implemented by adding configuration to the appsettings.json file. Log level can be specified by setting the log level value in the property named MinimumLevel.

  "Serilog": {
    "MinimumLevel": "Information",
    "WriteTo": [
      {
        "Name": "Async",
        "Args": {
          "configure": [
            {
              "Name": "RollingFile",
              "Args": {
                "pathFormat": "Serilogs\AppLogs-{Date}.log",
                "outputTemplate": "{Timestamp:HH:mm:ss.fff zzz} [{Level}] {Message}{NewLine}{Exception}",
                "fileSizeLimitBytes": 10485760
              }
            }
          ]
        }
      }
    ]
  }
Configure Serilog in Program & Startup class

Add UseSerilog() to CreateDefaultBuilder in Program.cs

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .UseSerilog()
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });

Load Serilog configuration from appsettings.json file in Startup.cs

public Startup(IConfiguration configuration)
{
    Configuration = configuration;

    Log.Logger = new LoggerConfiguration()
        .ReadFrom.Configuration(configuration)
        .CreateLogger();
}
Add logging in order controller

Serilog.Log class has been used to add logs with method Debug for debugging & Error in case of some exception.

[HttpPost]
[Route("Add")]
public async Task<ActionResult> Add([FromBody] Order orderdet)
{
    try
    {
        Log.Debug("Order Addition Started");
        Log.Debug("Order Addition Input", orderdet);
        string orderid = await _orderRepository.Add(orderdet);
        Log.Debug("Order Addition Output", orderid);
        return Ok(orderid);
    }
    catch (Exception ex)
    {
        Log.Error("Order Addition Failed", ex);
        throw new Exception("Order Addition Failed", innerException: ex);
    }
}
//Remaining code has been removed

Here for demonstration purposes, I have added logging feature only to the controller action ‘Add’ but as good practice, you need to add logs in a complete application with proper log levels which can assist in debugging complex issues on production. Since this is microservice, Async log writing has been configured as that reduces the overhead of logging calls by delegating work to a background thread.

Also, one more thing to note here is if you are running ASP.NET core application in docker container then you need to be careful with log file location as if you store the log file in the same container itself then there is a possibility of losing that data. In containerized environment logs should be stored on some persistent volume.

If you need further details on Logging with Serilog in ASP.NET Core then check my other article on ASP.NET Core Logging with Serilog

Add service monitoring mechanism

It is always good to keep a check on whether our service is up and running or functioning properly. Before our clients inform us about our broken service we should be able to proactively identify our broken services and take corrective actions. Healthchecks allow us to check if service is healthy i.e. up & running. Healthcheck endpoint can also be used to check its status from loadbalancer & disable a server on load balancer if our service returned failure for healthcheck on that server.

Implement microservice monitoring using ASP.NET Core Healthchecks

Healthchecks is an in-built middleware in ASP.NET Core for reporting the health of an application. Healthchecks can be exposed as one more endpoint in the application.

Install healthchecks packages

Using package manager console install the required packages for healthchecks

Install-Package Microsoft.Extensions.Diagnostics.HealthChecks
Install-Package Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore
Configure healthchecks ins Startup class

Here we have configured basic application health check & Entity Framework database context health check that confirms that the app can communicate with the database configured for an Entity Framework Core DbContext

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHealthChecks()
        .AddDbContextCheck<ApplicationDbContext>();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseHealthChecks("/checkhealth");
   }

    //Remaining code has been removed
}

You can check the service health using URL http://serverip:port/checkhealth

Create documentation for microservice

It is always good to maintain updated documentation for microservices. Other teams should be able to refer to these API specifications and consume microservice accordingly. We will be implementing Swashbuckle.AspNetCore for generating Swagger documents for order microservice.

Implement documentation using Swashbuckle Swagger

Swashbuckle is an open-source library to generate swagger documentation for ASP.NET Core Web API. This documentation can be used to explore and test API.

Install required swashbuckle swagger packages

Using package manager console install the required packages for swashbuckle

Install-Package Swashbuckle.AspNetCore
Install-Package Microsoft.OpenApi
Configure swagger ins Startup class
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSwaggerGen(options =>
        {
            options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
            {
                Title = "Microservice - Order Web API",
                Version = "v1",
                Description = "Sample microservice for order",
            });
        });
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseSwagger();
        app.UseSwaggerUI(options => options.SwaggerEndpoint("/swagger/v1/swagger.json", "PlaceInfo Services"));
    }

    //Remaining code has been removed
}

Below is the documentation generated with swagger for order microservice

ASP.NET Core Swagger

Add containerization to microservice

Containerization is used to bundle an application or feature of an application, all of it dependencies & its configuration in a container image. This image is deployed on the host operating system and bundled application works as a unit. This concept of container images allows us to deploy these across environments with little or no modifications at all. This way it is easy to scale out microservice quickly as the new containers can be easily deployed for short term purposes.

Docker will be used to add containerization to our microservice. Docker is an open-source project for creating containers that can run on docker host either on cloud or on-premises.

Implement containerization using Docker

To enable docker support in ASP.NET Core project right on the project in the solution explorer and select Add=>Docker Support from the Menu

Enable Docker in ASP.NET Core

This will enable docker and also create a Dockerfile to the project as shown below

#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

#Depending on the operating system of the host machines(s) that will build or run the containers, the image specified in the FROM statement may need to be changed.
#For more information, please see https://aka.ms/containercompat

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-nanoserver-sac2016 AS base
WORKDIR /app
EXPOSE 80

FROM mcr.microsoft.com/dotnet/core/sdk:3.1-nanoserver-sac2016 AS build
WORKDIR /src
COPY ["ProCodeGuide.Sample.Microservice/ProCodeGuide.Sample.Microservice.csproj", "ProCodeGuide.Sample.Microservice/"]
RUN dotnet restore "ProCodeGuide.Sample.Microservice/ProCodeGuide.Sample.Microservice.csproj"
COPY . .
WORKDIR "/src/ProCodeGuide.Sample.Microservice"
RUN dotnet build "ProCodeGuide.Sample.Microservice.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "ProCodeGuide.Sample.Microservice.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ProCodeGuide.Sample.Microservice.dll"]

This makes the application run within a container on the Docker host. For this to work docker desktop should be installed on the windows machine.

Benefits of Microservices

  • Gentle Learning Curve – As business functionality is broken into small services it allows developers to learn just the functionality of that service only.
  • Better Scaling – Each service is deployed independently and so can be scaled separately.
  • Lesser time to market – Application development cycle can be shorter as each service component is independent of each other.
  • Fault Isolation – Failure of one service does not affect the operation of other services.
  • No dependency on a single programming language & deployment model.
  • Parallel & Fast development of different Services is possible as separate teams are working on different services.

Drawbacks with Microservices

  • Small independent services require coordination among each other which may be not simple as compared to Monolith Application
  • Managing distributed transactions across multiple services can be complex.
  • There are Multiple Services/Components to Monitor.
  • Testing can be little time consuming as each independent service needs to be tested before integrated testing.
  • Whole deployment architecture for large applications becomes very Complex to Manage.

Microservices Architecture is about better handling a large & complex system but to achieve that it exposes its own set of complexities & implementation Challenges. Despite the disadvantages or problems, the benefits of adopting Microservices are driving factors for many companies to implement Microservices.

Best Practices

  • Microservice should implement only one single functionality that should be able to deliver value.
  • Each Microservice should have their own datastore and this should not be shared across services.
  • Always maintain updated documentation for Microservices.
  • Use containers for the deployment of services.
  • DevOps & CI/CD practices

Summary

We covered what is microservice architecture and how to get started with microservices with ASP.NET Core 3.1. I have not covered one more important feature of microservice i.e automated testing. Automated unit testing is a very vast topic in itself and I will do a separate article on it.

Quick recap on microservices characteristics

  • Microservices is a type of Architecture in which application is created as multiple small independent serviceable components
  • Microservice should contain only single business functionality and should be small enough to stay focussed and big enough to deliver value.
  • Each Microservice should have its own data store.
  • Microservices can communicate with each other using lightweight protocol i.e. over HTTP or Advanced Message Queue Protocol (AMQP)
  • Microservice can be deployed independently on a separate VM and can be scaled independently.
  • There are benefits in implementing API Gateway for large & Complex Microservices based Applications.

Source code download

Read Configuration values in ASP.NET Core

Read Configuration values in ASP.NET Core

In this article, we will learn about how to read configuration values in ASP.NET Core. We normally have many settings in our application which are different for each environment like third party service URLs, database connection string, email settings, log parameters, etc. We normally set these values in some configuration files so that we are able to change these values without the need to recompile the application.

ASP.NET Core uses appsettings.json file for such settings and this file is read when the application starts. Also, we can configure to read code to read these settings even after startup, i.e. we can change values even when the application is running.

Ins ASP.NET Core there is more than one source to read configuration values from like Environment variables, settings file, command-line arguments, directory files, in-memory objects, etc.

This article will cover how to read configuration values from appsettings.json file using options pattern.

Options Pattern

Option pattern uses classes to bind a group of related configuration settings to a strong typed access i.e. class will have properties that correspond to some setting in the configuration file. These classes are registered in the dependency injection containers to be injected into services, controllers, etc using constructor dependency injection.

Options pattern helps in keeping settings of the different sections separate i.e. setting for logging can be separate from setting for database connectivity.

For details on dependency injection, you can read my other article on Dependency Injection in ASP.NET Core 3.1

Type of options interfaces

There are 3 type of interfaces supported by options pattern

IOptions<TOptions> – Reads configuration data once when the application starts and any changes in configuration will require the application to be restarted. It is registered in the dependency injection container with a singleton lifetime.

IOptionsSnapshot<TOptions> – Reads configuration on every request. Any changes in configuration while the application is running will be available for new requests without the need to restart the application. It is registered in the dependency injection container as a scoped lifetime. Being a scoped service it receives current options values at the time the object is constructed.

IOptionsMonitor<TOptions> – It is a combination of IOptions & IOptionsSnapshot. Supports re-binding configuration but it is only when configuration changes and not on every request like IOptionsSnapshot. It is registered in the dependency injection container as a singleton lifetime (same as IOptions).

Once options pattern is configured then it can be injected via dependency injection using one of the available interfaces.

Implement Options Pattern

For a demonstration of options pattern to read configuration values in ASP.NET Core, we will be creating a Web API project and read configuration file values using options pattern

Create ASP.NET Core Project

Create ASP.NET Core Web API Project

Add Parameters to appsettings.json

Lets add some sample settings in appsettings.json file to be read using IOptions Pattern

{
  "ApplicationParameters": {
    "SQLServerConnection": "SQL Server Connection String",
    "EmailServer": "127.0.0.1",
    "ServiceURL": "https://procodeguide.com/getpost",
    "MaxLimitUsers": 3000
  }
}

We have added setting section named ApplicationParameters & have added four parameters under it i.e. SQLServerConnection, EmailServer, ServiceURL & MaxLimitUsers

Add properties class

We will add properties class which corresponds to the settings specified in appsettings.json ApplicationParameters section.

public class ApplicationParameters
{
    public string SQLServerConnection { get; set; }
    public string EmailServer { get; set; }
    public string ServiceURL { get; set; }
    public long MaxLimitUsers { get; set; }
}

Bind configuration to your class in Startup class

Let’s add service in startup class to read configuration values into an instance of properties class ApplicationParameters.

public class Startup
{
    public Startup(IWebHostEnvironment env)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);

        Configuration = builder.Build();
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();

        services.Configure<ApplicationParameters>(
            this.Configuration.GetSection("ApplicationParameters"));
    }
    //Remaining code has been removed
}

The default order in which values are read from JSON file is

  1. appsettings.json
  2. appsettings.{environment}.json

i.e. if same key exists in both appsettings.json & appsettings.{environment}.json then on runtime it should display the value from appsettings.{environment}.json

Add a controller to read parameters using IOptions

The sample controller has been added to inject configuration in constructor injection using IOptions & IOptionsSnapshot interfaces. It has one action index which return values of configuration parameters read using IOptions & IoptionsSnapshot

[Route("api/[controller]")]
[ApiController]
public class AppParamtersController : ControllerBase
{
    private ApplicationParameters applicationParameters;
    private ApplicationParameters applicationParametersSnapshot;
    public AppParamtersController(IOptions<ApplicationParameters> options, IOptionsSnapshot<ApplicationParameters> optionsSnap)
    {
        applicationParameters = options.Value;
        applicationParametersSnapshot = optionsSnap.Value;
    }

    [HttpGet]
    public IActionResult Index()
    {
        StringBuilder response = new StringBuilder();
        response.AppendLine("IOptions ==>");
        response.AppendLine(JsonConvert.SerializeObject(applicationParameters));
        response.AppendLine("");
        response.AppendLine("IOptionsSnapshot ==>");
        response.AppendLine(JsonConvert.SerializeObject(applicationParametersSnapshot));
        return Content(response.ToString());
    }
}

Run & Test the code

When we run the code and navigate to path api/AppParamters/Index then configuration values are displayed for IOption & IOptionsSnapshot. Both values match as it has been read when the app started.

IOptions Test Results 1

Now without stopping application modify values in appsettings.json to new values & save changes

Modified appsetting.json

Now refresh browser (without restarting application) and check values for both IOptions & IOptionsSnapshot. You will see that only IOptionsSnapshot contains the latest modified values as it is designed to read values before each request

IOptions Test Results 2

IOptionsMonitor also works in a way similar to IOptionsSnapshot only difference being in the IOptionMonitor service lifetime is Singleton instead of Scoped as in IOptionsSnapshot.

Guidelines for storing configuration values

  • All sensitive data like passwords, personal information should never be stored in plain text in configuration i.e. if at all it is being stored in configuration then it should be encrypted.
  • As far as possible production secrets should be different from development & staging secrets.
  • Use different naming conventions for configuration files on different environments like appsettings.development.json and appsettings.production.json.

Summary

We saw how to read configuration values in ASP.NET Core using IOtions Pattern. We can inject IOption into service, controller, etc using constructor injection.

There are 3 types of IOptions interface IOptions, IOptionsSnapshot & IOptionsMonitor. IOptionsSnapshot & IOptionsMonitor allows you to read modified parameters even while the application is running.

References: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-3.1

Download Source Code

Here is the source code for demonstration of Options Pattern in ASP.NET Core

ASP.NET Core Identity Claims based Authorization

ASP.NET Core Identity Claims based Authorization

This is the second post in the series – ASP.NET Core Security. In my last post, I covered how to get started with ASP.NET Core Identity. In this article, we will learn about claims and how to implement ASP.NET Core Identity claims-based authorization.

  1. ASP.NET Core Identity – Getting Started
  2. ASP.NET Core Identity Claims based Authorization

Before you start reading this I would suggest that first, you read my previous article about ASP.NET Core Identity – Getting Started

What are the claims?

The identity of the user consists of a set of properties like Unique Id, Name, Email Id, Email Verified, etc and these properties are called claims. A claim set is a list of properties that belongs to the user. Claims contain information about the user that can be used to build a flexible authorization model. These claims get assigned to the user when a new user is created using the register link. A claim is typically like a key-value pair.

ASP.NET Core Identity by default comes with claims for a user and we can implement our own custom claims as well. There can be multiple claims of the same type.

Authorization using a claim can be implemented based on the value of the claim for that user. Access to protected resources can be determined by comparing the claims needed to access the resource with the claims available for the user.

Implement a Custom Claim

In our previous demo, we created a custom user profile property Full Name which we will add to our list of custom claims i.e. claim list will contain a custom claim ‘FullName’

We will work on the source code which we created as part of the previous article where we added Identity to an existing Sample Blog Application. Here is the link to base source code which will be used for this demonstration.

Display all claims

First, let’s add a link to display all the claims from the identity of the logged-in user. We will add a new link ‘Claims’ besides post link & corresponding action in new user controller & cshtml to display all the available claims.

Add user controller

Add user controller to implement action for list of claims for the user

[Authorize]
public class UserController : Controller
{
    private readonly ILogger<UserController> _logger;

    public UserController(ILogger<UserController> logger)
    {
        _logger = logger;
    }

    public IActionResult Claims()
    {
        return View();
    }
}

Add view to display claims

This cshtml will iterate claims in identity user object to display all the available claims.

@model List<Post>
@{
    ViewData["Title"] = "View User Claims";
}
<h1>@ViewData["Title"]</h1>

<br />

@if (User.Identity.IsAuthenticated)
{
    <table class="table table-responsive-sm">
        @foreach (var claim in User.Claims)
        {
        <tr>
            <td>@claim.Type</td>
            <td>@claim.Value</td>
            <td>@claim.Issuer</td>
        </tr>
        }
    </table>
}

Add link for claims

We will add a menu or link besides posts link to display all claims. This will call Claims action in the User controller to display claims.cshtml which contains logic to display all claims of the logged-in user. Add the below code in application _Layout.cshtml page

<li class="nav-item">
    <a class="nav-link text-dark" asp-area="" asp-controller="User" asp-action="Claims">Claims</a>
</li>

Running the application & clicking on claims (post login) will display list of claims as shown below

List of Identity claims

We can see the list of default identity claims and now lets try to understand how to add a custom claim to identity.

Add custom claims

As part of article Identity – getting started we created a custom user profile property Full Name & now let’s make that Full Name as part of claims by adding a custom claim.

Add application level user claims principal factory

We will have to add application specific user claim principal factory that derives from Identities generic user claim principal factory which takes type of application specific user class i.e. SampleAppUser. In this we will have to override function GenerateClaims and add our application specific claim ‘FullName’ to it.

public class ApplicationUserClaimsPrincipalFactory : UserClaimsPrincipalFactory<SampleAppUser>
{
    public ApplicationUserClaimsPrincipalFactory(
        UserManager<SampleAppUser> userManager,
        IOptions<IdentityOptions> options
        ) : base(userManager, options)
    {

    }

    protected override async Task<ClaimsIdentity> GenerateClaimsAsync(SampleAppUser user)
    {
        var identity = await base.GenerateClaimsAsync(user);

        identity.AddClaim(new Claim("FullName",
            user.FullName
            ));

        return identity;
    }
}

Register claims factory in startup class

You will have have to add an application-specific user claims principal factory to dependency injection container.

services.AddScoped<IUserClaimsPrincipalFactory<SampleAppUser>, ApplicationUserClaimsPrincipalFactory>();

Now when you run the application and navigate to claim link and post login you should be able to see FullName in claims list.

Identity - Custom List of claims

Claim based authorization

ASP.NET Core Identity claims can be used to implement authorization i.e. based on user claim value we can decide whether access to a specific resource will be able or not to that user. e.g. in our case, we can configure authorization based on email id i.e. only user with email id [email protected] should be able to view the post details page.

Claims based Authorization

Implement claim based authorization

In our Sample Blog App, we will implement authorization based on email id i.e. if user email id is [email protected] then access to action Details in post controller will be allowed else it will be denied.

Register claim policy in startup class

Add the policy for user email id in the startup class. Here we have registered an authorization policy named EmailID using claim key http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name (email id) equal to [email protected] Add this code does not enable any authorization as it is registering a policy. We will have to apply this policy either to a controller or action to enable it.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddRazorPages();

    services.AddAuthorization(options =>
    {
        options.AddPolicy("EmailID", policy =>
        policy.RequireClaim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "[email protected]"
        ));
    });
}

Apply claim policy to controller action

Claim policy EmailID which we created above is applied to Action Details in the posts controller. This means that posts/details is allowed only for the user whose email id is [email protected]de.com

[Authorize(Policy = "EmailID")]
public IActionResult Details(string id)
{
    Post post = DataHelper.GetAllPosts().Find(p => p.Id.Equals(id));
    return View(post);
}

You can specify multiple policies as well for controller or actions. In case of multiple policies all the policies need to confirm before granting access.

Now run the run and check action post/details with different users. We will see that action post/details is allowed to [email protected] and access is denied to user [email protected]

Identity Claim Authorization Allowed
Identity Claim Authorization Denied

Summary

In this article, we learned about Identity claims, configure custom claims & claim based authorization. Though claims can be used for authorization roles are a more standard way of implementing authorization as the same role can be specified for multiple users.

In our next article in this series for ASP.NET Core Identity, we will learn about roles and how to how to implement role-based authorization.

References – https://docs.microsoft.com/en-us/aspnet/core/security/authorization/claims?view=aspnetcore-3.1

Download Source Code

ASP.NET Core Identity – Getting Started

ASP.NET Core Identity – Getting Started

This article is first in the series – ASP.NET Core Security. Here we will see what is ASP.NET Core Identity and how to get started. To understand things better we will explicitly add identify to an existing ASP.NET Core web application.

  1. ASP.NET Core Identity – Getting Started
  2. ASP.NET Core Identity Claims based Authorization

What is Identity?

ASP.NET Core Identity

ASP.NET Core Identity is authentication & authorization API and is used to implement security-related features in your ASP.NET Core Applications. ASP.NET Core Identity is a built-in membership system or framework which allows us to implement

  • Manage User Accounts
  • Authentication & Authorization
  • Email confirmation
  • Password Recovery
  • Two factor Authentication
  • External login providers like Microsoft, Google, Facebook etc

Identity source code is available on GitHub. SQL Server is the generally preferred database to store identity-related data like user name, password & user-related data. Alternatively, Azure table storage is also supported.

Implement ASP.NET Core Identity

Sample Application Details

Below is the GitHub link to sample ASP.NET Core Web Application which has been created to implement identity

When you download, build & run above visual studio project you can see below mentioned functionality available in application & also anyone can browse the application with no need to provide user credentials.

  • View list of posts
  • View details of each post
ASP.NET Core App 1
ASP.NET Core App 2

Add Identity to Sample Application

To add identity right click on project in solution explorer and select Add=>New Scaffolded Items from the context menu.

Add New Scaffolded Items

On selecting Add New Scaffolded Item below dialog appears where you need to select Identity under Installed and click on Add Button.

Add Microsoft Identity

After adding Identity you need to specify few things on the screen below

  1. Select the layout page from the project files
  2. Select Login & Logout file to be overridden
  3. Select Register file
  4. Add new data context class using + sign button
  5. Add new User class using + sign button
Select Scaffolded Items

ASP.NET Core Identity has provided with the ready library but you can override the standard implementation by scaffolding the items which you need to override.

Also above actions installs following NuGet packages implicitly.

Microsoft.VisualStudio.Web.CodeGeneration.Design
Microsoft.EntityFrameworkCore.Sqlsever
Microsoft.EntityFrameworkCore.Tools
Microsoft.AspNetCore.Identity.UI
Microsoft.AspNetCore.Identity.EntityFrameworkCore

ASP.NET Core Identity makes use of Entity Framework to manage user profiles. If you need further details on how an entity framework works then check my other article on Entity Framework Core in ASP.NET Core 3.1

Generated Code Explained

Areas/Identity/IdentityHostingStartup.cs file this works like a starting class for the project and is called each time the project starts. This class holds the configuration of identity-related services & Entity framework configuration for identity.

public class IdentityHostingStartup : IHostingStartup
{
    public void Configure(IWebHostBuilder builder)
    {
        builder.ConfigureServices((context, services) => {
            services.AddDbContext<SampleAppContext>(options =>
                options.UseSqlServer(
                    context.Configuration.GetConnectionString("SampleAppContextConnection")));

            services.AddDefaultIdentity<SampleAppUser>(options => options.SignIn.RequireConfirmedAccount = true)
                .AddEntityFrameworkStores<SampleAppContext>();
        });
    }
}

Areas/Identity/SampleAppUser.cs file which derives from Identity User. This class allows you to add your own fields for user profiles. Identity framework comes with default properties for user profile you can add your application-specific properties here. After adding properties you will have to modify Register Screen to capture new properties for the user.

public class SampleAppUser : IdentityUser
{
    public string FullName { get; set; }
}

Area/Identity/SampleAppContext.cs file which is the entity framework context class used by Identity.

public class SampleAppContext : IdentityDbContext<SampleAppUser>
{
    public SampleAppContext(DbContextOptions<SampleAppContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        // Customize the ASP.NET Identity model and override the defaults if needed.
        // For example, you can rename the ASP.NET Identity table names and more.
        // Add your customizations after calling base.OnModelCreating(builder);
    }
}

Also SQL Server connection is added to Appsettings.json file.

  "ConnectionStrings": {
    "SampleAppContextConnection": "Server=(localdb)\mssqllocaldb;Database=ProCodeGuide.Sample.Blog;Trusted_Connection=True;MultipleActiveResultSets=true"
  }

Add Migrations

To automate the migrations & create a database for Identity we need to run the following commands in the package manager console.

add-migration InitialMigration
update-database
Identity Database

You can see that Application specific property ‘FullName’ for user which we configured is also part of AspNetUsers table.

You can also explore the cshtml files for Login, Logout & Register which are available in folder Areas/Identity/Pages/Account

Execute Application

After building and running application still you don’t see any changes no login screen & no security added to action view all post & post details. Still you need to do following changes for identity related links to appear and to secure your actions.

For Identity-related links Login, Logout (after login) & Register to appear to add following code to application layout file i.e Views/Shared/_Layout.cshtml. Add it in the header tag.

<div class="col-md-2">
    <partial name="_LoginPartial.cshtml" />
</div>

To secure your actions add Authorize attribute (part of Microsoft.AspNetCore.Authorization namespace) either at a controller level for all actions or at Action level for specific actions.

[Authorize]
public class PostsController : Controller
{
    //Remaining code has been removed
}

Make changes to Startup class to add Authentication, Razor Pages & Endpoints for Razor Pages

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllersWithViews();
        services.AddRazorPages();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthentication();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
            endpoints.MapRazorPages();
        });
    }
}

Now when you run application and try to navigate to post pages it will redirect you to login page if you are not logged in already. Register link is also available to create new users.

Identity Login Screen
Identity Logged in User Screen

Also, note that only users with confirmed email are allowed to log in so after registering a user link to confirm the email is available which should be clicked to confirm email. Ideally, a mail should be sent out to the user to confirm email but by default, email support is not enabled so the link has been provided directly for an email confirmation.

Save custom user properties to database

ASP.NET Core Identity allows us to add custom fields to the user profile. Previously we added new user profile property Full Name but we did not add any code to save it to the database. Here we will make changes to save the same to the database as part of registering a new user.

First, we will have to make changes in Areas/Identity/Pages/Account/Register.cshtml to add an input box for user to enter it as part of the registration process. We will below HTML in Register.cshtml before Email Input

<div class="form-group">
    <label asp-for="Input.FullName"></label>
    <input asp-for="Input.FullName" class="form-control" />
    <span asp-validation-for="Input.FullName" class="text-danger"></span>
</div>

Then we need to add FullName property in InputModel class in Register.cshtml.cs

public class InputModel
{
    [Required]
    [StringLength(25, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 10)]
    [Display(Name = "Full Name")]
    public string FullName { get; set; }

    //Remaining Code has been removed
}

Finally we need set the value for FullName property in OnPostAsync method in Register.cshtml.cs file. Modify code as show below to set FullName from InputModel.

var user = new SampleAppUser { UserName = Input.Email, Email = Input.Email, FullName = Input.FullName };

After running code you should be able to see Full Name text box in Register form & on Register click the value entered in text box is saved to database in AspNetUsers table in column FullName.

Identity Register With Custom Field

Summary

In this first article for security series, we saw how to add Identity to additional ASP.NET Core application. We added Identity to add functionalities like Login, Logout & Register. We also saw how to secure our resources so that unauthorized access to the application is blocked.

Download Source Code

References – https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-3.1&tabs=visual-studio

Series: ASP.NET Core Security

Series: ASP.NET Core Security

ASP.NET Core Security

These days web applications are vulnerable to various attacks so there is a need to secure your application so that unauthorized users are not able to access it. ASP.NET Core Security provides a various mechanism to secure you applications i.e. authentication, authorization, data protection, Secure Socket Layer (HTTPS), app secrets, etc. This series will try to cover security techniques in detail to make you aware of various ways available to secure your applications.

Here is the list of posts in this series.

  1. ASP.NET Core Identity – Getting Started
  2. ASP.NET Core Identity Claims based Authorization

Also I will be listing new posts here when they are published.

ASP.NET Core Security Techniques

There are many in-built as well as third party tools & libraries available which can be used in ASP.NET Core applications to implement security-related features like authentication, authorization, etc.

Authentication

There are applications that have user-specific data i.e. user’s stocks, bank balance, mails, etc and this data is confidential so it should not be viewable by other users. Authentication helps you in identifying the user’s identity i.e. which user is accessing your application. The user has to prove his/her identity by providing the credential i.e. user id/password and in some cases, there might additional security features like multi-factor authentication.

Authorization

Once the user is identified and able to access your application then the next step is to determine what actions the user is able to perform. A superuser might be able to create data and other normal users might be able to view data added by the superuser but not delete allowed to delete records. Now authentication & authorization are two different things but they go hand in hand.

Data Security

Data security is about securing confidential application data from unauthorized access. Data should be secure between server round trips Also, access to the database should be secured.

Other Vulnerabilities

There are lots of other attacks against which ASP.NET Core security provides ways to secure our applications. To name a few of the other vulnerabilities like SQL Injection, Cross-Site Request Forgery, Server Version Disclosure, etc.

References: https://docs.microsoft.com/en-us/aspnet/core/security/?view=aspnetcore-3.1