mvc custom global exception handling

mvc custom global exception handling

  The handling of abnormal information is an indispensable part of any website. How to effectively display, record, and transmit abnormal information has become the most important issue. This article will implement mvc custom global exception handling based on the html2cancas screenshot function introduced in the previous article . Let’s take a look at the final result: http://yanweidie.myscloud.cn/Home/Index

Read the table of contents

back to the top

I understand good exception handling

  Good exception information processing should have the following advantages

  • Good display, not native yellow pages
  • Ability to directly analyze the source of the anomaly from the anomaly
  • Ability to record and deliver abnormal information to developers

     1. The first point is that you can customize the page in terms of display effects, including 404 and 500 status code pages. The 404 page in mvc can be customized in the following two ways

<system.web>
<!--Add customErrors node to define 404 jump page-->
 <customErrors mode="On">
      <error statusCode="404" redirect="/Error/Path404"/>
    </customErrors>
 </system.web>
//EndRequest monitoring Response status code of Global file
protected void Application_EndRequest()
{
  var statusCode = Context.Response.StatusCode;
    var routingData = Context.Request.RequestContext.RouteData;
    if (statusCode == 404 || statusCode == 500)
    {
      Response.Clear();
       Response.RedirectToRoute("Default", new {controller = "Error", action = "Path404" });
    }
}     

      2. The second point of abnormal information should be detailed, capable of recording the request parameters, request address, browser version server and current user and other related information, which requires modification and processing of the abnormal information record

      3. The third common abnormal information is recorded in the log file, and it is not easy to analyze when the log file is too large. When an exception occurs, if the exception information can be immediately sent to the developer via email or pictures, the analysis speed can be speeded up.

back to the top

Custom exception handling

  Here, the mvc filter is used to handle exceptions, which are respectively for interface 500 errors and page 500 errors. Part of the interface exceptions need to record request parameters to facilitate the analysis of the exceptions.

     1. the exception information entity is defined. The exception entity includes the request address type (page, interface), server-related information (number of bits, CPU, operating system, iis version), and client information (UserAgent, HttpMethod, IP)

   The exception entity code is as follows

   ///<summary>
   ///System error message
   ///</summary>
    public class ErrorMessage
    {
        public ErrorMessage()
        {

        }
        public ErrorMessage(Exception ex,string type)
        {
            MsgType = ex.GetType().Name;
            Message = ex.InnerException != null? Ex.InnerException.Message: ex.Message;
            StackTrace = ex.StackTrace;
            Source = ex.Source;
            Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
            Assembly = ex.TargetSite.Module.Assembly.FullName;
            Method = ex.TargetSite.Name;
            Type = type;

            DotNetVersion = Environment.Version.Major + "." + Environment.Version.Minor + "." + Environment.Version.Build + "." + Environment.Version.Revision;
            DotNetBit = (Environment.Is64BitProcess? "64": "32") + "bit";
            OSVersion = Environment.OSVersion.ToString();
            CPUCount = Environment.GetEnvironmentVariable("NUMBER_OF_PROCESSORS");
            CPUType = Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER");
            OSBit = (Environment.Is64BitOperatingSystem? "64": "32") + "bit";

            var request = HttpContext.Current.Request;
            IP = GetIpAddr(request) + ":" + request.Url.Port;
            IISVersion = request.ServerVariables["SERVER_SOFTWARE"];
            UserAgent = request.UserAgent;
            Path = request.Path;
            HttpMethod = request.HttpMethod;
        }
       ///<summary>
       ///Message type
       ///</summary>
        public string MsgType {get; set;}

       ///<summary>
       ///Message content
       ///</summary>
        public string Message {get; set;}

       ///<summary>
       ///Request path
       ///</summary>
        public string Path {get; set;}

       ///<summary>
       ///Assembly name
       ///</summary>
        public string Assembly {get; set;}

       ///<summary>
       ///Exception parameter
       ///</summary>
        public string ActionArguments {get; set;}

       ///<summary>
       ///Request type
       ///</summary>
        public string HttpMethod {get; set;}

       ///<summary>
       ///Exception stack
       ///</summary>
        public string StackTrace {get; set;}

       ///<summary>
       ///Exception source
       ///</summary>
        public string Source {get; set;}

       ///<summary>
       ///Server IP port
       ///</summary>
        public string IP {get; set;}

       ///<summary>
       ///Client browser ID
       ///</summary>
        public string UserAgent {get; set;}

       ///<summary>
       ///.NET interpretation engine version
       ///</summary>
        public string DotNetVersion {get; set;}

       ///<summary>
       ///Number of application pools
       ///</summary>
        public string DotNetBit {get; set;}


       ///<summary>
       ///Operating system type
       ///</summary>
        public string OSVersion {get; set;}

       ///<summary>
       ///Operating system bits
       ///</summary>
        public string OSBit {get; set;}

       ///<summary>
       ///Number of CPUs
       ///</summary>
        public string CPUCount {get; set;}

       ///<summary>
       ///CPU type
       ///</summary>
        public string CPUType {get; set;}

       ///<summary>
       ///IIS version
       ///</summary>
        public string IISVersion {get; set;}

       ///<summary>
       ///Request address type
       ///</summary>
        public string Type {get; set;}

       ///<summary>
       ///Whether to display the abnormal interface
       ///</summary>
        public bool ShowException {get; set;}

       ///<summary>
       ///The time when the exception occurred
       ///</summary>
        public string Time {get; set;}

       ///<summary>
       ///Method of exception occurrence
       ///</summary>
        public string Method {get; set;}

       //This code user requests real IP
        private static string GetIpAddr(HttpRequest request)
        {
           //HTTP_X_FORWARDED_FOR
            string ipAddress = request.ServerVariables["x-forwarded-for"];
            if (!IsEffectiveIP(ipAddress))
            {
                ipAddress = request.ServerVariables["Proxy-Client-IP"];
            }
            if (!IsEffectiveIP(ipAddress))
            {
                ipAddress = request.ServerVariables["WL-Proxy-Client-IP"];
            }
            if (!IsEffectiveIP(ipAddress))
            {
                ipAddress = request.ServerVariables["Remote_Addr"];
                if (ipAddress.Equals("127.0.0.1") || ipAddress.Equals("::1"))
                {
                   //Take the IP configured by the machine according to the network card
                    IPAddress[] AddressList = Dns.GetHostEntry(Dns.GetHostName()).AddressList;
                    foreach (IPAddress _IPAddress in AddressList)
                    {
                        if (_IPAddress.AddressFamily.ToString() == "InterNetwork")
                        {
                            ipAddress = _IPAddress.ToString();
                            break;
                        }
                    }
                }
            }
           //In the case of multiple agents, the first IP is the real IP of the client, and multiple IPs are divided according to','
            if (ipAddress != null && ipAddress.Length> 15)
            {
                if (ipAddress.IndexOf(",")> 0)
                {
                    ipAddress = ipAddress.Substring(0, ipAddress.IndexOf(","));
                }
            }
            return ipAddress;
        }

       ///<summary>
       ///Is a valid IP address
       ///</summary>
       ///<param name="ipAddress">IP address</param>
       ///<returns>bool</returns>
        private static bool IsEffectiveIP(string ipAddress)
        {
            return !(string.IsNullOrEmpty(ipAddress) || "unknown".Equals(ipAddress, StringComparison.OrdinalIgnoreCase));
        }
    }

