Logging and Exception handling, Versioning in ASP.NET WEB API – DEVELOPPARADISE
17/07/2018

Logging and Exception handling, Versioning in ASP.NET WEB API

Introduction

In this article, we are going to learn how to log each request and response of an API such that it helps to maintain logs, next we are going to handle all API exception such that if an error occurs, we can store errors and fix it as soon as possible, and last part is versioning of the API.

  1. Exception handling
  2. Logging
  3. Versioning

Because all these parts are key when you are developing a production API.

Logging and Exception handling, Versioning in ASP.NET WEB API

Icons made by Freepik from www.flaticon.com is licensed by CC 3.0 BY

Logging and Exception handling, Versioning in ASP.NET WEB API

1.     Exception handling

Let’s start, we create a simple Web API application “WebDemoAPI”.

Logging and Exception handling, Versioning in ASP.NET WEB API

After creating a simple Web API solution, you will get a default Home controller and the Values API Controller. Let’s first run application and call get request.

Note: – You can use any Rest Client for sending a request for this demo I am going to use POSTMAN rest client.

URL:-  http://localhost:50664/api/values

Sending Get Request

Logging and Exception handling, Versioning in ASP.NET WEB API

After sending a request to API we got a response.

Now let’s make a change in Get Method, here I am going to throw an exception.

public class ValuesController : ApiController
{
    // GET api/values
    public IEnumerable<string> Get()
    {
        throw new NotImplementedException("");
        //return new string[] { "value1", "value2" };
    }
   }

Now if we send request to values API get request then it will throw error in response.

Response before handling the exception

Logging and Exception handling, Versioning in ASP.NET WEB API

Now we got the error let’s see how to handle this error globally.

Handling API Exception using ExceptionHandler class

Logging and Exception handling, Versioning in ASP.NET WEB API

 For handling exceptions, we are going to create a class “GlobalExceptionHandler” which will inherit from “ExceptionHandler” abstract class inside this we are going to implement Handle method. Before that we are going to create “CustomHandler” folder in this folder we are going to add “GlobalExceptionHandler” class.

Logging and Exception handling, Versioning in ASP.NET WEB API

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using System.Web.Http.ExceptionHandling;

namespace WebDemoAPI.CustomHandler
{
    public class GlobalExceptionHandler : ExceptionHandler
    {
        public override void Handle(ExceptionHandlerContext context)
        {
            var result = new HttpResponseMessage(HttpStatusCode.InternalServerError)
            {
                Content = new StringContent("Internal Server Error Occurred"),
                ReasonPhrase = "Exception"
            };

            context.Result = new ErrorMessageResult(context.Request, result);
        }

        public class ErrorMessageResult : IHttpActionResult
        {
            private HttpRequestMessage _request;
            private readonly HttpResponseMessage _httpResponseMessage;

            public ErrorMessageResult(HttpRequestMessage request, HttpResponseMessage httpResponseMessage)
            {
                _request = request;
                _httpResponseMessage = httpResponseMessage;
            }

            public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
            {
                return Task.FromResult(_httpResponseMessage);
            }
        }
    }
}

Now we have implemented Handle method from ExceptionHandler class.

Before doing it first we go to create HttpResponseMessage for that we are going to add a class “ErrorMessageResult” which will inherit from “IHttpActionResult” interface. This class will have a Parameterized Constructor which takes 2 parameters 1. HttpRequestMessage ,2. HttpResponseMessage the HttpResponseMessage which we took parameters will be used by ExecuteAsync to create HttpResponseMessage.

Then this HttpResponseMessage we are going to assign it to “context.Result”

After handling the exception, next we need to register this handler.

Registering Exception handler

We are going to Register “GlobalExceptionHandler” in WebApiConfig class, such that any web API exception can be handled globally.

//Registering GlobalExceptionHandler
config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using System.Web.Http.ExceptionHandling;
using WebDemoAPI.CustomHandler;

namespace WebDemoAPI
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services
            // Web API routes
            config.MapHttpAttributeRoutes();
            //Registering GlobalExceptionHandler
            config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}

Now let’s run this application and check do we handle exception now.

Snapshot of exception thrown

Logging and Exception handling, Versioning in ASP.NET WEB API

After throwing an exception now we display proper error message not error stack trace to consumers.

Response after handling the exception

Logging and Exception handling, Versioning in ASP.NET WEB API

Now we have handled the exception, but we have not logged the exception.

Exception Logging

Logging and Exception handling, Versioning in ASP.NET WEB API

In this part, we are going store exception into the database, for doing that let’s first have a look at table structure where we are going to store it.

API_Error

