mvc自定义全局异常处理

  异常信息处理是任何网站必不可少的一个环节,怎么有效显示,记录,传递异常信息又成为重中之重的问题。本篇将基于上篇介绍的html2cancas截图功能,实现mvc自定义全局异常处理。先看一下最终实现效果:http://yanweidie.myscloud.cn/Home/Index

阅读目录

  • 我理解中好的异常处理
  • 自定义异常处理
  • 问题拓展
  • 总结

回到顶部

我理解中好的异常处理

    好的异常信息处理应该具有以下几个优点

  • 显示效果佳,而不是原生黄页
  • 能够从异常中直接分析出异常源
  • 能够记录传递异常信息给开发人员

1.第一点显示效果方面可以自定义页面,常见的包括404和500状态码页面。在mvc中404页面可以通过以下两种方式进行自定义

<system.web>
<!--添加customErrors节点 定义404跳转页面-->
 <customErrors mode="On">
      <error statusCode="404" redirect="/Error/Path404" />
    </customErrors>
 </system.web>
//Global文件的EndRequest监听Response状态码
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.第二点 异常信息应该详细,能够记录下请求参数,请求地址,浏览器版本服务器和当前用户等相关信息,这就需要对异常信息记录改造加工

3.第三点 常见的异常信息都是记录在日志文件里面,日志文件过大时也不太好分析。发生异常时要是能马上将异常信息通过邮件或者图片等方式发给开发者,可以加快分析速度。

回到顶部

自定义异常处理

  

  这里采用mvc的过滤器进行异常处理,分别为接口500错误和页面500错误进行处理,接口部分异常需要记录请求参数,方便分析异常。

首先定义了异常信息实体,异常实体包含了 请求地址类型(页面,接口),服务器相关信息(位数,CPU,操作系统,iis版本),客户端信息(UserAgent,HttpMethod,IP)

   异常实体代码如下

    /// <summary>
    /// 系统错误信息
    /// </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") + "位";
            OSVersion = Environment.OSVersion.ToString();
            CPUCount = Environment.GetEnvironmentVariable("NUMBER_OF_PROCESSORS");
            CPUType = Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER");
            OSBit = (Environment.Is64BitOperatingSystem ? "64" : "32") + "位";

            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>
        /// 消息类型
        /// </summary>
        public string MsgType { get; set; }

        /// <summary>
        /// 消息内容
        /// </summary>
        public string Message { get; set; }

        /// <summary>
        /// 请求路径
        /// </summary>
        public string Path { get; set; }

        /// <summary>
        /// 程序集名称
        /// </summary>
        public string Assembly { get; set; }

        /// <summary>
        /// 异常参数
        /// </summary>
        public string ActionArguments { get; set; }

        /// <summary>
        /// 请求类型
        /// </summary>
        public string HttpMethod { get; set; }

        /// <summary>
        /// 异常堆栈
        /// </summary>
        public string StackTrace { get; set; }

        /// <summary>
        /// 异常源
        /// </summary>
        public string Source { get; set; }

        /// <summary>
        /// 服务器IP 端口
        /// </summary>
        public string IP { get; set; }

        /// <summary>
        /// 客户端浏览器标识
        /// </summary>
        public string UserAgent { get; set; }

        /// <summary>
        /// .NET解释引擎版本
        /// </summary>
        public string DotNetVersion { get; set; }

        /// <summary>
        ///  应用程序池位数
        /// </summary>
        public string DotNetBit { get; set; }

        /// <summary>
        /// 操作系统类型
        /// </summary>
        public string OSVersion { get; set; }

        /// <summary>
        /// 操作系统位数
        /// </summary>
        public string OSBit { get; set; }

        /// <summary>
        /// CPU个数
        /// </summary>
        public string CPUCount { get; set; }

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

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

        /// <summary>
        /// 请求地址类型
        /// </summary>
        public string Type { get; set; }

        /// <summary>
        /// 是否显示异常界面
        /// </summary>
        public bool ShowException { get; set; }

        /// <summary>
        /// 异常发生时间
        /// </summary>
        public string Time { get; set; }

        /// <summary>
        /// 异常发生方法
        /// </summary>
        public string Method { get; set; }

        //这段代码用户请求真实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"))
                {
                    // 根据网卡取本机配置的IP
                    IPAddress[] AddressList = Dns.GetHostEntry(Dns.GetHostName()).AddressList;
                    foreach (IPAddress _IPAddress in AddressList)
                    {
                        if (_IPAddress.AddressFamily.ToString() == "InterNetwork")
                        {
                            ipAddress = _IPAddress.ToString();
                            break;
                        }
                    }
                }
            }
            // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照‘,‘分割
            if (ipAddress != null && ipAddress.Length > 15)
            {
                if (ipAddress.IndexOf(",") > 0)
                {
                    ipAddress = ipAddress.Substring(0, ipAddress.IndexOf(","));
                }
            }
            return ipAddress;
        }

        /// <summary>
        /// 是否有效IP地址
        /// </summary>
        /// <param name="ipAddress">IP地址</param>
        /// <returns>bool</returns>
        private static bool IsEffectiveIP(string ipAddress)
        {
            return !(string.IsNullOrEmpty(ipAddress) || "unknown".Equals(ipAddress, StringComparison.OrdinalIgnoreCase));
        }
    }