The method of obtaining the client request IP is used in the above code to obtain the real IP of the request source.

After the basic exception information is defined, what remains is the exception record and page jump. The exception filter in mvc is implemented as follows.

///<summary>
   ///Global page controller exception record
   ///</summary>
    public class CustomErrorAttribute: HandleErrorAttribute
    {
        public override void OnException(ExceptionContext filterContext)
        {
            base.OnException(filterContext);

            ErrorMessage msg = new ErrorMessage(filterContext.Exception, "page");
            msg.ShowException = MvcException.IsExceptionEnabled();

           //Error record
            LogHelper.WriteLog(JsonConvert.SerializeObject(msg, Formatting.Indented), null);

           //Set to true to prevent wrong execution in golbal
            filterContext.ExceptionHandled = true;
            filterContext.Result = new ViewResult() {ViewName = "/Views/Error/ISE.cshtml", ViewData = new ViewDataDictionary<ErrorMessage>(msg) };

        }
    }

   ///<summary>
   ///Global API exception record
   ///</summary>
    public class ApiHandleErrorAttribute: ExceptionFilterAttribute
    {
        public override void OnException(HttpActionExecutedContext filterContext)
        {
            base.OnException(filterContext);

           //Exception information
            ErrorMessage msg = new ErrorMessage(filterContext.Exception, "Interface");
           //Interface call parameters
            msg.ActionArguments = JsonConvert.SerializeObject(filterContext.ActionContext.ActionArguments, Formatting.Indented);
            msg.ShowException = MvcException.IsExceptionEnabled();

           //Error record
            string exMsg = JsonConvert.SerializeObject(msg, Formatting.Indented);
            LogHelper.WriteLog(exMsg, null);

            filterContext.Response = new HttpResponseMessage() {StatusCode = HttpStatusCode.InternalServerError, Content = new StringContent(exMsg) };
        }
    }

   ///<summary>
   ///Exception information display
   ///</summary>
    public class MvcException
    {
       ///<summary>
       ///Is it allowed to display exceptions?
       ///</summary>
        private static bool HasGetExceptionEnabled = false;

        private static bool isExceptionEnabled;

       ///<summary>
       ///Whether to display abnormal information
       ///</summary>
       ///<returns>Whether to display exception information</returns>
        public static bool IsExceptionEnabled()
        {
            if (!HasGetExceptionEnabled)
            {
                isExceptionEnabled = GetExceptionEnabled();
                HasGetExceptionEnabled = true;
            }
            return isExceptionEnabled;
        }

       ///<summary>
       ///Determine whether to display exception information according to the ExceptionEnabled value under the Web.config AppSettings node
       ///</summary>
       ///<returns></returns>
        private static bool GetExceptionEnabled()
        {
            bool result;
            if(!Boolean.TryParse(ConfigurationManager.AppSettings["ExceptionEnabled"],out result))
            {
                return false;
            }
            return result;
        }
    }

