Avatar

Blog (pg. 10)

  • Published on
    This is an amalgmation of techniques taken from various sources on how best to handle exceptions/errors in ASP.NET MVC. There are many ways to approach this, but this is one that I'm happy with at the moment, it is based on the premise that the application doesn't swallow up exceptions in try catch blocks and silently fail, instead the error is logged and then reported to the user so they can make a decision as to whether they can fix the issue/try again to resolve (e.g. for validation exceptions). Firstly, override the "Application_Error" in the global.asax so that any unhandled exceptions are caught and handled in a nicer way than the "yellow screen of death" provided by ASP.NET.
    
    /// <summary>
    /// http://www.codeproject.com/Articles/422572/Exception-Handling-in-ASP-NET-MVC#handleerror
    /// http://prideparrot.com/blog/archive/2012/5/exception_handling_in_asp_net_mvc#elmah
    /// http://stackoverflow.com/questions/5226791/custom-error-pages-on-asp-net-mvc3
    /// </summary>
    /// <param name="sender">Sender</param>
    /// <param name="e">Args</param>
    protected void Application_Error(object sender, EventArgs e)
    {
        var httpContext = ((MvcApplication)sender).Context;
    
        var currentRouteData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(httpContext));
        var currentController = " ";
        var currentAction = " ";
    
        if (currentRouteData != null)
        {
            if (currentRouteData.Values["controller"] != null && !string.IsNullOrEmpty(currentRouteData.Values["controller"].ToString()))
            {
                currentController = currentRouteData.Values["controller"].ToString();
            }
    
            if (currentRouteData.Values["action"] != null && !string.IsNullOrEmpty(currentRouteData.Values["action"].ToString()))
            {
                currentAction = currentRouteData.Values["action"].ToString();
            }
        }
    
        var ex = this.Server.GetLastError();
        Trace.TraceError(ex.MessageEx());
    
        var routeData = new RouteData();
        var controller = new ErrorController();
        var action = "Index";
        int httpStatusCode = (int)HttpStatusCode.InternalServerError;
    
        if (ex is HttpException)
        {
            var httpEx = ex as HttpException;
    
            switch (httpEx.GetHttpCode())
            {
                case 404:
                    action = "Http404";
                    break;
    
                // define additional http code error pages here
                default:
                    action = "Index";
                    break;
            }
        }
    
        httpContext.ClearError();
        httpContext.Response.Clear();
        httpContext.Response.StatusCode = ex is HttpException ? ((HttpException)ex).GetHttpCode() : httpStatusCode;
        httpContext.Response.TrySkipIisCustomErrors = true;
        httpContext.Response.Headers.Add("Content-Type", "text/html");
        routeData.Values["controller"] = "Error";
        routeData.Values["action"] = action;
    
        controller.ViewData.Model = new HandleErrorInfo(ex, currentController, currentAction);
        ((IController)controller).Execute(new RequestContext(new HttpContextWrapper(httpContext), routeData));
    }
    
    This works by interpreting the exception, logging the error to the trace listeners and then redirecting the users to an "/Error/Index" action (or 404). The content of the error page can be something like this:
    
    @model HandleErrorInfo
    
    @{
        ViewBag.Title = "Error";
    }
    
    <h1>Error</h1>
    
    <div class="alert alert-danger" role="alert">
        <p><i class="fa fa-warning"></i> Sorry, an error occurred whilst processing your request.</p>
        <p>@Model.Exception.Message</p>
    </div>
    
    That will take care of problems in "normal" page loads and postbacks, i.e. non-async calls. For async calls this is handled slightly differently. Create an action filter that will check for exceptions and wrap the message inside a JsonResponse object (see http://www.dotnetcurry.com/ShowArticle.aspx?ID=496 ):
    
    public class HandleJsonExceptionAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            if (filterContext.HttpContext.Request.IsAjaxRequest() && filterContext.Exception != null)
            {
                filterContext.HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
                filterContext.Result = new JsonResult()
                {
                    JsonRequestBehavior = JsonRequestBehavior.AllowGet,
                    Data = new
                    {
                        Message = filterContext.Exception.MessageEx()
                    }
                };
    
                Trace.TraceError(filterContext.Exception.MessageEx());
                filterContext.ExceptionHandled = true;
            }
        }
    }
    
    Now decorate the actions you will be calling asynchronously with this attribute, usually these are the actions that return a JsonResult or a Partial View. For a consistent theme, you can return the same response "Message" for your WebAPI calls by applying the following exception filter:
    
    public class HandleExceptionAttribute : ExceptionFilterAttribute, IFilter
    {
        public override void OnException(HttpActionExecutedContext actionExecutedContext)
        {
            actionExecutedContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.InternalServerError);
    
            actionExecutedContext.Response.Content = new StringContent(
                JObject.Parse(
                        $"{{'Message': '{actionExecutedContext.Exception.MessageEx().Replace("'", "\"")}'}}"
            ).ToString(), Encoding.UTF8, "application/json");
    
            Trace.TraceError(actionExecutedContext.Exception.MessageEx() + "Stack:" + actionExecutedContext.Exception.StackTrace);
        }
    }
    
    
    // --- Register this in the WebApiConfig "Register" section..
    
    config.Filters.Add(new HandleExceptionAttribute());
    
    Noting that WebAPI doesn't use the global.asax error handler (since there are no views). Now if you are calling the MVC action or webAPI endpoint from a jQuery style $.Ajax request, then this response object can be read in the fail block of that code structure:
    
    $.ajax({
        url: '/Controller/Action',
        method: "POST",
        data: {
            'example': data
        }
    }).done(function (result) {            
            // process a good result
    }).fail(function (errorResult) {
        alert("Could not perform the operation\n" + errorResult.responseJSON.Message);
    });
    
    If you are using the Ajax.BeginForm approach, you can handle the failure condition by referencing your handler function in the "OnFailure" attribute of "AjaxOptions", (see http://johnculviner.com/ajax-beginform-ajaxoptions-custom-arguments-for-oncomplete-onsuccess-onfailure-and-onbegin/)
    
    Ajax.BeginForm(
            "Controller",
            "Action",
            FormMethod.Post,
            new AjaxOptions()
            {
                HttpMethod = "POST",
                InsertionMode = InsertionMode.Replace,
                UpdateTargetId = "",
                OnSuccess = "HandleGoodResult();",
                OnFailure = "HandleFailure(xhr);"
            }, 
            new { id = "myForm" }))
    
    Finally, you may be calling the API from some c# web client code, so you can parse the error as follows:
    
    public string ParseErrorResponse(string responseText)
    {
           string errorText = responseText;
    
           try
           {
               dynamic errorJson = (dynamic)JToken.Parse(errorText);
               errorText = errorJson.Message;
           }
           catch (Exception)
           {
           }
    
           return errorText;
    }
    
    Note: Remember that the exceptions you allow to arrive to user's browser should be legible and meaningul. Also, some exceptions can be "survived" so no need to bubble them, and finally exceptions in general are costly and so should be avoided in the first place (e.g. add clientside validation, add checks before calling dependencies that throw exceptions). For information on the "MessageEx()" extension, see my other post. NOTE: If you are deploying this website to Azure you will need to add the following tag to your system.webServer node of your web.config
    
    <httpErrors existingResponse="PassThrough"/>
    
  • Published on
    There is nothing more annoying than tracing an exception that only says "see inner exception for details". For this reason, I have started to use this simple extension method which traverses the entire exception structure, concatenating the messages together:
    
    internal static class ExceptionExtensions
        {
            public static string MessageEx(this Exception ex)
            {
                StringBuilder errorMessageBuilder = new StringBuilder();
    
                Exception exception = ex;
                while (exception != null)
                {
                    errorMessageBuilder.AppendLine(exception.Message);
                    exception = exception.InnerException;
                }
    
                return errorMessageBuilder.ToString();
            }
        }
    
  • Published on
    I was working with some legacy Silverlight code, to add the functionality of saving the current state to a file and re-opening it later. The way that I chose to implement this was simply using the DataContractSerializer to turn the viewmodel state into a string and write it to a file. I marked the viewmodels with the DataContract attribute and marked any user related properties as DataMember. This worked great for saving the state to a file, and to some extent it worked when re-hyrating the state back to a viewmodel instance. However the problem I had, is that the viewmodel instances that are created when the application is initialised are part of a web of events registrations and dependancies between other viewmodels in the app. It was going to be a pain to re-wire the newly hydrated instances into all the places where the "initialised" viewmodels already were. So my solution was to simply "partially re-hydrate" the already existing viewmodel instances. To acheive this with minimal on-going maintenance, I wrote a simple generic class that uses reflection to identify all the DataMember properties of a class and copies the values from one instance to another. I could then re-hydrate an instance from the file and from that partially rehydrate the plumbed in existing instance. Class below:
    
    using System.Runtime.Serialization;
    
    namespace Project.Silverlight.Helpers
    {
        public class PartialRehydrator<T>
        {
            public void Rehydrate(T source, T target)
            {
                // loop through each property of the given type
                foreach (var prop in typeof(T).GetProperties())
                {
                    // look for the DataMemberAttribute on this property
                    var dma = prop.GetCustomAttributes(typeof(DataMemberAttribute), true);
    
                    // if it has a "DMA" then it's in scope for hydration
                    if (dma != null && dma.Length > 0)
                    {
                        // get the value from the source
                        var sourceValue = prop.GetGetMethod().Invoke(source, null);
    
                        // copy the value to the target
                        prop.GetSetMethod().Invoke(target, new object[] { sourceValue });
                    }
                }
            }
        }
    }
    
  • Published on
    I had a requirement to host the content of one responsive web application inside a view of another responsive web application. Since both applications were responsive in design there was no definitive height, width or aspect ratio. I found a tutorial here: https://benmarshall.me/responsive-iframes/ which talks about a library called Pym.js and also has some css/js examples of a simpler approach. I decided that using Pym.js was overkill for my needs, mostly because I didn't want to make any code changes to the target site. The code I found on the above link also didn't quite work for my needs, since I didn't have a particular aspect ratio (it will depend on the viewing device) and also couldn't give a hard-coded initial width/height to the iframe. In my case, the aspect ratio is actually dependant on the viewing devices screen, therefore I changed the script slightly as below:
    
    $(document).ready(function () {
                var $responsive_iframes = $(".responsive_iframe");
    
                // Resize the iframes when the window is resized
                $(window).resize(function () {
                    $responsive_iframes.each(function () {
                        // Get the parent containe's width and the screen ratio
                        var width = $(this).parent().width();
                        var ratio = $(window).height() / $(window).width();
    
                        $(this).width(width)
                              .height(width * ratio);
                    });
                    // Resize to fix all iframes on page load.
                }).resize();
            });
    
    (I also made the jQuery selector look for a class name, rather than all iframes)
  • Published on
    Shared projects are a relatively new thing in Visual Studio, they allow you to share code across multiple assemblies without compiling it in its own dll (the code is compiled into the referencing projects dll). Today I came across a bit of a gotcha - If you reference a shared project in an assembly A and in assembly B, now both of those projects define and export all of the public code from the shared project. Now you have a 3rd project C that also has a reference to the shared project, but also references A and B. You will get a compiler error "call is ambiguous" since the namespace/class/method you are trying to use is now defined 3 times. The simple answer, which is counter intuitive to "normal" library development is to make all of your "shared project" classes "internal" instead of "public". That way each assembly that references the shared project can use the code internally but will not export it to other assemblies.