Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Exception Log with Request Detail in ASP.NET MVC

0.00/5 (No votes)
16 Jul 2014 1  
Exception log with request detail in ASP.NET MVC

Introduction

Exception is a common issue in web projects. Exception loggers are quite important to track such exceptions to improve the quality of the solution. But in my case, I wanted to track little extra information rather than just the stack trace. So here, we will see how we can trace the whole web request object in ASP.NET web service.

Background

The intention was started when I first wrote about the Log all parameters that were passed to some method in C#. But I wanted to avoid such number of try/catch blocks, and use more centralized code. So let’s see how we can do this in ASP.NET MVC.

Let’s say we want to collect error data as:

public class ErrorModel
{
    /*RouteDetail*/
    public string ControllerName { get; set; }
    public string ActionName { get; set; }

    /*SessionDetail*/
    public bool? HasSession { get; set; }
    public string Session { get; set; }

    /*RequestDetail*/
    public Dictionary<string, string> RequestHeader { get; set; }
    public string RequestData { get; set; }

    /*ExceptionDetail*/
    public Exception Exception { get; set; }
    public DateTime CreatedDateTime { get; set; }
}

Where Can We Find the Errors?

  • In Action
  • In Controller for all of its actions
  • For all controllers actions, using a base controller
  • For whole application

In Action

This is a simple try/catch block in action. An exception would be found at the catch scope.

public ActionResult Create(string name)
{
    try
    {
        throw new Exception("Error to Reset.");
    }
    catch (Exception exception)
    {
        //Here is the exception
        var aException = exception;
        throw new Exception("Error.", exception);
    }
}

In Controller for All of Its Actions

Using OnException on a controller means whenever an exception would be thrown from any action of the controller, this would fire each time.

protected override void OnException(ExceptionContext filterContext)
{
    //Here is the exception
    var aException = filterContext.Exception;
    base.OnException(filterContext);
}

For All Controllers Actions, Using a Base Controller

Using a base controller for each controller of the project, as I did in Session in ASP.NET MVC and adding placing the OnException at the BaseController means whenever an error would be found at any actions of the SubControllers, the BaseController’s OnException would be fired.

For Whole Application

Adding Application_Error method at the Global.asax:

protected void Application_Error(Object sender, EventArgs e)
{
    //Here is the exception
    Exception aException = HttpContext.Current.Server.GetLastError();
    //Or
    var httpContext = ((MvcApplication)sender).Context;
    aException = httpContext.Server.GetLastError();
}

What Do We Need More?

  • Session Object
  • HttpRequest Object

Session Object

It’s not important to have a session object. The Log utility can create logs without the session too. But it is important to know if the user was logged in when the exception was thrown.

Do let’s add an inline session object in controller like this:

protected override void Initialize(RequestContext requestContext)
{
    base.Initialize(requestContext);
    Session["logOnUserId"] = "User-101";
}

HttpRequest Object

Which may come from any browser or via any request. Here, we can use any of those to our error logger utility.

HttpRequest request = System.Web.HttpContext.Current.Request;   
HttpRequestBase requestBase = new HttpRequestWrapper(request);     //this one is important

Let’s Start Building the Error Log Utility

Here is the Extensions method, which will process the HttpRequest object, to trace request header and params.

public static class HttpRequestExtensions
{
    public static string ParamsToString(this HttpRequestBase request)
    {
        request.InputStream.Seek(0, SeekOrigin.Begin);
        return new StreamReader(request.InputStream).ReadToEnd();
    }

    public static Dictionary<string, string> ToRaw(this HttpRequest request)
    {
        return new HttpRequestWrapper(request).ToRaw();
    }

    public static Dictionary<string, string> ToRaw(this HttpRequestBase requestBase)
    {
        Dictionary<string, string> writer = new Dictionary<string, string>();
        WriteStartLine(requestBase, ref writer);
        WriteHeaders(requestBase, ref writer);
        WriteBody(requestBase, ref writer);
        return writer;
    }

    private static void WriteStartLine(HttpRequestBase request, ref Dictionary<string, string> writer)
    {
        writer.Add("HttpMethod", request.HttpMethod);
        writer.Add("Url", request.Url.ToString());
        writer.Add("ServerVariables", request.ServerVariables["SERVER_PROTOCOL"]);
    }

    private static void WriteHeaders(HttpRequestBase request, ref Dictionary<string, string> writer)
    {
        foreach (string key in request.Headers.AllKeys)
        {
            writer.Add(key, request.Headers[key]);
        }
    }

    private static void WriteBody(HttpRequestBase request, ref Dictionary<string, string> writer)
    {
        StreamReader reader = new StreamReader(request.InputStream);
        try
        {
            string body = reader.ReadToEnd();
            writer.Add("Body", body);
        }
        finally
        {
            reader.BaseStream.Position = 0;
        }
    }
} 