It is worth noting that the GetExceptionEnabled method of the above MvcException class, this method reads the node "ExceptionEnabled" from the web.config appsetting to control whether the exception information is initialized and displayed. In addition to displaying the abnormal information on the page, the log4net component is also used to record in the error log for easy traces.

After the filter definition is complete, you need to add a reference in filterconfig

    public class FilterConfig
    {
        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new CustomErrorAttribute());
            filters.Add(new HandleErrorAttribute());
        }
    }

back to the top

Problem expansion

  After the background exception handling code is completed, the foreground needs to be processed accordingly. This is mainly for the api interface, because the background of the request page can be directly redirected to the 500 error page, and the api interface is generally requested through ajax or client httpclient. If an error occurs, it will jump to the 500 page, which is not friendly to the client . Based on this, the api request exception returns the detailed json object of the exception, allowing the client to handle the exception by itself. I am here to give ajax how to handle exceptions.

     In jquery, the global ajax request can be set to the corresponding default parameters. For example, the following code sets the global ajax request as an asynchronous request without caching

//ajax request global settings
$.ajaxSetup({
   //Asynchronous request
    async: true,
   //Cache settings
    cache: false
});

    The completion of ajax request will trigger the Complete event, the global Complete event in jquery can be monitored by the following code

$(document).ajaxComplete(function (evt, request, settings) {
    var text = request.responseText;
    if (text) {
        try {
           //Unauthorized login timeout or no permission
            if (request.status == "401") {
                var json = $.parseJSON(text);
                if (json.Message == "logout") {
                   //Login timeout, system login box pops up
                } else {
                    layer.alrt(json.ExceptionMessage? json.ExceptionMessage: "System exception, please contact the system administrator", {
                        title: "Error reminder",
                        icon: 2
                    });
                }
            } else if (request.status == "500") {
                var json = $.parseJSON(text);
                $.ajax({
                    type: "post",
                    url: "/Error/Path500",
                    data: {"": json },
                    data: json,
                    dataType: "html",
                    success: function (data) {
                       //Page layer
                        layer.open({
                            title:'Exception Information',
                            type: 1,
                            shade: 0.8,
                            shift: -1,
                            area: ['100%', '100%'],
                            content: data,
                        });
                    }
                });

            }
        } catch (e) {
            console.log(e);
        }
    }
});

The code in red is the code I used to handle the 500 error. Re-send the request to the abnormal display interface and render it into html. In fact, this undoubtedly adds a request. The best way to achieve this is to draw the html directly through the exception information json and js. At this point, the MVC global page and interface exception information processing have been completed. By combining the above front-end screenshot plug-in, you can quickly take a screenshot and retain it, which is convenient for subsequent programmers to analyze abnormal information.

back to the top

summary

  With a little modification, we have completed an error handling method that is both beautiful and easy to expand. Seeing the cute pictures above, do you feel moved? I want to download the code and experience it right away. Here is all the source code of this article:

     svn code browsing: http://code.taobao.org/p/MyCustomGlobalError/src/trunk          svn tool code checkout address: http://code.taobao.org/svn/MyCustomGlobalErro

      As a preview, the next article will upgrade the previous TaskManager management platform, mainly to realize the management interface to facilitate viewing all currently running tasks and management tasks. Explain the technology used in the management platform, so stay tuned!

Reference: https://cloud.tencent.com/developer/article/1014510 mvc custom global exception handling-Cloud + Community-Tencent Cloud