Friday, 12 November 2021

Use multiple implementations of an interface with ASP.NET Core DI

 On my previous article Middleware and Filters power in ASP.NET Core, one of my friend requested me to write about multiple implementations of an interface dependency resolution and I promised him that my next article will cove this. So here it is.

In this article, I’ll be using the same example by extending it to use multiple implementation of logger service by resolving it based on a config setting to log my exception.

First we will create ILoggerService and different implementation of it by creating a Services folder in the project.

namespace CoreWebAPIDemo.Services
{
public enum LogType
{
Trace,
Debug,
Information,
Warning,
Error
}
public interface ILoggerService
{
public void Log(LogType logType, object logInfo);
}
public class NullLoggerService : ILoggerService
{
public void Log(LogType logType, object logInfo)
{
//Do Nothing
}
}
public class SerilogFileLoggerService : ILoggerService
{
public void Log(LogType logType, object logInfo)
{
//your logging code
}
}
public class DbLoggerService : ILoggerService
{
public void Log(LogType logType, object logInfo)
{
//your logging code
}
}
}

Next, I’ll add a delegate to hold a reference of a function which accept the one string argument ‘logger type’ and return the ILoggerService instance. For this create a cs file “LoggerServiceResolver.cs” and add below code.

namespace CoreWebAPIDemo.Services
{
public delegate ILoggerService LoggerServiceResolver(string loggerType);
}

Now in Startup.ConfigureServices method, add the different logger services and also add LoggerServiceResolver with an implementation to find the right requested service.

services.AddScoped<SerilogFileLoggerService>();
services.AddScoped<DbLoggerService>();
services.AddScoped<NullLoggerService>();
services.AddTransient<LoggerServiceResolver>(serviceProvider => loggerType =>
{
switch (loggerType)
{
case "SerilogFileLogger":
return serviceProvider.GetService<SerilogFileLoggerService>();
case "DbLogger":
return serviceProvider.GetService<DbLoggerService>();
default:
return serviceProvider.GetService<NullLoggerService>();
}
});

I want to set the logger type option configurable through appsettings.json file so will add a setting there as:

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"UserLogger": "SerilogFileLogger",
"AllowedHosts": "*"
}

By this implementation is ready. Now we need to add the code to consume this and for that I’ll enhance my “ExceptionHandlingMiddleware” which I use in my previous article. Please read it here: Middleware and Filters power in ASP.NET Core

using CoreWebAPIDemo.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using System;
using System.Net;
using System.Threading.Tasks;
namespace CoreWebAPIDemo.Middleware
{
public class ExceptionHandlingMiddleware
{
private readonly LoggerServiceResolver _serviceResolver;
private readonly IConfiguration _configuration;
private readonly RequestDelegate _requestDelegate;public ExceptionHandlingMiddleware(RequestDelegate requestDelegate, IConfiguration configuraiton, LoggerServiceResolver serviceResolver)
{
_requestDelegate = requestDelegate;
_serviceResolver = serviceResolver;
_configuration = configuraiton;

}
public async Task Invoke(HttpContext context)
{
try
{
await _requestDelegate(context);
}
catch (Exception ex)
{
await HandleException(context, ex);
}
}
private Task HandleException(HttpContext context, Exception ex)
{
var logType = _configuration.GetValue<string>("UserLogger");
var myLogger = _serviceResolver(logType);
myLogger.Log(LogType.Error, ex);

context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
return context.Response.WriteAsync(ex.Message);
}
}
}

In above code, Code in Bold is the new code added and needed for this example. Now we all set to use the different implementations of ILoggerService with ASP.NET Core DI with my middleware. Similarly we can implement the same with “ExceptionHandlerFilter” class and changes would be (highlighted in Bold):

using CoreWebAPIDemo.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Configuration;
using System;
using System.Net;
namespace CoreWebAPIDemo.Middleware
{
public class ExceptionHandlerFilter : Attribute, IActionFilter
{
private readonly LoggerServiceResolver _serviceResolver;
private readonly IConfiguration _configuration;
public ExceptionHandlerFilter(IConfiguration configuraiton, LoggerServiceResolver serviceResolver)
{
_serviceResolver = serviceResolver;
_configuration = configuraiton;
}
public void OnActionExecuting(ActionExecutingContext context) { }public void OnActionExecuted(ActionExecutedContext context)
{
var logType = _configuration.GetValue<string>("UserLogger");
var myLogger = _serviceResolver(logType);


if (context.Exception is Exception exception)
{
myLogger.Log(LogType.Error, exception);
context.Result = new ObjectResult(exception.Message)
{
StatusCode = (int)HttpStatusCode.InternalServerError,
};
context.ExceptionHandled = true;
}
}
}
public class HttpResponseException : Exception
{
public int Status { get; set; } = 500;
public object Value { get; set; }
}
}

Thank you for reading. Hope you enjoyed it and learn the Use of multiple implementations of an interface with ASP.NET Core DI. Don’t forget to clap if you like it and follow me to receive such article updates from me.

No comments:

Post a Comment