Common features in ASP.NET Core 2.1 WebApi: Logging

 

Introduction

Often overlooked, logging errors and other information in case of bug in the program is an essential feature in a Web API.

In this article we will see how to implement logging in a rolling file in an ASP.NET Core WebAPI with Serilog.

Serilog, Like many other libraries for .NET, Serilog provides diagnostic logging to files, the console, and elsewhere. It is easy to set up, has a clean API, and is portable between recent .NET platforms.

Unlike other logging libraries, Serilog is built with powerful structured event data in mind.

Installation

Download these three packages:

PM> Install-Package Serilog.AspNetCore -Version 2.1.1
PM> Install-Package Serilog.Settings.Configuration -Version 3.0.1
PM> Install-Package Serilog.Sinks.RollingFile -Version 3.3.0

The Serilog.AspNetCore enables Serilog for ASP.NET Core, you have to add Serilog.Settings.Configuration if you need to enable configuration from appsettings.json.

Then Serilog.Sinks.RollingFile enables you to log into rolling files your logs.

Configuring your appsettings.json and Startup.cs

Step 1: Configuring appsettings.json

We chose rolling files to put our logs.

There are three big parts in the configuration

  1. Logging minimum level: In this example the minimum log level is “Information”
  2. Serilog itself: “WriteTo”, as you can see it’s an array, it means you can setup many Sinks (destination of the log), in our case “RollingFile”
  3. Properties: “Application” allows you to name your log if you want to know what log belongs to what application.

Example:

{
 "Serilog": {
    "MinimumLevel": "Information",
    "WriteTo": [
    {
       "Name": "RollingFile",
       "Args": {
          "pathFormat": "C:\\Temp\\log-{Date}.txt",
          "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] {Message}{NewLine}{Exception}"
       }
    }
   ],
   "Properties": {
      "Application": "Common feature in WebApi demo"
   }
 }
}

Once done we have to go to Startup.cs assigning this configuration to Serilog.

NB: Using settings in appsettings.json is not mandatory, you can build your configuraion by code, you can find examples here.

Step 2: Configuring Startup.cs

As we said before we need to read the configuration from appsettings.json in our example:

public Startup(IConfiguration configuration)
{
   // Init Serilog configuration
   Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(configuration).CreateLogger();
   Configuration = configuration;
}

Then we need to add Serilog to the LoggerFactory in the Configure method:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
   // logging
   loggerFactory.AddSerilog();
   app.UseMvc();
 }

In fact Serilog override the default Microsoft Logger provided by the assembly: Microsoft.Extensions.Logging

Logging in action

Let’s create an action that catch an exception and logs it:

[Route("api/[controller]")]
[ApiController]
public class DemoExceptionController : ControllerBase
{
   private readonly ILogger<DemoExceptionController> _logger;

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

   [HttpGet]
   public IEnumerable<string> Get()
   {
      try
      {
         _logger.LogInformation("Could break here :(");
         throw new Exception("bohhhh very bad error");
      }
      catch (Exception e)
      {
         _logger.LogError(e, "It broke :(");
      }
      return new string[] { "value1", "value2" };
   }
}

Let’s see now what happen when this code is executed:

We can see the error log and more (Information) because we set it as minimum level.

Conclusion

This was an example of implementing logging in an ASP.NET Core WebAPI 🙂

Serilog provides many differents ways to log (Sinks), you can find the complete list here.

Serilog is very powerfull, you can also enrich your log easily with Enrichers, a great feature of Serilog!

Awesome right ? 😉

Common features in ASP.NET Core 2.1 WebApi: Error handling

 

Introduction

The exception handling features help us deal with the unforeseen errors which could appear in our code.

In ASP.NET Core we can handle errors in different manners :

  • Error Handling with Try-Catch Block
  • Error Handling with an Exception Filter
  • Handling Errors Globally with the Built-In Middleware (UseExceptionHandler)
  • Handling Errors Globally with the Custom Middleware

In this article we will see how to handle errors with my favourite method: Custom Middleware.

As part of an ASP.NET Core 2.1 application, middleware makes up the processing chain (pipeline) of HTTP requests. They interact on the response and the HTTP request.