Here is the Utility class to provide us the ErrorModel:

public class ErrorLogUtility
{
    public readonly HttpRequestBase RequestBase;

    public ErrorLogUtility(HttpRequestBase requestBase)
    {
        if (requestBase == null)
        {
            throw new NullReferenceException("requestBase is null at ErrorLogUtility.");
        }
        RequestBase = requestBase;
    }

    public ErrorLogUtility(HttpRequest request)
        : this(new HttpRequestWrapper(request))
    {
    }

    public ErrorModel GetErrorModel(Exception exception)
    {
        var errorModel = new ErrorModel();
        SetRouteDetail(ref errorModel);
        SetRequestDetail(ref errorModel);
        SetExceptionDetail(ref errorModel, exception);
        return errorModel;
    }

    public ErrorModel GetErrorModel(Exception exception, object session)
    {
        var errorModel = GetErrorModel(exception);
        SetSessionDetail(ref errorModel, session);
        return errorModel;
    }

    private void SetRequestDetail(ref ErrorModel errorModel)
    {
        errorModel.RequestHeader = RequestBase.ToRaw();
        errorModel.RequestData = RequestBase.ParamsToString();
    }

    private void SetRouteDetail(ref ErrorModel errorModel)
    {
        Func<string, string> routeDataValue = delegate(string indexName)
        {
            bool hasValue = RequestBase.RequestContext.RouteData.Values[indexName] != null;
            return hasValue ? RequestBase.RequestContext.RouteData.Values[indexName].ToString() : "";
        };
        errorModel.ControllerName = routeDataValue("controller");
        errorModel.ActionName = routeDataValue("action");
    }

    private void SetExceptionDetail(ref ErrorModel errorModel, Exception exception)
    {
        errorModel.Exception = exception;
        errorModel.CreatedDateTime = DateTime.Now;
    }

    private void SetSessionDetail(ref ErrorModel errorModel, object session)
    {
        errorModel.HasSession = session != null;
        errorModel.Session = (session != null)? session.ToString() : "";
    }
}

Using the Code

In Controller for All of Its Actions / For All Controllers Actions, Using a Base Controller

/*take log*/
protected override void OnException(ExceptionContext filterContext)
{
    //HttpRequest or HttpRequestBase is required in the constructor
    var errorModelUsingRequestBase = new ErrorLogUtility
    (filterContext.RequestContext.HttpContext.Request).GetErrorModel(filterContext.Exception);
    var errorModelUsingRequest = new ErrorLogUtility
    (System.Web.HttpContext.Current.Request).GetErrorModel(filterContext.Exception);
    
    //include session in error model
    var errorModelWithSession = new ErrorLogUtility(System.Web.HttpContext.Current.Request)
                                    .GetErrorModel(filterContext.Exception, Session["logOnUserId"]);
    base.OnException(filterContext);
}

For Whole Application

protected void Application_Error(Object sender, EventArgs e)
{
    /*take log*/
    var httpContext = ((MvcApplication)sender).Context;
    Exception exception = HttpContext.Current.Server.GetLastError();            
    //or
    //exception = httpContext.Server.GetLastError();

    //HttpRequest or HttpRequestBase is required at contructor
    var errorModelUsingRequestBase = new ErrorLogUtility(httpContext.Request).GetErrorModel(exception);
    var errorModelUsingRequest = new ErrorLogUtility
    (System.Web.HttpContext.Current.Request).GetErrorModel(exception);

    //include session in error model
    var errorModelWithSession = new ErrorLogUtility(System.Web.HttpContext.Current.Request)
                                    .GetErrorModel(exception, Session["logOnUserId"]);
}

In Action

/*take log*/
public ActionResult Create(string name)
{
    try
    {
        throw new Exception("Error to Reset.");
    }
    catch (Exception exception)
    {
        //HttpRequest is required at contructor
        var errorModelUsingRequest = new ErrorLogUtility
        (System.Web.HttpContext.Current.Request).GetErrorModel(exception);
        //include session in error model
        var errorModelWithSession = new ErrorLogUtility(System.Web.HttpContext.Current.Request)
                                        .GetErrorModel(exception, Session["logOnUserId"]);
        throw new Exception("Error.", exception);
    }
}

Limitations

  1. I am still working with it. There could be some errors too. If you find any, just let me know.
  2. If you are using custom models of the session, make sure you override the ToString method.
    class Person
    {
        public long Id { get; set; }
        public string Name { get; set; }
    
        public override string ToString()
        {
            /*your value*/
            return string.Format("Id: {0}, Name: {1}", Id, Name);
        }
    }
  3. Try to avoid repeated uses of this log process for a particular exception. (In action/ in controller/ base controller/ global.asax) and once.
  4. Have a look at the comment section "A good idea, a couple of comments about the implementation" where John Brett pointed some good things.