Logging and Exception handling, Versioning in ASP.NET WEB API 

After having a look at table structure further I have written a simple procedure to store this exception in the table

Now we have Complete with database part, next let’s add classes and method to write an exception into the database.

APIError Class

public class ApiError
{
    public string Message { get; set; }
    public string RequestMethod { get; set; }
    public string RequestUri { get; set; }
    public DateTime TimeUtc { get; set; }
}

Note: – Stored Procedures and table scripts are available for download

SqlErrorLogging Class

In this part we are going to write error in the database, in this class, we have InsertErrorLog method which takes the ApiError class as an input parameter.

 

public class SqlErrorLogging
{
    public void InsertErrorLog(ApiError apiError)
    {
        try
        {
            using (var sqlConnection = new SqlConnection(ConfigurationManager.ConnectionStrings["APILoggingConnection"].ConnectionString))
            {
                sqlConnection.Open();
                var cmd =
                    new SqlCommand("API_ErrorLogging", connection: sqlConnection)
                    {
                        CommandType = CommandType.StoredProcedure
                    };
                cmd.Parameters.AddWithValue("@TimeUtc", apiError.TimeUtc);
                cmd.Parameters.AddWithValue("@RequestUri", apiError.RequestUri);
                cmd.Parameters.AddWithValue("@Message", apiError.Message);
                cmd.Parameters.AddWithValue("@RequestMethod", apiError.RequestMethod);

               cmd.ExecuteNonQuery();
            }
        }
        catch (Exception)
        {
            throw;
        }
    }
}

After adding classes and Method next we are going to add Class “UnhandledExceptionLogger” which will inherit from “ExceptionLogger” abstract class.

UnhandledExceptionLogger Class

We are going to add a class “UnhandledExceptionLogger” which will inherit from “ExceptionLogger” an abstract class in that we are going to Override “Log” Method, in this method we are going to get an exception which has occurred from that exception, we are going to pull information such as Source, StackTrace, TargetSite and assign it to ApiError class for storing in the database.

using System;
using System.Web.Http.ExceptionHandling;
using WebDemoAPI.Models;

namespace WebDemoAPI.CustomHandler
{
    public class UnhandledExceptionLogger : ExceptionLogger
    {
        public override void Log(ExceptionLoggerContext context)
        {
            var ex = context.Exception;

           string strLogText = "";
            strLogText += Environment.NewLine + "Source ---/n{0}" + ex.Source;
            strLogText += Environment.NewLine + "StackTrace ---/n{0}" + ex.StackTrace;
            strLogText += Environment.NewLine + "TargetSite ---/n{0}" + ex.TargetSite;

            if (ex.InnerException != null)
            {
                strLogText += Environment.NewLine + "Inner Exception is {0}" + ex.InnerException;//error prone
            }
            if (ex.HelpLink != null)
            {
                strLogText += Environment.NewLine + "HelpLink ---/n{0}" + ex.HelpLink;//error prone
            }

            var requestedURi = (string)context.Request.RequestUri.AbsoluteUri;
            var requestMethod = context.Request.Method.ToString();
            var timeUtc = DateTime.Now;

            SqlErrorLogging sqlErrorLogging = new SqlErrorLogging();
            ApiError apiError = new ApiError()
            {
                Message = strLogText,
                RequestUri = requestedURi,
                RequestMethod = requestMethod,
                TimeUtc = DateTime.Now
            };
            sqlErrorLogging.InsertErrorLog(apiError);
        }
    }
}

After creating “UnhandledExceptionLogger” class and writing error into database next we are going to register this class globally in WebApiConfig class.

//Registering UnhandledExceptionLogger
config.Services.Replace(typeof(IExceptionLogger), new UnhandledExceptionLogger());

Registering UnhandledExceptionLogger in WebApiConfig class

using System.Web.Http;
using System.Web.Http.ExceptionHandling;
using WebDemoAPI.CustomHandler;

namespace WebDemoAPI
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services
            // Web API routes
            config.MapHttpAttributeRoutes();
            //Registering GlobalExceptionHandler
            config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
            //Registering UnhandledExceptionLogger
            config.Services.Replace(typeof(IExceptionLogger), new UnhandledExceptionLogger());
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}

After registering UnhandledExceptionLogger class now let’s run the application and see does it store exception occurred in the database.

Logging and Exception handling, Versioning in ASP.NET WEB API

After getting the error we have handled it and displayed a proper error message to the user and also logged the error in the database.

Response after handling exception and Logging exception

Logging and Exception handling, Versioning in ASP.NET WEB API

Storing Exception

Logging and Exception handling, Versioning in ASP.NET WEB API

