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

Break the habit of logfiles by using automated error handling in ASP.NET MVC5

0.00/5 (No votes)
14 Nov 2017 1  
Save time and effort with the help of codeRR, which finds and analyses errors for you, improving code quality along the way.

Introduction

When you are browsing your favorite website and it fails, what do you do?

Do you begin analyze the website, trying to find the root cause and then email a detailed error description to the site support?

We developers, know the importance of detailed error reports. Despite that, only a few of us help fellow coders by taking the time to write a proper report. And a regular user will make even less effort to report errors. If you happen to receive an error report, you can be almost sure that several other users have experienced the same error already.

I would argue that coders that rely on error reports from users are only aware of a fraction of all errors that their application really has.

What is codeRR?

codeRR is a replacement to log files and bug reports from users. codeRR can answer questions like: How many unique exceptions do my application throw? How often do they occur? How many users are affected? What did the user do when the exception was thrown?

With codeRR, you no longer need to scan log files for errors or to backtrack what the user did to understand why an exception was thrown.

Further, codeRR lets you take back control over your software deficiencies and allows you to focus on coding new features instead of dealing with the time-consuming task of analyzing and understanding errors.

codeRR and MVC

codeRR integrates itself into both the MVC and the ASP.NET pipeline to be able to pick up all errors and exceptions that occur in MVC.

To activate codeRR, install the MVC Nuget package (coderr.client.aspnet.mvc5) and configure your application in global.asax as follows:

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {

        // codeRR configuration
        // appKey is found in your codeRR server
        var uri = new Uri("http://yourwebserver/coderr/");
        Err.Configuration.Credentials(uri,
            "yourAppKey",
            "yourSharedSecret");

        // Tell codeRR to catch unhandled exceptions
        Err.Configuration.CatchMvcExceptions();

        // the usual stuff
        AreaRegistration.RegisterAllAreas();
        RouteConfig.RegisterRoutes(RouteTable.Routes);
    }
}

We'll discuss the configuration options in more detail later.

Now that codeRR is activated, let's say that you have the following simple action and view model:

[HttpPost]
public ActionResult Update(UpdateViewModel model)
{
    var dbEntity = ToEntity(model);
    _repository.Update(dbEntity);
    return RedirectToAction("Updated");
}

public class UpdateViewModel
{
    [Required]
    public int UserId { get; set; }

    [Required]
    public string UserName { get; set; }

    [Required]
    public string FirstName { get; set; }

    [StringLength(40, MinimumLength = 2)]
    public string LastName { get; set; }
}

When something fails in that method, for instance due to a database error, you will still get the standard yellow screen of death:

However, before you have blinked, you will also have received an (optional) email notification with a link to the new error. The notification is only sent once per unique error, no matter how many times the exception is thrown.

The great thing about that is that you will no longer have to wait until a user decides to report the error nor do you have to scan the logfile to find it.

When clicking on the link in the email, codeRR will open the related incident for you:

That page gives you a brief overview of how many times the incident have been reported, the number of users waiting for status updates and give you a list of all reports for that specific error.

You can also access a world map which shows where the reports have come from. Another view displays the context data values that are most frequently attached to the error, for instance if most reports are related to a country's common UI culture. Read our introductory article (LINK) for more information about the codeRR service.

You can browse through the context data collections by clicking on a specific report under the incident. For example, below is the RouteData collection where we see which route the user took:

.. and what information the user submitted in the HTML page:

As you see in the examples above, codeRR collects a lot of different information from MVC to allow you to easily see why something went wrong.

To learn more about context collections (12 collections are specific for ASP.NET MVC5), visit our documentation.

Reporting exceptions manually

Even though automated exception handling fits most of the use cases, sometimes you need to catch exceptions yourself. codeRR will help you to do that.

Let's look again at the code example from shown earlier, but now with model state handling in mind.