Sample JSONs

Here, I have converted the ErrorModel into json.

As you can see:

  • RequestData is pointing the data for which such exception was found.
  • RequestHeader points little extra information too.
{
  "ControllerName": "InControllerLog",
  "ActionName": "Edit",
  "HasSession": true,
  "Session": "User-101",
  "RequestHeader": {
    "HttpMethod": "POST",
    "Url": "http://localhost:1999/InControllerLog/Edit",
    "ServerVariables": "HTTP/1.1",
    "Cache-Control": "no-cache",
    "Connection": "keep-alive",
    "Pragma": "no-cache",
    "Content-Length": "143",
    "Content-Type": "application/json; charset=utf-8",
    "Accept": "application/json, text/javascript, */*; q=0.01",
    "Accept-Encoding": "gzip, deflate",
    "Accept-Language": "en-US,en;q=0.5",
    "Cookie": "ASP.NET_SessionId=xs32x1xeelllzuycaz01lpn3",
    "Host": "localhost:1999",
    "Referer": "http://localhost:1999/InControllerLog",
    "User-Agent": "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:30.0) Gecko/20100101 Firefox/30.0",
    "X-Requested-With": "XMLHttpRequest",
    "Body": ""
  },
  "RequestData": "{\"id\":1,
  \"student\":{\"id\":1,\"name\":\"Student_1\"},
  \"subjects\":[{\"id\":1,\"name\":\"Subject_1\"},
  {\"id\":2,\"name\":\"Subject_2\"},{\"id\":3,
  \"name\":\"Subject_3\"}]}",
  "Exception": {
    "ClassName": "System.Exception",
    "Message": "Error to Edit.",
    "Data": {},
    "InnerException": null,
    "HelpURL": null,
    "StackTraceString": "   at ErrorRequestLog.Controllers.InControllerLogController.Edit
    (UInt64 id, Student student, List`1 subjects) in 
    c:\\Users\\Dipon Roy\\Desktop\\ErrorRequestLog\\ErrorRequestLog\\ErrorRequestLog\\Controllers\\
    InControllerLogController.cs:line 35\r\n   at lambda_method(Closure , ControllerBase , 
    Object[] )\r\n   at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, 
    Object[] parameters)\r\n   at System.Web.Mvc.ReflectedActionDescriptor.Execute
    (ControllerContext controllerContext, IDictionary`2 parameters)\r\n   at 
    System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, 
    ActionDescriptor actionDescriptor, IDictionary`2 parameters)\r\n   
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass42.
    <BeginInvokeSynchronousActionMethod>b__41()\r\n   at System.Web.Mvc.Async.AsyncResultWrapper.
    <>c__DisplayClass8`1.<BeginSynchronous>b__7(IAsyncResult _)\r\n   
    at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()\r\n   at 
    System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod
                          (IAsyncResult asyncResult)\r\n   
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass37.<>c__DisplayClass39.
    <BeginInvokeActionMethodWithFilters>b__33()\r\n   
                   at System.Web.Mvc.Async.AsyncControllerActionInvoker.
    <>c__DisplayClass4f.<InvokeActionMethodFilterAsynchronously>b__49()\r\n   
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass37.
    <BeginInvokeActionMethodWithFilters>b__36(IAsyncResult asyncResult)\r\n   
    at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()\r\n   
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethodWithFilters
    (IAsyncResult asyncResult)\r\n   at System.Web.Mvc.Async.AsyncControllerActionInvoker.
    <>c__DisplayClass25.<>c__DisplayClass2a.<BeginInvokeAction>b__20()\r\n   
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass25.
    <BeginInvokeAction>b__22(IAsyncResult asyncResult)",
    "RemoteStackTraceString": null,
    "RemoteStackIndex": 0,
    "ExceptionMethod": "8\nEdit\nErrorRequestLog, Version=1.0.0.0, 
    Culture=neutral, PublicKeyToken=null\nErrorRequestLog.Controllers.InControllerLogController
    \nSystem.Web.Mvc.JsonResult Edit(UInt64, ErrorRequestLog.Model.Student, 
    System.Collections.Generic.List`1[ErrorRequestLog.Model.Subject])",
    "HResult": -2146233088,
    "Source": "ErrorRequestLog",
    "WatsonBuckets": null
  },
  "CreatedDateTime": "2014-07-16T13:15:56.5202454-08:00"
}

Use this json at http://www.jsoneditoronline.org/ to see how it would look like. Or use the model object as you want to use.

Find VS2012 MVC4 project solution in the attachment. Rebuild, let nugget install the required packages.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here