After handling and logging Exception next we are going log each request and response to Web API.

2.     Logging Request and Response

Logging and Exception handling, Versioning in ASP.NET WEB API

 

In this part, we are going to log each request and response of WEB API.

In doing that we are going to inherit an abstract class “DelegatingHandler” and override SendAsync method.

Logging and Exception handling, Versioning in ASP.NET WEB API

If you see below table you will get a clear idea what all data we are storing from request and response into the database.

Let’s first start with creating an “API_Log” table where we are going to store this request in response.

Logging and Exception handling, Versioning in ASP.NET WEB API

After creating a table, we have created a simple stored procedure for inserting Log into API_Log table this stored procedure is available for download.

Next, we are going to add “ApiLog” Class to pass data to the stored procedure.

namespace WebDemoAPI.Models
{
    public class ApiLog
    {
        public string Host { get; set; }
        public string Headers { get; set; }
        public string StatusCode { get; set; }
        public string RequestBody { get; set; }
        public string RequestedMethod { get; set; }
        public string UserHostAddress { get; set; }
        public string Useragent { get; set; }
        public string AbsoluteUri { get; set; }
        public string RequestType { get; set; }
    }
}

After adding ApiLog class next we are going Add an ApiLogging class in that class we are going to add InsertLog method which will take ApiLog class as a parameter and ApiLog class data will be mapped to SQL parameters to insert data into database.

public class ApiLogging
{
    public void InsertLog(ApiLog apiLog)
    {
        try
        {
            using (var sqlConnection = new SqlConnection(ConfigurationManager.ConnectionStrings["APILoggingConnection"].ConnectionString))
            {
                sqlConnection.Open();
                var cmd =
                    new SqlCommand("API_Logging", connection: sqlConnection)
                    {
                        CommandType = CommandType.StoredProcedure
                    };
                cmd.Parameters.AddWithValue("@Host", apiLog.Host);
                cmd.Parameters.AddWithValue("@Headers", apiLog.Headers);
                cmd.Parameters.AddWithValue("@StatusCode", apiLog.StatusCode);
                cmd.Parameters.AddWithValue("@RequestBody", apiLog.RequestBody);
                cmd.Parameters.AddWithValue("@RequestedMethod", apiLog.RequestedMethod);
                cmd.Parameters.AddWithValue("@UserHostAddress", apiLog.UserHostAddress);
                cmd.Parameters.AddWithValue("@Useragent", apiLog.Useragent);
                cmd.Parameters.AddWithValue("@AbsoluteUri", apiLog.AbsoluteUri);
                cmd.Parameters.AddWithValue("@RequestType", apiLog.RequestType);
                cmd.ExecuteNonQuery();
            }
        }
        catch (Exception)
        {
            throw;
        }
    }
}

After completing with the adding ApiLogging class next we are going to Right the main heart of this process is adding the Custom handler.

Creating Custom Handler

We are going to add a class with name “RequestResponseHandler” and then we are going to inherit from DelegatingHandler abstract class and Override SendAsync method.

public class RequestResponseHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
    }
}

Before implementing SendAsync Method I have written a simple class MessageLogging which has 2 methods in it IncomingMessageAsync and OutgoingMessageAsync I have created this method for just assigning Request types and to call both methods separately.

public class MessageLogging
{
    public void IncomingMessageAsync(ApiLog apiLog)
    {
        apiLog.RequestType = "Request";
        var sqlErrorLogging = new ApiLogging();
        sqlErrorLogging.InsertLog(apiLog);
    }

    public void OutgoingMessageAsync(ApiLog apiLog)
    {
        apiLog.RequestType = "Response";
        var sqlErrorLogging = new ApiLogging();
        sqlErrorLogging.InsertLog(apiLog);
    }
}

Now after adding MessageLogging class next we are going to implement SendAsync method from DelegatingHandler abstract class.

