Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET MVC - Approach for global error handling?

I was wondering what the best implementation for a global error (doesn't have to be errors, can also be success messages) handler would be? Let me break it down for you with an example:

  1. User tries to delete a record
  2. Deletion fails and an error is logged
  3. User redirects to another page
  4. Display error message for user (using a HtmlHelper or something, don't want it to be a specific error page)

I'm just curious what you guys think. I've been considering TempData, ViewData and Session but they all have their pros and cons.

TIA!

UPDATE:

I'll show an example what I exactly mean, maybe I wasn't clear enough. This is an example of a method that adds a message when user deletes a record. If user succeeds, user redirects to another page

public ActionResult DeleteRecord(Record recordToDelete)
{
    // If user succeeds deleting the record
    if (_service.DeleteRecord(recordToDelete) 
    {
        // Add success message
        MessageHandler.AddMessage(Status.SUCCESS, "A message to user");

        // And redirect to list view
        return RedirectToAction("RecordsList");
    }
    else 
    {
        // Else return records details view
        return View("RecordDetails", recordToDelete);
    }
}

And in the view "RecordsList", it would be kinda cool to show all messages (both error and success messages) in a HtmlHelper or something.

<%= Html.RenderAllMessages %>

This can be achieved in many ways, I'm just curious what you guys would do.

UPDATE 2:

I have created a custom error (message) handler. You can see the code if you scroll down.

like image 365
Kristoffer Lundberg Avatar asked Aug 17 '11 21:08

Kristoffer Lundberg


1 Answers

Just for fun, I created my own custom error (message) handler that works pretty much as TempData, but with the small difference that this handler is accessible all over the application.

I'm not going to explain every single step of code, but to sum it all up, I used IHttpModule to fire a method for every request and Session to save data. Below is the code, feel free to edit or give suggestions for improvements.

Web.config (Define module)

<httpModules>
  <add name="ErrorManagerModule" type="ErrorManagerNamespace.ErrorManager"/>
</httpModules>

<system.webServer>
  <modules runAllManagedModulesForAllRequests="true">
    <add name="ErrorManagerModule" type="ErrorManagerNamespace.ErrorManager"/>
  </modules>
</system.webServer>

ErrorManager.cs (Error manager handler code)

public class ErrorManager : IRequiresSessionState, IHttpModule
{
    private const string SessionKey = "ERROR_MANAGER_SESSION_KEY";

    public enum Type 
    {
        None,
        Warning,
        Success,
        Error
    }

    /*
     * 
     * Public methods
     * 
     */

    public void Dispose() 
    {
    }

    public void Init(HttpApplication context) 
    {
        context.AcquireRequestState += new EventHandler(Initiliaze);
    }

    public static IList<ErrorModel> GetErrors(ErrorManager.Type type = Type.None) 
    {
        // Get all errors from session
        var errors = GetErrorData();

        // Destroy Keep alive
        // Decrease all errors request count
        foreach (var error in errors.Where(o => type == ErrorManager.Type.None || o.ErrorType == type).ToList())
        {
            error.KeepAlive = false;
            error.IsRead = true;
        }

        // Save errors to session
        SaveErrorData(errors);

        //return errors;
        return errors.Where(o => type == ErrorManager.Type.None || o.ErrorType == type).ToList();
    }

    public static void Add(ErrorModel error) 
    {
        // Get all errors from session
        var errors = GetErrorData();
        var result = errors.Where(o => o.Key.Equals(error.Key, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();

        // Add error to collection
        error.IsRead = false;

        // Error with key is already associated
        // Remove old error from collection
        if (result != null)
            errors.Remove(result);

        // Add new to collection
        // Save errors to session
        errors.Add(error);
        SaveErrorData(errors);
    }

    public static void Add(string key, object value, ErrorManager.Type type = Type.None, bool keepAlive = false) 
    {
        // Create new error
        Add(new ErrorModel()
        {
            IsRead = false,
            Key = key,
            Value = value,
            KeepAlive = keepAlive,
            ErrorType = type
        });
    }

    public static void Remove(string key) 
    {
        // Get all errors from session
        var errors = GetErrorData();
        var result = errors.Where(o => o.Key.Equals(key, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();

        // Error with key is in collection
        // Remove old error
        if (result != null)
            errors.Remove(result);

        // Save errors to session
        SaveErrorData(errors);
    }

    public static void Clear() 
    {
        // Clear all errors
        HttpContext.Current.Session.Remove(SessionKey);
    }

    /*
     * 
     * Private methods
     * 
     */

    private void Initiliaze(object o, EventArgs e) 
    {
        // Get context
        var context = ((HttpApplication)o).Context;

        // If session is ready
        if (context.Handler is IRequiresSessionState || 
            context.Handler is IReadOnlySessionState)
        {
            // Load all errors from session
            LoadErrorData();
        }
    }

    private static void LoadErrorData() 
    {
        // Get all errors from session
        var errors = GetErrorData().Where(o => !o.IsRead).ToList();

        // If KeepAlive is set to false
        // Mark error as read
        foreach (var error in errors)
        {
            if (error.KeepAlive == false)
                error.IsRead = true;
        }

        // Save errors to session
        SaveErrorData(errors);
    }

    private static void SaveErrorData(IList<ErrorModel> errors) 
    {
        // Make sure to remove any old errors
        HttpContext.Current.Session.Remove(SessionKey);
        HttpContext.Current.Session.Add(SessionKey, errors);
    }

    private static IList<ErrorModel> GetErrorData() 
    {
        // Get all errors from session
        return HttpContext.Current.Session[SessionKey]
            as IList<ErrorModel> ??
            new List<ErrorModel>();
    }

    /*
     * 
     * Model
     * 
     */

    public class ErrorModel 
    {
        public string Key { get; set; }
        public object Value { get; set; }
        public bool KeepAlive { get; set; }
        internal bool IsRead { get; set; }
        public Type ErrorType { get; set; }
    }

HtmlHelperExtension.cs (An extension method for rendering the errors)

public static class HtmlHelperExtension
{
    public static string RenderMessages(this HtmlHelper obj, ErrorManager.Type type = ErrorManager.Type.None, object htmlAttributes = null) 
    {
        var builder = new TagBuilder("ul");
        var errors = ErrorManager.GetErrors(type);

        // If there are no errors
        // Return empty string
        if (errors.Count == 0)
            return string.Empty;

        // Merge html attributes
        builder.MergeAttributes(new RouteValueDictionary(htmlAttributes), true);

        // Loop all errors
        foreach (var error in errors)
        {
            builder.InnerHtml += String.Format("<li class=\"{0}\"><span>{1}</span></li>",
                error.ErrorType.ToString().ToLower(),
                error.Value as string);
        }

        return builder.ToString();
    }
}

Usage for creating errors

// This will only be available for one request
ErrorManager.Add("Key", "An error message", ErrorManager.Type.Error);

// This will be available for multiple requests
// When error is read, it will be removed
ErrorManager.Add("Key", "An error message", ErrorManager.Type.Error, true);

// Remove an error
ErrorManager.Remove("AnotherKey");

// Clear all error
ErrorManager.Clear();

Usage for rendering errors

// This will render all errors
<%= Html.RenderMessages() %>

// This will just render all errors with type "Error"
<%= Html.RenderMessages(ErrorManager.Type.Error) %>
like image 143
Kristoffer Lundberg Avatar answered Oct 17 '22 05:10

Kristoffer Lundberg