[HttpPost]
public ActionResult Update(UpdateViewModel model)
{
    if (!ModelState.IsValid)
        return View(model);
        
    try
    {
        var dbEntity = ToEntity(model);
        _repository.Store(dbEntity);
        return RedirectToAction("Updated");
    }
    catch (Exception ex)
    {
        ModelState.AddModelError("", ex.Message);

        //magic, model as context data.
        this.ReportException(ex, model);

        return View();
    }
}

Here, we have included the view model when we report the error to codeRR.

In the codeRR web site, the view model is represented as a context collection:

All other context collections are of course included.

Getting wild with context data

When reporting errors manually, the second parameter is used to include context data. That parameter is really useful when you need to include custom data, like a view model, a logged in user, or any other information which would aid you in understanding why the exception was thrown.

You can attach any type of context data, codeRR supports everything from simple primitives to complex object hierarchies.

try
{
    //[..some code..]
}
catch (Exception ex)
{
    var contextData = new {
        User = userModel,
        Id = id
    }
    Err.Report(ex, contextData)
}

The object attached above will be presented as:

Sometimes you want to divide context information into multiple collections. To do that, you need to use the ContextCollectionDTO class. You can use the ToContextCollection() extension method for cleaner code.

// a anonymous type 
var actionParameters = new { Id = id };

// create collections for our
// view model and action parameters.
var collections = new[] { 
    userModel.ToContextCollection("UserData"),
    actionParameters.ToContextCollection("ActionParameters")
};

// And attach them to the error report.
Err.Report(exception, contextData)

When you visit the incident in the codeRR website you will see two distinct collections. Note the attached objects are plain DTOs as the properties are not prefixed with User..

Validation helper

Clean code is important when you want to build maintainable applications (i.e. not led the code quality degrade over time).

There is an attribute in the codeRR MVC library that you can use to remove validation code from your action methods.

Traditionally you would have validation logic repeated in all your action methods like this:

[HttpPost]
public ActionResult Update(UpdateViewModel model)
{
    if (!ModelState.IsValid)
        return View(model);
        
    try
    {
        var dbEntity = ToEntity(model);
        _repository.Store(dbEntity);
        return RedirectToAction("Updated");
    }
    catch (Exception ex)
    {
        ModelState.AddModelError("", ex.Message);

        //magic, model as context data.
        this.ReportException(ex, model);

        return View();
    }
}

But with our attribute you can keep the code clean:

[HttpPost, CoderrAction]
public ActionResult Update(UpdateViewModel model)
{
    var dbEntity = ToEntity(model);
    _repository.Store(dbEntity);
    return View();
}

The attribute does the following:

  1. Validates the model, if it isn't valid the view will be returned again (with the model specified).
  2. If the action fails, the view will be returned with the model and the exception.Message in the validation summary
  3. The exception will also be reported to codeRR along with the model.

Clean and simple code leads to better quality as less assumptions are required to understand what the code do. The attribute above is one way to help with that.

Error pages

From a user experience perspective, error pages are important to minimize the frustration that errors can trigger. A well-designed error page can ease the pain and let the user know that you have not forgotten them and will do your best to resolve their issue.

However, it's not feasible to have one to one communication with all your users and that's typically the reason why error pages are static.

codeRR solves both challenges. We have built in error pages and also allows users to both submit feedback (what they did when the error occurred) and also invite them to follow the incident. The latter means that you can send status updates to inform all interested users as the work progress. In the last status update you typically tell which application version that the error has been corrected in.

Below is the default built in error page (with the option to follow status updates turned off).

Default error page

Any comment added will be presented in the codeRR server under the correct incident:

To activate error pages you need to add Err.Configuration.DisplayErrorPages(); to global.asax.

Built in error pages

The first error page alternative is the built in error pages that this library provides.

The error pages are configured by using the following properties:

//ask what the user did when the error occurred
Err.Configuration.UserInteraction.AskUserForDetails = true;

//do not send error directly, ask for permission first
Err.Configuration.UserInteraction.AskUserForPermission = true;