public class RequestResponseHandler: DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        var requestedMethod = request.Method;
        var userHostAddress = HttpContext.Current != null ? HttpContext.Current.Request.UserHostAddress : "0.0.0.0";
        var useragent = request.Headers.UserAgent.ToString();
        var requestMessage = await request.Content.ReadAsByteArrayAsync();
        var uriAccessed = request.RequestUri.AbsoluteUri;

        var responseHeadersString = new StringBuilder();
        foreach (var header in request.Headers)
        {
            responseHeadersString.Append($"{header.Key}: {String.Join(", ", header.Value)}{Environment.NewLine}");
        }

        var messageLoggingHandler = new MessageLogging();

        var requestLog = new ApiLog()
        {
            Headers = responseHeadersString.ToString(),
            AbsoluteUri = uriAccessed,
            Host = userHostAddress,
            RequestBody = Encoding.UTF8.GetString(requestMessage),
            UserHostAddress = userHostAddress,
            Useragent = useragent,
            RequestedMethod = requestedMethod.ToString(),
            StatusCode = string.Empty
        };

        messageLoggingHandler.IncomingMessageAsync(requestLog);

        var response = await base.SendAsync(request, cancellationToken);

        byte[] responseMessage;
        if (response.IsSuccessStatusCode)
            responseMessage = await response.Content.ReadAsByteArrayAsync();
        else
            responseMessage = Encoding.UTF8.GetBytes(response.ReasonPhrase);

        var responseLog = new ApiLog()
        {
            Headers = responseHeadersString.ToString(),
            AbsoluteUri = uriAccessed,
            Host = userHostAddress,
            RequestBody = Encoding.UTF8.GetString(responseMessage),
            UserHostAddress = userHostAddress,
            Useragent = useragent,
            RequestedMethod = requestedMethod.ToString(),
            StatusCode = string.Empty
        };

        messageLoggingHandler.OutgoingMessageAsync(responseLog);
        return response;
    }
}

Let’s understand what we have written in the SendAsync method.

Request Method

var requestedMethod = request.Method;

we are storing request method does it was a POST PUT DELETE or GET.

Logging and Exception handling, Versioning in ASP.NET WEB API

Host Address

var userHostAddress = HttpContext.Current != null ? HttpContext.Current.Request.UserHostAddress : "0.0.0.0";

we are getting IP Address from where this request came from.

Logging and Exception handling, Versioning in ASP.NET WEB API

UserAgent

var useragent = request.Headers.UserAgent.ToString();

UserAgent gives you a raw string about the browser.

Logging and Exception handling, Versioning in ASP.NET WEB API

Request Body

var requestMessage = await request.Content.ReadAsByteArrayAsync();

 

Logging and Exception handling, Versioning in ASP.NET WEB API

Absolute Uri

var uriAccessed = request.RequestUri.AbsoluteUri;

Logging and Exception handling, Versioning in ASP.NET WEB API

Headers

var responseHeadersString = new StringBuilder();
foreach (var header in request.Headers)
{
    responseHeadersString.Append($"{header.Key}: {String.Join(", ", header.Value)}{Environment.NewLine}");
}

Logging and Exception handling, Versioning in ASP.NET WEB API

Assign Value to ApiLog Class

var messageLoggingHandler = new MessageLogging();

var requestLog = new ApiLog()
{
    Headers = responseHeadersString.ToString(),
    AbsoluteUri = uriAccessed,
    Host = userHostAddress,
    RequestBody = Encoding.UTF8.GetString(requestMessage),
    UserHostAddress = userHostAddress,
    Useragent = useragent,
    RequestedMethod = requestedMethod.ToString(),
    StatusCode = string.Empty
};

Incoming Request Logging

messageLoggingHandler.IncomingMessageAsync(requestLog);

Outgoing Response Logging

messageLoggingHandler.OutgoingMessageAsync(responseLog);

Registering RequestResponseHandler

using System.Web.Http;
using System.Web.Http.ExceptionHandling;
using WebDemoAPI.CustomHandler;

namespace WebDemoAPI
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services

            // Web API routes
            config.MapHttpAttributeRoutes();

            //Registering GlobalExceptionHandler
            config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
            
            //Registering UnhandledExceptionLogger
            config.Services.Replace(typeof(IExceptionLogger), new UnhandledExceptionLogger());

            //Registering RequestResponseHandler
            config.MessageHandlers.Add(new RequestResponseHandler());

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}

Now we got an idea how this process work lets run the application and see does it works.

Accessing Values API Controller

Logging and Exception handling, Versioning in ASP.NET WEB API

 

Request and Response Web API Logging

Logging and Exception handling, Versioning in ASP.NET WEB API

3.     Versioning

Logging and Exception handling, Versioning in ASP.NET WEB API

 Icons made by Freepik from www.flaticon.com is licensed by CC 3.0 BY

It is the most important part of Web API development as we keep refining application, we keep making changes to application, if we make changes to the API which are already in production and many users are consuming it will break working application, solution for this is to version your AIPs such that older user which are consuming your API will not have any effect on it.

Let’s start implementing versioning in the Asp.net Web API in with simple steps.

First, we are going to add “Microsoft.AspNet.WebApi.Versioning” NuGet package to the application.

 

Logging and Exception handling, Versioning in ASP.NET WEB API

 

After installing NuGet Package next, we are going to Register AddApiVersioning method in WebApiConfig.cs file.   