Each middleware can call the following middleware. They can perform operations before AND after their successor.

It is important to understand that the HTTP response is built only when the request is passed in this middleware suite.

How to create a middleware

Creating a middleware is really very simple. The IApplicationBuilder interface provides three methods for doing this.

  • Run
  • Map
  • Use

I will just describe in this article the Use method because it allows to create a class we will “use” as a middleware, instead of Run and Map that require an inline declaration.

First, the class must contain an Invoke method that takes an HttpContext parameter and returns a Task. In addition, our class must have a constructor with at least one parameter to use the next middleware (the next method). This one is of type RequestDelegate. However, the constructor may have other parameters. These parameters will be passed to him when adding the middleware in the HTTP pipeline. This addition is done using the UseMiddleware method.

Example with a custom middleware that handle errors:

public class CustomExceptionMiddleware
{
   private readonly RequestDelegate _next;

   public CustomExceptionMiddleware(RequestDelegate next)
   {
      _next = next;
   }

   public async Task Invoke(HttpContext context)
   {
      try
      {
         await _next.Invoke(context);
      }
      catch (Exception ex)
      {
         await HandleExceptionAsync(context, ex);
      }
   }

   private async Task HandleExceptionAsync(HttpContext context, Exception exception)
   {
      var response = context.Response;
      var customException = exception as BaseCustomException;
      var statusCode = (int)HttpStatusCode.InternalServerError;
      var message = "Unexpected error";
      var description = "Unexpected error";

      if (null != customException)
      {
         message = customException.Message;
         description = customException.Description;
         statusCode = customException.Code;
      }

      response.ContentType = "application/json";
      response.StatusCode = statusCode;
      await response.WriteAsync(JsonConvert.SerializeObject(new CustomErrorResponse
      {
         Message = message,
         Description = description
      }));
   }
}

This middleware handle all errors and control the answer to the client: it sends a custom error message.

There are 2 cases :

First case: it’s a custom error that we know what to do with and what to send to the client BaseCustomException

Second case: it’s an unmanaged error that we don’t know what to do with, we will send a generic error message to the client.

Here is the complete implementation of custom errors, DTO and exception raising:

public class BaseCustomException : Exception
{
   private int _code;
   private string _description;

   public int Code
   {
      get => _code;
   }
   public string Description
   {
      get => _description;
   }

   public BaseCustomException(string message, string description, int code) : base(message)
   {
      _code = code;
      _description = description;
   }
}
public class CustomErrorResponse
{
   public string Message { get; set; }
   public string Description { get; set; }
}
public class NotFoundCustomException : BaseCustomException
{
   public NotFoundCustomException(string message, string description) : base(message, description, (int)HttpStatusCode.NotFound)
   {
   }
}
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
   [HttpGet]
   public ActionResult<IEnumerable<string>> Get()
   {
      throw new Exception();
      return new string[] { "value1", "value2" };
   }

   [HttpGet("{id}")]
   public ActionResult<string> Get(int id)
   {
      throw new NotFoundCustomException("No data found", $"Please check your parameters id: {id}");
      return "value";
   }
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
   if (env.IsDevelopment())
   {
      app.UseDeveloperExceptionPage();
   }
   app.UseMiddleware<CustomExceptionMiddleware>();
   app.UseMvc();
}

Demo

Case 1:

Unhandled error scenario

Case 2:

Custom error scenario

Conclusion

This was an example of implementing error handling in an ASP.NET Core WebAPI 🙂

Nice isn’t it ? 😉

Common features in ASP.NET Core 2.1 WebApi: Validation

 

Introduction

Validating user input is a common scenario in a Web-based application. For production applications, developers often end up spending a lot more time and code on this task than we would like. In building the ASP.NET Core Web API with FluentValidation, it was important to try and make the task of validating input a lot easer than it has been in the past.

FluentValidation is  popular .NET library for building strongly-typed validation rules.

Configuring the project

Step 1: Download FluentValidation.AspNetCore Nuget package

PM> Install-Package FluentValidation.AspNetCore -Version 8.0.100

 

Step 2: Add FluentValidation in Startup.cs

