Global Exception Handling and Logging in aspnet core webapi

Create a Model for your Error details as below:

public class ErrorDetails
{
	public int StatusCode { get; set; }
	public string Message { get; set; }


	public override string ToString()
	{
		return JsonConvert.SerializeObject(this); //JsonConvert is part of Newtonsoft.Json package.
	}
}

Create the Exception Factory which will handle Exceptions globally in your Api:

public static class ExceptionFactory
{ 
    public static void ConfigureExceptionHandler(this IApplicationBuilder app, int StatusCode = 0, string message = "")
    {
        app.UseExceptionHandler(appError =>
        {
            appError.Run(async context =>
            {
                context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                context.Response.ContentType = "application/json";
                
                var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
                if (contextFeature != null)
                {
                    LogTraceFactory.LogError($"Something went wrong: {contextFeature.Error}");

                    await context.Response.WriteAsync(new ErrorDetails()
                    {
                        StatusCode = context.Response.StatusCode,
                        Message = "Internal Server Error."
                    }.ToString());
                }
            });
        });
    }
}

Register Exception handling in your Api:

// 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();
	}
	app.ConfigureExceptionHandler();
	.....
}

Also, you can return the error details in your Action Controllers as below:

[HttpGet]
[Route("product/getproductdetails")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public IActionResult GetproductDetails([FromQuery]int productId)
{
	LeadsProduct lead = null;
	if (productId == 0)
	{
		LogTraceFactory.LogError($"Incorrect parameters, productId: {productId}");
		return BadRequest(new ErrorDetails { StatusCode = Convert.ToInt32(HttpStatusCode.BadRequest), Message = $"Missing parameters, productId: {productId} for product Leads." });
	}

	lead = leadService.FetchProductDetails(productId);

	if (lead == null)
	{
		LogTraceFactory.LogError($"product Leads Not Found for productId: {productId}");
		return NotFound(new ErrorDetails { StatusCode = Convert.ToInt32(HttpStatusCode.NotFound), Message = $"product Leads not found for productId {productId}." });
	}

	return Ok(lead);
}

The example above uses the nlog package in the .net core Web Api. You can create LogTraceFactory class as below:

public static class LogTraceFactory
{
	private static ILogger logger = LogManager.GetCurrentClassLogger();

	public static void LogDebug(string message)
	{
		logger.Debug(message);
	}

	public static void LogError(string message)
	{
		logger.Error(message);
	}

	public static void LogInfo(string message)
	{
		logger.Info(message);
	}

	public static void LogWarn(string message)
	{
		logger.Warn(message);
	}
}

Configure nlog package as below in the nlog.config file:

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      internalLogLevel="Trace"
      internalLogFile="C:\internal_logs\internallog.txt">

  <targets>
    <target name="logfile" xsi:type="File"
            fileName="C:\ProjectLogs\${shortdate}_logfile.txt"
            layout="${longdate} ${level:uppercase=true} ${message}"/>
  </targets>

  <rules>
    <logger name="*" minlevel="Debug" writeTo="logfile" />
  </rules>
</nlog>

You can modify the above configuration as required. As part of the clean architecture, it is better to setup the ExceptionFactory and LogTraceFactory in the Common Layer of your Solution.


Omg! Now you can earn a six figure income. Click here to know how.

How to serialize data using translator C# .net core webapi

Suppose you’re trying to fetch user data from your database using ado.net in you .net core webapi. You have a SQLHelper class that calls a Stored Procedure and returns data that requires to be converted to a DTO object with pre-defined properties in C#.

The SQLHelper class will have the following method to call your Stored Procedure:

public static TData ExtecuteProcedureReturnData<TData>(string connString, string procName,
	Func<SqlDataReader, TData> translator,
	params SqlParameter[] parameters)
{
	using (var sqlConnection = new SqlConnection(connString))
	{
		using (var sqlCommand = sqlConnection.CreateCommand())
		{
			sqlCommand.CommandType = System.Data.CommandType.StoredProcedure;
			sqlCommand.CommandText = procName;
			if (parameters != null)
			{
				sqlCommand.Parameters.AddRange(parameters);
			}
			sqlConnection.Open();
			using (var reader = sqlCommand.ExecuteReader())
			{
				TData elements;
				try
				{
					elements = translator(reader);
				}
				finally
				{
					while (reader.NextResult())
					{ }
				}
				return elements;
			}
		}
	}
}

What is a Translator?

A translator is a class like a DTO in C# which will serialize your data returned from the Stored Procedure into it’s properties.
This will be returned as a json object by your WebApi to your Client front-end.

You can create a Translators folder in your .net core WebApi Project to have all such classes in one place.

An example Translator is as shown below:

public static class UserTranslator
{
	public static User TranslateAsUser(this SqlDataReader reader)
	{
		if (!reader.HasRows)
			return null;
		reader.Read();

		var item = new User();

		if (reader.IsColumnExists("Username"))
			item.Username = SqlHelper.GetNullableString(reader, "Username");

		if (reader.IsColumnExists("FullName"))
			item.FullName = SqlHelper.GetNullableString(reader, "FullName");
			
		if (reader.IsColumnExists("RoleName"))
                item.RoleName = SqlHelper.GetNullableString(reader, "RoleName");

		if (reader.IsColumnExists("Email"))
			item.Email = SqlHelper.GetNullableString(reader, "Email");

		return item;
	}
}

In the above example, you data will have the following columns as Username, FullName and Email. It only returns one row and not a list.

For returning a list:

public static List<User> TranslateAsUsersList(this SqlDataReader reader)
{
	var list = new List<User>();
	while (reader.Read())
	{
		list.Add(TranslateAsUser(reader, true));
	}
	return list;
}

Make sure your reader.Read() method is not called twice.

The DTO for user is as follows:

public class User
{
	public string Username { get; set; }
	public string FullName { get; set; }
	public string RoleName { get; set; }
	public string Email { get; set; }
}

Now, you need to call your Stored Procedure from your Repository:

public User getUserDetails(string UserName)
{
	string connString = CommonUtil.ConnectionString;
	SqlParameter[] param =
	{
		new SqlParameter("@Username", UserName)
	};

	User user = SqlHelper.ExtecuteProcedureReturnData<User>(
		connString,
		"GetUserDetailsFromDB",
		r => r.TranslateAsUser(), //call TranslateAsUsersList if List of Users is required and return List<User>
		param
		);

	return user;
}

Assuming, you’re using the Repository pattern in your WebApi Data Layer. Else, you can call the above method however your Project structure works.

I’ve written another post on multiple ways to fetch data for calling StoredProcedure in your WebApi for your SQLHelper class.

How to read connection strings stored in appsettings file C#

This post is based on a setup of an asp.net core application. Configuration is read in the Startup class upon the Application startup. The Configure method in this class calls the ApiBootstrapper to check whether the connection string for Dev or Production is required.
This can be further used to call the Stored Procedures or query tables using ADO.Net.

Appsettings.json file is the asp.net core config file. This file contains the Connection Strings is as shown below:

{
	"configSetting": {
		"ConnectionStrings": {
			"ProdConnection": "Data Source=ServerName;Initial Catalog=DBProd;UID=username;PWD=password;",
			"DevConnection": "Data Source=ServerName;Initial Catalog=DBDev;UID=username;PWD=password;"
		},
		"Parameters": {
			"IsProduction": true
			"IsDev": false
		}
	}
}
public class Startup
{
	public Startup(IConfiguration configuration)
	{
		Configuration = configuration;
	}
	
	public IConfiguration Configuration { get; }
	
	public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
	{
		////
	
	
		ApiBootstrapper.Initialize(Configuration);
	}
	
}

Below is the code for ApiBootstrapper class:

public class ApiBootstrapper
{
	
	public static void Initialize(IConfiguration configuration)
	{
		CommonUtil.IsProduction = configuration.GetSection(ConfigKeys.configSetting.ToString()).GetSection(ConfigKeys.Log.ToString()).Value.ToString();
		if (Convert.ToBoolean(CommonUtil.IsProduction))
		{
			CommonUtil.ConnectionString = configuration.GetSection("configSetting").GetSection("ConnectionStrings").GetSection("ProdConnection").Value.ToString();
		}
	}
}

The above example shows how we can store Connection Strings for different environments like Dev and Prod and read it based on Config file settings.

Authorize WebAPI request with ActionFilterAttribute

Action filters let you use pre and post processing logic which can be applied globally, to an action method or Controller class. These are generally used for applying logic for logging, caching, authorization etc. which can be shared across Controllers.

For adding Authorization to a WebAPI, we can use ActionFilterAttributes to enable strict access for a particular role or user. The ActionFilterAttribute class below implements the authorization logic.

The base ActionFilterAttribute class has the following methods that you can override as per the MS documentation:

  • OnActionExecuting – This method is called before a controller action is executed.
  • OnActionExecuted – This method is called after a controller action is executed.
  • OnResultExecuting – This method is called before a controller action result is executed.
  • OnResultExecuted – This method is called after a controller action result is executed.

As an example, you have a class called TokenFilter that inherits from ActionFilterAttribute. This class overrides the following methods OnActionExecuting and OnActionExecuted as below:

public class TokenFilter : ActionFilterAttribute
{


	public override void OnActionExecuting(HttpActionContext actionContext)
	{
		try
		{
			if (CheckValidHeaders(actionContext))
			{
				var auth_token = actionContext.Request.Headers.GetValues("access-token").FirstOrDefault();
				
				var ValidateObj = MyUtils.ValidateToken(auth_token);
				if (!ValidateObj.IsAuthenticated)
				{
					var error = MyUtils.GetNotSignedInErrorMessage();
					var response = actionContext.Request.CreateResponse(System.Net.HttpStatusCode.Unauthorized, error);
					actionContext.Response = response;
				}
				else
				{
					if (!ValidateObj.IsDurationValid)
					{
						auth_token = MyUtils.GenerateToken(email);
						actionContext.Request.Headers.Remove("access-token");
						actionContext.Request.Headers.Add("access-token", auth_token);
					}

					HttpContext.Current.Items.Add("access-token", auth_token);
					
				}
			}
			else
			{
				var response = actionContext.Request.CreateResponse(System.Net.HttpStatusCode.Unauthorized, "error message");
				actionContext.Response = response;
			}
		}
		catch (Exception ex)
		{
			var response = actionContext.Request.CreateResponse(System.Net.HttpStatusCode.Unauthorized, "error message");
			actionContext.Response = response;
		}
	}

The above code OnActionExecuting will check for valid Headers in the request, Validates the auth token and also generates the token if required. The new auth token is again injected into the Request Headers.

The OnActionExecuted method is as below:

public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
    var auth_token = HttpContext.Current.Items["access-token"].ToString();
    actionExecutedContext.Response.Headers.Add("access-token", auth_token);
    var email = HttpContext.Current.Items["emailid"].ToString();
    actionExecutedContext.Response.Headers.Add("emailid", email);
}

Similary, other custom headers can be used to Authorize the request.

The below Authorize Filter class will authorize the user based on email id:

public class AuthorizeUserAttribute : ActionFilterAttribute
{
	bool IsinRole = false;
	public override void OnActionExecuting(HttpActionContext actionContext)
	{
		if (HttpContext.Current.Request.Headers["emailid"] != null)
		{
			var email = HttpContext.Current.Request.Headers["emailid"].ToString();
			 IsinRole = MyUtils.Checkroles(email, "admin");
			if(!IsinRole)
			{
				var response = actionContext.Request.CreateResponse(System.Net.HttpStatusCode.Unauthorized, "error message");
				actionContext.Response = response;
			}
		}

	}
}

The below controller will use the TokenFilter and AuthorizeUser attributes to use the Filter out Unauthorized users as below:

[TokenFilter]
[AuthorizeUser]
public class ValuesController : ApiController
{

}

So any Action method that gets called for this Controller, has to have a valid token and a valid role Authorized to access this Controller.


Get great detals on Mobile Phones at Alibaba.

SQL Server Instead Of trigger to prevent duplicates

You might face a scenario where you’re using a REST Web Service or WebAPI where multiple hits of the same request to the API from the User interface is causing duplicate inserts. As the hits come to the API at the same date time-stamp, the API check fails at the database level as that record won’t exist in the table at that point in time.

To prevent the duplicate from getting inserted, we have multiple options at the Database level and one of them is using Instead Of trigger.

Using the INSTEAD Of trigger, you can conditionally choose to INSERT into the table or take some other action as per the requirement.

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[PREVENT_DUP_INSERT] ON [dbo].[tbl_submission]
INSTEAD OF INSERT
AS
BEGIN
	SET NOCOUNT OFF;
	IF NOT EXISTS(
		SELECT 1 FROM dbo.tbl_submission
		WHERE question_uid=(SELECT inserted.q_uid FROM inserted)
		AND user_survey_instance_id=(SELECT user_inst_id FROM inserted)
	)
	BEGIN
		INSERT INTO dbo.tbl_submission(user_id,q_uid,[value],[group],user_inst_id,created_at,updated_at)
		SELECT user_id,q_uid,[value],[group],user_inst_id,created_at,updated_at FROM inserted
	END
	SELECT [id] FROM [dbo].[tbl_submission] WHERE @@ROWCOUNT > 0 AND [id] = scope_identity();
END

An id must be returned by the body of the INSTEAD OF trigger. This is required especially if you’re using an ORM like Entity Framework which may be give concurrency related exception.

You can also choose to apply UNIQUE constraint on select columns to prevent duplicates and handle the Insert exceptions in the API or Trigger itself. But in my case, since there are already few duplicate insertions and one of the columns was VARCHAR(MAX) which does not allow creating UNIQUE indexes.

What are Content-type and Accept Headers

I’ve often felt confused with the difference between the Content-type and Accept Headers that are passed along with a request to an API. I’ve used a fake Json REST API called JsonPlaceHolder to show the Json request and response with a Post request.

Accept Header tells the API that it is expecting the response in the specified media type e.g. application/json or application/xml.
Accept: application/json

And Content-Type tells the API about the media type of the request being sent in the request body e.g. application/json.
Content-Type: application/json

Both headers are sent along with the call to the API from the Client. Below I’m using the Postman Client to test the scenario for json request.
For the scenario, to test the response in XML format, the API should support the format provided in the Accept Header.

The 2nd image above shows the Json body being passed in the Post request for Creating the resource.

For a standard HTML Post request, the Content-Type Header will be one of the Standard types:

  1. application/x-www-form-urlencoded for simple ASCII text
  2. multipart/form-data for file upload support or non-ASCII text