The ApiVersioningOptions class allows you to configure, customize, and extend the default behaviors when you add an API versioning to your application.

Logging and Exception handling, Versioning in ASP.NET WEB API

Referenced from: – https://github.com/Microsoft/aspnet-api-versioning/wiki/API-Versioning-Options

Code Snippet of AddApiVersioning Method

config.AddApiVersioning(o =>
    {
        o.ReportApiVersions = true;
        o.AssumeDefaultVersionWhenUnspecified = true;
        o.DefaultApiVersion = new ApiVersion(2, 0);
        o.ApiVersionReader = new HeaderApiVersionReader("version");
        o.ApiVersionSelector = new CurrentImplementationApiVersionSelector(o);
    }
);

Complete Code Snippet of WebApiConfig

In this part, we are going to comment default routing and enable attribute base routing.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.AddApiVersioning(o =>
            {
                o.ReportApiVersions = true;
                o.AssumeDefaultVersionWhenUnspecified = true;
                o.DefaultApiVersion = new ApiVersion(2, 0);
                o.ApiVersionReader = new HeaderApiVersionReader("version");
                o.ApiVersionSelector = new CurrentImplementationApiVersionSelector(o);
            }
        );
        // Web API configuration and services

        // Web API routes
        config.MapHttpAttributeRoutes();

        //Registering GlobalExceptionHandler
        config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());

        //Registering UnhandledExceptionLogger
        config.Services.Replace(typeof(IExceptionLogger), new UnhandledExceptionLogger());

        //Registering RequestResponseHandler
        config.MessageHandlers.Add(new RequestResponseHandler());

        //config.Routes.MapHttpRoute(
        //    name: "DefaultApi",
        //    routeTemplate: "api/{controller}/{id}",
        //    defaults: new { id = RouteParameter.Optional }
        //);
    }
}

After completing with registering method next we are going to add another API controller with the name “Values2Controller”.

Adding Values2Controller API controller

If you see we have added Values2 name API controller we have added a version in the name of the controller is not mandatory to add but the name must be unique and easy to understand.

public class Values2Controller : ApiController
{
    // GET: api/Values2
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }

    // GET: api/Values2/5
    public string Get(int id)
    {
        return "value";
    }

    // POST: api/Values2
    public void Post([FromBody]string value){}

    // PUT: api/Values2/5
    public void Put(int id, [FromBody]string value) {}

    // DELETE: api/Values2/5
    public void Delete(int id) {}
}

After adding Values2 API controller next we are going to add Route Attributes to both API controller the old one also and the new one also.

Adding ApiVersion Attribute and Route Attribute to Values API controller

[ApiVersion("1.0")]
[Route("api/values")]
public class ValuesController : ApiController
{
    // GET api/values
    public IEnumerable<string> Get()
    {
        //throw new NotImplementedException("");
        return new string[] { "value1", "value2" };
    }

    // GET api/values/5
    public string Get(int id) {  return "value";}

    // POST api/values
    public void Post([FromBody]string value){}

    // PUT api/values/5
    public void Put(int id, [FromBody]string value){}

    // DELETE api/values/5
    public void Delete(int id) {} 
}

Adding ApiVersion Attribute and Route Attribute to Values2 API controller

[ApiVersion("2.0")]
[Route("api/values")]
public class Values2Controller : ApiController
{
    // GET api/Values2
    public IEnumerable<string> Get()
    {
        //throw new NotImplementedException("");
        return new string[] { "version2", " version2" };
    }

    // GET api/Values2/5
    public string Get(int id){return "value";}

    // POST api/Values2
    public void Post([FromBody]string value) { }

    // PUT api/Values2/5
    public void Put(int id, [FromBody]string value) { }

    // DELETE api/Values2/5
    public void Delete(int id) { }
}

After adding routes and version attribute next save and run the application.

Now to call the API, we need to pass an API version from the header and the name of the header is “version”.

We are going to pass header name “version” and value as 1.0 to the calling values controller.

Requesting Values API

After completing with calling version 1.0 values API next in the same way we are going to call values2 API with version 2.0 header.

We are going to pass header name “version” and value as 2.0 to the calling Values2 controller.

Logging and Exception handling, Versioning in ASP.NET WEB API

After accessing values controller (values2controller) with version 2.0 header we got a valid response which we were expecting.

Conclusion

In this article we have learned how to “handle exceptions”, “Log Exceptions” and also learned how to log each incoming and outgoing request and response of web API along with it how we can do versioning of web api such that I should not break existing working APIs, this all we learned in step by step way and in detail manner such we can directly integrate with Live Projects.

Thank you, I hope you liked my article.