public void ConfigureServices(IServiceCollection services) 
{ 
   // mvc + validating
   services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1).AddFluentValidation();
}

Creating a validator

FluentValidation ships with several built-in validators. In ths next example will see how to use two of them:

  • NotNull
  • NotEmpty

We will see how to build a custom validator as well with the RuleBuilder Must.

Step 1: Create a model you want to validate

Example of User model:

public class User
{
   public string Gender { get; set; }
   public string FirstName { get; set; }
   public string LastName { get; set; }
   public string SIN { get; set; }
}

Step 2: Create your validator

Note that your validator must inherit from the abstract class named AbstractValidator

public class UserValidator : AbstractValidator<User>
{
   public UserValidator()
   {
      // Rules here
   }
}

Step 3: Create your rules

In this example we will ensure that FirstName, LastName and SIN are not null and not empty, we will also ensure that SIN number is a valid SIN with this algorithm:

public static class Utilities
{
   public static bool IsValidSIN(int sin)
   {
      if (sin < 0 || sin > 999999998) return false;

      int checksum = 0;
      for (int i = 4; i != 0; i--)
      {
         checksum += sin % 10;
         sin /= 10;

         int addend = 2 * (sin % 10); if (addend >= 10) addend -= 9;
         checksum += addend;
         sin /= 10;
      }
         return (checksum + sin) % 10 == 0;
   }
}

Implementation of rules:

public class UserValidator : AbstractValidator<User>
{
   public UserValidator()
   {
      RuleFor(x => x.FirstName)
      .NotNull()
      .NotEmpty()
      .WithMessage("FirstName is mandatory.");

      RuleFor(x => x.LastName)
      .NotNull()
      .NotEmpty()
      .WithMessage("LastName is mandatory.");

      RuleFor(x => x.SIN)
      .NotNull()
      .NotEmpty()
      .WithMessage("SIN is mandatory.")
      .Must((o, list, context) =>
      {
         if (null != o.SIN)
         {
            context.MessageFormatter.AppendArgument("SIN", o.SIN);
            return Utilities.IsValidSIN(int.Parse(o.SIN));
         }
         return true;
      })
     .WithMessage("SIN ({SIN}) is not valid.");
   } 
}

Step 4: Declare your rules as a singleton service in Startup.cs

public void ConfigureServices(IServiceCollection services) 
{ 
   // Validators
   services.AddSingleton<IValidator<User>, UserValidator>();
   // mvc + validating
   services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1).AddFluentValidation();
}

Step 5: Manage your validation errors in Startup.cs

ASP.NET Core 2.1 allows you to override the default behavior of ModelState management (ApiBehaviorOptions), you have now an alternative to MVC Attributes:

public void ConfigureServices(IServiceCollection services) 
{ 
   // Validators
   services.AddSingleton<IValidator<User>, UserValidator>();
   // mvc + validating
   services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1).AddFluentValidation();

    // override modelstate
    services.Configure<ApiBehaviorOptions>(options =>
    {
       options.InvalidModelStateResponseFactory = (context) =>
       {
          var errors = context.ModelState.Values.SelectMany(x => x.Errors.Select(p => p.ErrorMessage)).ToList();
          var result = new
          {
             Code = "00009",
             Message = "Validation errors",
             Errors = errors
          };
          return new BadRequestObjectResult(result);
       };
    });
}

When a validation fails, this piece of code is performed (ModelState is false)

Is this example, I manage what I do with errors and how I display errors to the client, simply an object that contains an error code, a message, and a list of validation errors encountered.

Here we are! Let’s see how it works now

Using a validator

It’s really easy to use a validator after it’s creation.

You just need to create your action that takes in parameter your model to validate.

Because the validation service added in your configuration, FluentValidation will automatically detect your model when you use it into an action and fire the validator !

Step 1: Create an action that uses your model to validate

[Route("api/[controller]")]
[ApiController]
public class DemoValidationController : ControllerBase
{
   [HttpPost]
   public IActionResult Post(User user)
   {
      return NoContent();
   }
}

Step 2: Test your action with POSTMAN

Conclusion

This was an example of implementing validation in an ASP.NET Core WebAPI 🙂

The complete source code can be found here.