// ask if the user want to get status updates regarding your bug fix
Err.Configuration.UserInteraction.AskForEmailAddress = true;

To use the ones included in the library, you only need to activate them as mentioned above:

Err.Configuration.DisplayErrorPages();

Default error page

This page is shown if there are not a specific HTTP code error page.

Default error page

Not found page

Shown for HTTP status code 404.

Not found

Custom error pages

You can create your own error pages too. Just create a new folder under Views\ named Errors. Views should be named as the HTTP codes are defined in the HttpStatusCode enum in .NET.

The Error.cshtml view is used when a specific status code view is not found. This allows you to create specific pages for authentication failures (Unauthorized.cshtml) or if access to a specific page is denied (Forbidden.cshtml).

When create a view, the only thing you need to remember is to use our ViewModel CoderrViewModel in it:

@model codeRR.Client.AspNet.Mvc5.CoderrViewModel

<h1>Internal Server Error</h1>
<p>
    We've experienced a malfunction in the core crystal cooling. The ship will explode within five seconds.
</p>

<h3>Reason</h3>
<p>
    @Model.Exception.Message
</p>

To allow the user to describe what they did when the exception was thrown you need to include a form in your error view:

@model codeRR.Client.AspNet.Mvc5.CoderrViewModel

<h1>Error</h1>
<img src="@Url.Content("~/Content/oops.png")" />

<div>
    <h3>Reason</h3>
    <p>
        @Model.Exception.Message
    </p>
</div>
<div>
    <h3>Can you describe what you tried to do?</h3>
    <form method="post" action="/coderr/submit">
        @Html.HiddenFor(x => x.ReportId)
        @Html.TextAreaFor(x => x.UserErrorDescription, new { rows = 5, cols = 20, @class = "form-control", placeholder = "What did you do?!" })
        <br />
        <button class="btn btn-primary">Send error report</button>
    </form>
</div>

Result:

ErrorController

The error controller is used when you need more control than just being able to customize views. Create it like any other controller in the Controllers folder.

The action methods should be named like the views. i.e. public ActionResult InternalServer(CoderrViewModel model). codeRR will automatically invoke the correct action method (that corresponds to the current HTTP status code).

public class ErrorController : Controller
{
    public ActionResult Index(CoderrViewModel model)
    {
        return View("Error", model);
    }

    public ActionResult NotFound(CoderrViewModel model)
    {
        return View(model);
    }

    public ActionResult InternalServerError(CoderrViewModel model)
    {
        return View(model);
    }
}

Errors for other content types (WebApi)

codeRR will check what kind of content type the client requests (i.e.check the Accept http header). If JSON or HTML is preferred over HTML the responses below will be used instead.

JSON

{
  "error": { 
    "msg": "<The error message>", 
    "reportId": "<Unique error id>"
  }, 
  hint: "Use the report id when contacting us if you need further assistance." 
}

XML

<Error ReportId="<Unique error id>" hint="Use the report id when contacting us if you need further assistance">
    [Error message]
</Error>

"Why would I want to use these error pages"

Since exceptions are proved errors, it helps to get information directly from users. These error pages provide that possibility without any extra effort or additional coding from you.

The error descriptions will be attached to the reported error, so that all information, both from the users and collected context information is presented in a single place.

Conclusion

If I had to name the two most important features in codeRR, it would be the following:

  • The first key features with codeRR is error discovery. Since you do not have to rely on log file scanning and user error reports, but instead get awareness of all exceptions in your system you will have a much better chance of creating software with better quality.

  • Another key feature is the timesaving obtained, error management is reduced to a minimal as there is no need to analyze user error reports for duplicates or to study logfiles looking for the reason of the error.

These two things alone, will lead you well on the journey of software quality improvements and leave you ample time to focus on the most awesome thing ever: Writing new code!

What to do next

So, what do you think? Would codeRR be able to help improve your application quality? Are you missing any important features? Kudos for any kind of feedback in the comment section.

Want to get started?

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