为了账号安全,请及时绑定邮箱和手机立即绑定

mvc自定义全局异常处理

标签:
C#

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

  • 显示效果佳,而不是原生黄页

  • 能够从异常中直接分析出异常源

  • 能够记录传递异常信息给开发人员

     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全局的页面,接口异常信息处理。通过结合上面的前端截图插件,快速截图留证,方便后续程序员分析异常信息。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消