上面代码中用到了获取客户端请求IP的方法,用于获取请求来源的真实IP。

基础异常信息定义完后,剩下的是异常记录和页面跳转了,mvc中的异常过滤器实现如下。

 /// <summary>
    /// 全局页面控制器异常记录
    /// </summary>
    public class CustomErrorAttribute : HandleErrorAttribute
    {
        public override void OnException(ExceptionContext filterContext)
        {
            base.OnException(filterContext);

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

            //错误记录
            LogHelper.WriteLog(JsonConvert.SerializeObject(msg, Formatting.Indented), null);

            //设置为true阻止golbal里面的错误执行
            filterContext.ExceptionHandled = true;
            filterContext.Result = new ViewResult() { ViewName = "/Views/Error/ISE.cshtml", ViewData = new ViewDataDictionary<ErrorMessage>(msg) };

        }
    }

    /// <summary>
    /// 全局API异常记录
    /// </summary>
    public class ApiHandleErrorAttribute : ExceptionFilterAttribute
    {
        public override void OnException(HttpActionExecutedContext filterContext)
        {
            base.OnException(filterContext);

            //异常信息
            ErrorMessage msg = new ErrorMessage(filterContext.Exception, "接口");
            //接口调用参数
            msg.ActionArguments = JsonConvert.SerializeObject(filterContext.ActionContext.ActionArguments, Formatting.Indented);
            msg.ShowException = MvcException.IsExceptionEnabled();

            //错误记录
            string exMsg = JsonConvert.SerializeObject(msg, Formatting.Indented);
            LogHelper.WriteLog(exMsg, null);

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

    /// <summary>
    /// 异常信息显示
    /// </summary>
    public class MvcException
    {
        /// <summary>
        /// 是否已经获取的允许显示异常
        /// </summary>
        private static bool HasGetExceptionEnabled = false;

        private static bool isExceptionEnabled;

        /// <summary>
        /// 是否显示异常信息
        /// </summary>
        /// <returns>是否显示异常信息</returns>
        public static bool IsExceptionEnabled()
        {
            if (!HasGetExceptionEnabled)
            {
                isExceptionEnabled = GetExceptionEnabled();
                HasGetExceptionEnabled = true;
            }
            return isExceptionEnabled;
        }

        /// <summary>
        /// 根据Web.config AppSettings节点下的ExceptionEnabled值来决定是否显示异常信息
        /// </summary>
        /// <returns></returns>
        private static bool GetExceptionEnabled()
        {
            bool result;
            if(!Boolean.TryParse(ConfigurationManager.AppSettings["ExceptionEnabled"],out result))
            {
                return false;
            }
            return result;
        }
    }

值得注意的是上面的MvcException类的GetExceptionEnabled方法,该方法从web.config appsetting中读取节点"ExceptionEnabled"来控制异常信息是否初始化显示。异常信息除了显示在页面,还使用了log4net组件记录在错误日志中,方便留痕。

过滤器定义完成后,需要在filterconfig添加引用

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

回到顶部

问题拓展

  后台异常处理代码完成以后,前台还需进行相应的处理。这里主要针对api接口,因为请求页面后台可以直接转向500错误页面,而api接口一般是通过ajax或者客户端httpclient请求的,如果错误了跳转到500页面,这样对客户端来说就不友好了。基于这点所以api请求异常返回了异常的详细json对象,让客户端自己进行异常处理。我这里给出ajax处理异常的方式。

在jquery中全局ajax请求可以设置相应默认参数,比如下面代码设置了全局ajax请求为异步请求,不缓存

//ajax请求全局设置
$.ajaxSetup({
    //异步请求
    async: true,
    //缓存设置
    cache: false
});

ajax请求完成会触发Complete事件,在jquery中全局Complete事件可以通过下面代码监听

$(document).ajaxComplete(function (evt, request, settings) {
    var text = request.responseText;
    if (text) {
        try {
            //Unauthorized  登录超时或者无权限
            if (request.status == "401") {
                var json = $.parseJSON(text);
                if (json.Message == "logout") {
                    //登录超时,弹出系统登录框
                } else {
                    layer.alert(json.ExceptionMessage ? json.ExceptionMessage : "系统异常,请联系系统管理员", {
                        title: "错误提醒",
                        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) {
                        //页面层
                        layer.open({
                            title: ‘异常信息‘,
                            type: 1,
                            shade: 0.8,
                            shift: -1,
                            area: [‘100%‘, ‘100%‘],
                            content: data,
                        });
                    }
                });

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

红色部分代码就是我用来处理500错误的代码,重新发请求到异常显示界面渲染成html后显示。其实这么做无疑增加了一次请求,最好的实现方式,直接通过异常信息json,通过js绘制出html。至此完成了mvc全局的页面,接口异常信息处理。通过结合上面的前端截图插件,快速截图留证,方便后续程序员分析异常信息。

回到顶部

总结

  通过一点小小的改造,我们完成了一个既美观又方便拓展的错误处理方式。看到上面萌萌的图片你是否心动了,想马上下载代码体验一把呢。下面就给出本文所有的源代码:

svn代码浏览:http://code.taobao.org/p/MyCustomGlobalError/src/trunk         svn工具代码checkout地址:http://code.taobao.org/svn/MyCustomGlobalErro

预告一下,下一篇将会对之前的TaskManager管理平台进行升级,主要实现管理界面方便查看当前运行的所有任务和管理任务。讲解管理平台运用到的技术,敬请期待!

如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】按钮。
如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的【关注我】。
因为,我的写作热情也离不开您的肯定支持。

感谢您的阅读,如果您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是焰尾迭 。

时间: 2024-11-05 16:04:09

mvc自定义全局异常处理的相关文章

Spring MVC自定义统一异常处理类,并且在控制台中输出错误日志

在使用SimpleMappingExceptionResolver实现统一异常处理后(参考Spring MVC的异常统一处理方法), 发现出现异常时,log4j无法在控制台输出错误日志.因此需要自定义一个继承至SimpleMappingExceptionResolver的 RrtongMappingExceptionResolver类,在RrtongMappingExceptionResolver中通过 log.error(ex.getMessage())的方式输出日志到控制台上.以下是具体的配

asp.net mvc 自定义全局过滤器 验证用户是否登录

一般具有用户模块的系统都需要对用户是否登录进行验证,如果用户登录了就可以继续操作,否则退回用户的登录页面 对于这样的需求我们可以通过自定义一个独立的方法来完成验证的操作,但是这样代码的重复率就大大提高了 对于这样的需求,有一个比较好的解决方案,通过自定义一个全局的过滤器来完成这个操作 这里我们需要实现AuthorizeAttribute特性来完成对用户身份的验证 首先给出自定义的类,通过这个类来实现对用户身份的判断,通过重写HandleUnauthorizedRequest函数 来完成用户验证失

ASP.NET MVC自定义异常处理

1.自定义异常处理过滤器类文件 新建MyExceptionAttribute.cs异常处理类文件 MyExceptionAttribute.cs代码如下: using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace WebApp.Models { public class MyExceptionAttribute : Han

MVC 全局异常处理及禁用显示头

MVC网站的global.asax中的Application_Start方法里,有这样一段代码: 1 public class MvcApplication : System.Web.HttpApplication 2 { 3 protected void Application_Start() 4 { 5 AreaRegistration.RegisterAllAreas(); 6 FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters

学一学MVC的过滤器--异常处理

学习MVC不久,看到公司的项目里有日志记录功能,出于自己的原因一直没有看看是如何实现的,后来看了个学习视频和一些博文,才发现采用MVC里的过滤器 Filter 可以很容易实现,比较常见也很容易的做法就是继承  IActionFilter,IExceptionFilter 接口,然后实现里面的方法即可. 下面看一下代码,这是公司项目的一段代码,主要是将项目中出现的异常记录在 每天的txt日志文件里.当然实现日志的方法有很多种,这个只是其中一种,如果出现了IO错误,这种方法就不好说啦.  publi

SpringMVC 全局异常处理

在 JavaEE 项目的开发中,不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的.不可预知的异常需要处理.每个过程都单独处理异常,系统的代码耦合度高,工作量大且不好统一,维护的工作量也很大.SpringMvc 对于异常处理这块提供了支持,通过 SpringMvc 提供的全局异常处理机制,能够将所有类型的异常处从各处理过程解耦出来,这样既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护. 全局异常实现方式 Spring MVC

springboot 全局异常处理

springboot 全局异常处理 研究了半天springboot的全局异常处理,虽然还是需要再多整理一下,但是对于常见的404和500足以可以区分开,能够根据这两个异常分别处理 首先配置视图解析路径 spring.mvc.view.prefix=/WEB-INF/ spring.mvc.view.suffix=.jsp 针对500错误 创建单独的配置类,放在启动类同包或子包下 import org.springframework.web.bind.annotation.ControllerAd

zbb20180921 springboot 全局异常处理 404 500

研究了半天springboot的全局异常处理,虽然还是需要再多整理一下,但是对于常见的404和500足以可以区分开,能够根据这两个异常分别处理 首先配置视图解析路径 spring.mvc.view.prefix=/WEB-INF/ spring.mvc.view.suffix=.jsp 针对500错误 创建单独的配置类,放在启动类同包或子包下 import org.springframework.web.bind.annotation.ControllerAdvice; import org.s

Spring Boot 2 Webflux的全局异常处理

https://www.jianshu.com/p/6f631f3e00b9 本文首先将会回顾Spring 5之前的SpringMVC异常处理机制,然后主要讲解Spring Boot 2 Webflux的全局异常处理机制. SpringMVC的异常处理 Spring 统一异常处理有 3 种方式,分别为: 使用 @ExceptionHandler 注解 实现 HandlerExceptionResolver 接口 使用 @controlleradvice 注解 使用@ExceptionHandle