RESTful日#6:使用操作过滤器、异常过滤器和NLog在Web api中进行请求日志记录和异常处理/日志记录

表的内容 目录介绍路线图请求日志 在WebAPI中设置NLog 步骤1:下载NLog包步骤2:配置NLog 添加动作过滤器的NLogger类 步骤2:注册动作过滤器(LoggingFilterAttribute) 运行应用程序异常日志记录 实现异常日志 步骤1:异常过滤器属性步骤2:修改NLogger类步骤3:修改异常控制器步骤4:运行应用程序 自定义异常日志 JSon序列化器修改NLogger类修改globalexceptionattribute修改产品控制器运行应用程序更新控制器进行新的异常处理 产品控制器 结论其他系列 介绍 从本系列的最后五篇文章开始,我们已经学习了很多关于WebAPI、它的使用、实现和安全性方面的知识。本系列的这篇文章将解释如何处理请求并对其进行日志记录,以便跟踪,以及如何处理异常并对其进行日志记录。我们将遵循WebAPI中处理异常的集中方式,编写自定义类来映射到我们遇到的异常类型,并相应地记录这些异常。我还将使用NLog来记录请求和异常。我们将利用异常过滤器和动作过滤器的功能来集中WebAPI中的请求日志记录和异常处理。 路线图 下面是我为逐步学习WebAPI设置的路线图, , ,RESTful日#1:企业级应用程序架构,使用实体框架、通用存储库模式和工作单元的Web api。RESTful日#2:使用Unity容器和引导程序在Web api中使用依赖注入实现控制反转。RESTful日#3:使用Unity容器和可管理扩展框架(MEF)在Asp.net Web api中使用控制反转和依赖注入来解决依赖关系的依赖关系。RESTful日#4:使用MVC 4 Web api中的属性路由自定义URL重写/路由。RESTful日#5:使用操作过滤器的Web api中基于基本身份验证和令牌的自定义授权。RESTful日#6:使用操作过滤器、异常过滤器和NLog在Web api中进行请求日志记录和异常处理/日志记录。RESTful日#7:使用NUnit和Moq框架在WebAPI中进行单元测试和集成测试(第一部分)。使用NUnit和Moq框架在WebAPI中进行单元测试和集成测试(第二部分)。净Web api。RESTful日#10:创建自托管的ASP。NET WebAPI与CRUD操作在Visual Studio 2010 我有意使用Visual Studio 2010和。net Framework 4.0,因为在。net Framework 4.0中很少有很难找到的实现,但我将通过演示如何实现来简化它。 请求日志记录 因为我们正在编写web服务,所以我们正在公开我们的端点。我们必须知道请求来自哪里,哪些请求会到达我们的服务器。日志记录非常有用,可以在很多方面帮助我们,比如调试、跟踪、监视和分析。 我们已经有了一个现有的设计。如果您打开解决方案,您将看到下面提到的结构,或者也可以在现有的解决方案中实现此方法。 在WebAPI中设置NLog NLog用于各种目的,但主要是用于日志记录。我们将使用NLog登录到文件和windows事件中。您可以在http://NLog-project.org/上阅读更多关于NLog的信息 我们可以使用第5天使用的样例应用程序,也可以使用任何其他应用程序。我使用的是我们在本系列的所有部分中使用的现有示例应用程序。我们的应用程序结构看起来像: 步骤1:下载NLog包 右键单击WebAPI项目并从列表中选择manage Nuget包。当Nuget包管理器出现时,搜索NLog。您将得到如下图所示的Nlog,只需将其安装到我们的项目中。 添加之后,您会发现应用程序中引用了以下NLog dll。 步骤2:配置NLog 要用应用程序配置NLog,请在现有的WebAPI web中添加以下设置。配置文件, ConfigSection - 配置部分-我已经添加了<NLog>节中配置并定义了动态目标日志文件的路径和格式名称,还将eventlog源添加到Api服务中。 如上面的目标路径所述,我还在application的基本目录中创建了to“APILog”文件夹。 现在我们已经在应用程序中配置了NLog,可以开始进行请求日志记录了。注意,在规则部分中,我们定义了文件和windows事件日志中的登录规则,您可以同时选择这两个规则,也可以选择其中一个。让我们从使用操作过滤器在应用程序中记录请求开始- - - - - - NLogger类 在API中添加一个文件夹“Helpers”,它将为可读性、更好的理解和可维护性隔离应用程序代码。 要开始添加我们的主类“NLogger”,它将负责所有类型的错误和信息记录,到相同的帮助文件夹。这里,NLogger类实现了ITraceWriter接口,该接口为服务请求提供了“跟踪”方法。 隐藏,收缩,复制Code

#region Using namespaces.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http.Tracing;
using NLog;
using System.Net.Http;
using System.Text;
using WebApi.ErrorHelper;
#endregion

namespace WebApi.Helpers
{
    /// <summary>
    /// Public class to log Error/info messages to the access log file
    /// </summary>
    public sealed class NLogger : ITraceWriter
    {
        #region Private member variables.
        private static readonly Logger ClassLogger = LogManager.GetCurrentClassLogger();

        private static readonly Lazy<Dictionary<TraceLevel, Action<string>>> LoggingMap = new Lazy<Dictionary<TraceLevel, Action<string>>>(() => new Dictionary<TraceLevel, Action<string>> { { TraceLevel.Info, ClassLogger.Info }, { TraceLevel.Debug, ClassLogger.Debug }, { TraceLevel.Error, ClassLogger.Error }, { TraceLevel.Fatal, ClassLogger.Fatal }, { TraceLevel.Warn, ClassLogger.Warn } });
        #endregion

        #region Private properties.
        /// <summary>
        /// Get property for Logger
        /// </summary>
        private Dictionary<TraceLevel, Action<string>> Logger
        {
            get { return LoggingMap.Value; }
        }
        #endregion

        #region Public member methods.
        /// <summary>
        /// Implementation of TraceWriter to trace the logs.
        /// </summary>
        /// <paramname="request"></param>
        /// <paramname="category"></param>
        /// <paramname="level"></param>
        /// <paramname="traceAction"></param>
        public void Trace(HttpRequestMessage request, string category, TraceLevel level, Action<TraceRecord> traceAction)
        {
            if (level != TraceLevel.Off)
            {
                if (traceAction != null && traceAction.Target != null)
                {
                    category = category + Environment.NewLine + "Action Parameters : " + traceAction.Target.ToJSON();
                }
                var record = new TraceRecord(request, category, level);
                if (traceAction != null) traceAction(record);
                Log(record);
            }
        }
        #endregion

        #region Private member methods.
        /// <summary>
        /// Logs info/Error to Log file
        /// </summary>
        /// <paramname="record"></param>
        private void Log(TraceRecord record)
        {
            var message = new StringBuilder();

            if (!string.IsNullOrWhiteSpace(record.Message))
                message.Append("").Append(record.Message + Environment.NewLine);

            if (record.Request != null)
            {
                if (record.Request.Method != null)
                    message.Append("Method: " + record.Request.Method + Environment.NewLine);

                if (record.Request.RequestUri != null)
                    message.Append("").Append("URL: " + record.Request.RequestUri + Environment.NewLine);

                if (record.Request.Headers != null && record.Request.Headers.Contains("Token") && record.Request.Headers.GetValues("Token") != null && record.Request.Headers.GetValues("Token").FirstOrDefault() != null)
                    message.Append("").Append("Token: " + record.Request.Headers.GetValues("Token").FirstOrDefault() + Environment.NewLine);
            }

            if (!string.IsNullOrWhiteSpace(record.Category))
                message.Append("").Append(record.Category);

            if (!string.IsNullOrWhiteSpace(record.Operator))
                message.Append(" ").Append(record.Operator).Append(" ").Append(record.Operation);

            
            Logger[record.Level](Convert.ToString(message) + Environment.NewLine);
        }
        #endregion
    }
}

添加操作过滤器 动作过滤器将负责处理所有传入api的请求,并使用NLogger类对它们进行日志记录。我们有“onactionexecute”方法,如果我们标记控制器或全局应用程序来使用特定的过滤器,它将被隐式调用。因此,每当任何控制器的任何动作被命中时,我们的“onactionexecute”方法将执行以记录请求。 步骤1:添加LoggingFilterAttribute类 创建一个类LoggingFilterAttribute到“ActionFilters”文件夹,并添加以下代码。 隐藏,收缩,复制Code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http.Filters;
using System.Web.Http.Controllers;
using System.Web.Http.Tracing;
using System.Web.Http;
using WebApi.Helpers;


namespace WebApi.ActionFilters
{
    public class LoggingFilterAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext filterContext)
        {
            GlobalConfiguration.Configuration.Services.Replace(typeof(ITraceWriter), new NLogger());
            var trace = GlobalConfiguration.Configuration.Services.GetTraceWriter();
            trace.Info(filterContext.Request, "Controller : " + filterContext.ControllerContext.ControllerDescriptor.ControllerType.FullName + Environment.NewLine + "Action : " + filterContext.ActionDescriptor.ActionName, "JSON", filterContext.ActionArguments);
        }
    }
}

LoggingFilterAttribute类派生自ActionFilterAttribute,它位于System.Web.Http下。筛选并覆盖onactionexecution方法。 这里,我用控制器的服务容器中的NLogger类实例替换了默认的“ITraceWriter”服务。现在GetTraceWriter()方法将返回我们的实例(实例NLogger类),Info()将调用NLogger类的trace()方法。 注意下面的代码。 隐藏,复制Code

GlobalConfiguration.Configuration.Services.Replace(typeof(ITraceWriter), new NLogger());

用于解决ITaceWriter和NLogger类之间的依赖关系。然后,我们使用一个名为trace的变量来获取实例,并使用trace. info()记录请求以及我们希望随请求一起添加的任何文本。 步骤2:注册动作过滤器(LoggingFilterAttribute) 为了将创建的动作过滤器注册到应用程序的过滤器中,只需在配置中添加一个动作过滤器的新实例。过滤器在WebApiConfig类。 隐藏,复制Code

using System.Web.Http;
using WebApi.ActionFilters;

namespace WebApi.App_Start
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.Filters.Add(new LoggingFilterAttribute());
        }
    }
}

现在这个动作过滤器适用于我们项目中的所有控制器和动作。您可能不相信,但是已经完成了请求日志记录。现在运行应用程序并验证我们的作业。 图片来源:https://pixabay.com/en/social-media-network-media-54536/ 运行应用程序 让我们运行应用程序并尝试使用基于令牌的授权进行调用,我们已经在第5天讨论了授权。您首先需要使用登录服务验证您的请求,然后该服务将返回一个令牌用于调用其他服务。使用该令牌调用其他服务。欲了解更多细节,请阅读本系列的第5天。 只要运行应用程序,我们得到 我们已经添加了我们的测试客户端,但是对于新的读者,只要去管理Nuget包,通过右击WebAPI项目和输入WebAPITestClient在搜索框在线包 您将获得“一个用于ASP的简单测试客户端”。NET Web API”,只要添加它。你将在以下区域得到一个帮助控制器->如下图所示: 我在上一篇文章中已经提供了数据库脚本和数据,您可以使用相同的脚本和数据。 在应用程序url中添加“/help”,您将获得测试客户端, 得到: 职位: 把: 删除: 您可以通过单击每个服务来测试它。单击服务链接后,您将被重定向到测试该特定服务的服务页面。在该页面的右下角有一个按钮测试API,只需按下该按钮来测试您的服务: Get所有产品的服务: 在下面的例子中,我已经生成了令牌,现在我使用它来调用从数据库中的products表中获取所有产品。 这里我调用了allproducts API,将参数Id和“Token”头的值与当前值相加,点击得到结果: 现在让我们看看应用程序中的APILog文件夹发生了什么。这里创建了API日志,其名称与我们在web中的NLog配置中配置的名称相同。配置文件。日志文件包含所有提供的细节,如时间戳、方法类型、URL、头信息(令牌)、控制器名称、操作和操作参数。您还可以在此日志中添加您认为对您的应用程序很重要的详细信息。 日志记录完成了! 异常日志 我们的日志设置已经完成,现在我们还将集中精力进行异常日志记录,这样在不进行日志记录的情况下,任何异常都不会转义。日志异常非常重要,它可以跟踪所有的异常。无论是业务异常、应用程序异常还是系统异常,都必须记录下来。 实现异常日志 步骤1:异常过滤器属性 现在我们将在应用程序中添加一个动作过滤器,用于记录异常。为此,创建一个类,GlobalExceptionAttribute到“ActionFilter”文件夹,并添加下面的代码,这个类是从System.Web.Http.Filters下的ExceptionFilterAttribute派生出来的。 我重写了OnExc方法,并将默认的“ITraceWriter”服务替换为控制器服务容器中的NLogger类实例,与上一节中的操作日志操作相同。现在GetTraceWriter()方法将返回我们的实例(实例NLogger类),Info()将调用NLogger类的trace()方法。 隐藏,收缩,复制Code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http.Filters;
using System.Web.Http;
using System.Web.Http.Tracing;
using WebApi.Helpers;
using System.ComponentModel.DataAnnotations;
using System.Net.Http;
using System.Net;

namespace WebApi.ActionFilters
{
    /// <summary>
    /// Action filter to handle for Global application errors.
    /// </summary>
    public class GlobalExceptionAttribute : ExceptionFilterAttribute
    {
        public override void OnException(HttpActionExecutedContext context)
        {
            GlobalConfiguration.Configuration.Services.Replace(typeof(ITraceWriter), new NLogger());
            var trace = GlobalConfiguration.Configuration.Services.GetTraceWriter();
            trace.Error(context.Request, "Controller : " + context.ActionContext.ControllerContext.ControllerDescriptor.ControllerType.FullName + Environment.NewLine + "Action : " + context.ActionContext.ActionDescriptor.ActionName, context.Exception);

            var exceptionType = context.Exception.GetType();

            if (exceptionType == typeof(ValidationException))
            {
                var resp = new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent(context.Exception.Message), ReasonPhrase = "ValidationException", };
                throw new HttpResponseException(resp);

            }
            else if (exceptionType == typeof(UnauthorizedAccessException))
            {
                throw new HttpResponseException(context.Request.CreateResponse(HttpStatusCode.Unauthorized));
            }
            else
            {
                throw new HttpResponseException(context.Request.CreateResponse(HttpStatusCode.InternalServerError));
            }
        }
    }
}

步骤2:修改NLogger类 我们的NLogger类能够记录所有的信息和事件,我在私有方法log()中做了一些更改来处理异常 隐藏,收缩,复制Code

#region Private member methods.
/// <summary>
/// Logs info/Error to Log file
/// </summary>
/// <paramname="record"></param>
private void Log(TraceRecord record)
{
var message = new StringBuilder();

if (!string.IsNullOrWhiteSpace(record.Message))
                message.Append("").Append(record.Message + Environment.NewLine);

      if (record.Request != null)
{
      	if (record.Request.Method != null)
           	message.Append("Method: " + record.Request.Method + Environment.NewLine);

                if (record.Request.RequestUri != null)
                    message.Append("").Append("URL: " + record.Request.RequestUri + Environment.NewLine);

                if (record.Request.Headers != null && record.Request.Headers.Contains("Token") && record.Request.Headers.GetValues("Token") != null && record.Request.Headers.GetValues("Token").FirstOrDefault() != null)
                    message.Append("").Append("Token: " + record.Request.Headers.GetValues("Token").FirstOrDefault() + Environment.NewLine);
            }

            if (!string.IsNullOrWhiteSpace(record.Category))
                message.Append("").Append(record.Category);

            if (!string.IsNullOrWhiteSpace(record.Operator))
                message.Append(" ").Append(record.Operator).Append(" ").Append(record.Operation);

            if (record.Exception != null && !string.IsNullOrWhiteSpace(record.Exception.GetBaseException().Message))
            {
                var exceptionType = record.Exception.GetType();
                message.Append(Environment.NewLine);
                message.Append("").Append("Error: " + record.Exception.GetBaseException().Message + Environment.NewLine);
            }

            Logger[record.Level](Convert.ToString(message) + Environment.NewLine);
        }

步骤3:修改控制器以应对异常 我们的应用程序现在可以运行了,但是代码中没有异常,所以我在ProductController中添加了一个抛出异常代码,只有Get(int id)方法,以便它可以抛出异常来测试异常日志记录机制。如果数据库中没有提供id的产品,它将抛出一个异常。 隐藏,复制Code

  // GET api/product/5
 [GET("productid/{id?}")]
 [GET("particularproduct/{id?}")]
 [GET("myproduct/{id:range(1, 3)}")]
 public HttpResponseMessage Get(int id)
 {
var product = _productServices.GetProductById(id);
      if (product != null)
      	return Request.CreateResponse(HttpStatusCode.OK, product);

 throw new Exception("No product found for this id");
      //return Request.CreateErrorResponse(HttpStatusCode.NotFound,   "No product found for this id");
 }

步骤4:运行应用程序 运行应用程序并单击Product/all API 将参数id值添加到1,并将标题令牌的当前值添加到1,点击send按钮可以得到结果: 现在我们可以看到状态是200/OK,并且我们还在响应体中获得了一个具有所提供id的产品。现在让我们看看API日志: 日志已经捕捉到产品API的调用,现在提供一个新的产品id作为参数,数据库中没有,我使用12345作为产品id,结果是: 我们可以看到有一个500/内部服务器的错误现在在响应状态,让我们检查API日志: 现在,日志已经捕获了服务器上相同调用的事件和错误,您可以看到调用日志的详细信息和日志中提供的错误消息。 自定义异常日志 在上面的部分中,我们实现了异常日志记录,但是有默认的系统响应和状态(即500/内部服务器错误)。为您的API拥有自己的自定义响应和异常总是好的。这将使客户端更容易使用和理解API响应。 步骤1:添加自定义异常类 添加“ErrorHelper”文件夹到应用程序,以单独维护我们的定制异常类,并添加“IApiExceptions”接口到新创建的“ErrorHelper”文件夹 添加以下代码IApiExceptions接口,这将作为所有异常类的模板,我为我们的自定义类添加了四个公共属性来维护错误代码,ErrorDescription, HttpStatus(包含为HTTP定义的状态代码的值)和reason短语。 隐藏,收缩,复制Code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;

namespace WebApi.ErrorHelper
{
    /// <summary>
    /// IApiExceptions Interface
    /// </summary>
    public interface IApiExceptions
    {
        /// <summary>
        /// ErrorCode
        /// </summary>
        int ErrorCode { get; set; }
        /// <summary>
        /// ErrorDescription
        /// </summary>
        string ErrorDescription { get; set; }
        /// <summary>
        /// HttpStatus
        /// </summary>
        HttpStatusCode HttpStatus { get; set; }
        /// <summary>
        /// ReasonPhrase
        /// </summary>
        string ReasonPhrase { get; set; }
    }
}

这里,我将例外分为三类: API异常——用于API级别的异常。业务异常——用于业务逻辑级别的异常。数据异常——与数据相关的异常。 要实现这一点,需要创建三个新的类ApiException。cs, apidatexception .cs和ApiBusinessException类到同一个文件夹,实现IApiExceptions接口,类代码如下。 隐藏,收缩,复制Code

#region Using namespaces.
using System;
using System.Net;
using System.Runtime.Serialization;
#endregion


namespace WebApi.ErrorHelper
{
    /// <summary>
    /// Api Exception
    /// </summary>
    [Serializable]
    [DataContract]
    public class ApiException : Exception, IApiExceptions
    {
        #region Public Serializable properties.
        [DataMember]
        public int ErrorCode { get; set; }
        [DataMember]
        public string ErrorDescription { get; set; }
        [DataMember]
        public HttpStatusCode HttpStatus { get; set; }
        
        string reasonPhrase = "ApiException";

        [DataMember]
        public string ReasonPhrase
        {
            get { return this.reasonPhrase; }

            set { this.reasonPhrase = value; }
        }
        #endregion
    }
}

我已经初始化了ReasonPhrase属性与不同的默认值在这些类来区分实现,你可以使用实现你的自定义类根据你的应用需要。 应用在类上的指令为Serializable和DataContract,以确保类定义或实现的数据契约是可序列化的,并且可以由序列化器序列化。 注意:添加“System.Runtime.Serialization”的引用。如果您遇到任何汇编问题。 以同样的方式将“ApiBusinessException”和“ApiDataException”类添加到同一个文件夹中,使用以下代码— 隐藏,收缩,复制Code

#region Using namespaces.
using System;
using System.Net;
using System.Runtime.Serialization; 
#endregion

namespace WebApi.ErrorHelper
{
    /// <summary>
    /// Api Business Exception
    /// </summary>
    [Serializable]
    [DataContract]
    public class ApiBusinessException : Exception, IApiExceptions
    {
        #region Public Serializable properties.
        [DataMember]
        public int ErrorCode { get; set; }
        [DataMember]
        public string ErrorDescription { get; set; }
        [DataMember]
        public HttpStatusCode HttpStatus { get; set; }

        string reasonPhrase = "ApiBusinessException";

        [DataMember]
        public string ReasonPhrase
        {
            get { return this.reasonPhrase; }

            set { this.reasonPhrase = value; }
        }
        #endregion

        #region Public Constructor.
        /// <summary>
        /// Public constructor for Api Business Exception
        /// </summary>
        /// <paramname="errorCode"></param>
        /// <paramname="errorDescription"></param>
        /// <paramname="httpStatus"></param>
        public ApiBusinessException(int errorCode, string errorDescription, HttpStatusCode httpStatus)
        {
            ErrorCode = errorCode;
            ErrorDescription = errorDescription;
            HttpStatus = httpStatus;
        } 
        #endregion

    }
}

#region Using namespaces.
using System;
using System.Net;
using System.Runtime.Serialization;
#endregion

namespace WebApi.ErrorHelper
{
    /// <summary>
    /// Api Data Exception
    /// </summary>
    [Serializable]
    [DataContract]
    public class ApiDataException : Exception, IApiExceptions
    {
        #region Public Serializable properties.
        [DataMember]
        public int ErrorCode { get; set; }
        [DataMember]
        public string ErrorDescription { get; set; }
        [DataMember]
        public HttpStatusCode HttpStatus { get; set; }

        string reasonPhrase = "ApiDataException";

        [DataMember]
        public string ReasonPhrase
        {
            get { return this.reasonPhrase; }

            set { this.reasonPhrase = value; }
        }

        #endregion

        #region Public Constructor.
        /// <summary>
        /// Public constructor for Api Data Exception
        /// </summary>
        /// <paramname="errorCode"></param>
        /// <paramname="errorDescription"></param>
        /// <paramname="httpStatus"></param>
        public ApiDataException(int errorCode, string errorDescription, HttpStatusCode httpStatus)
        {
            ErrorCode = errorCode;
            ErrorDescription = errorDescription;
            HttpStatus = httpStatus;
        }
        #endregion
    }
}

JSon序列化器 有一些对象需要用JSON序列化,以便通过模块进行日志记录和传输,为此,我向Object类添加了一些扩展方法。 为此添加“System.Web.Extensions”。在Helpers文件夹中添加“JSONHelper”类,代码如下: 隐藏,收缩,复制Code

#region Using namespaces.
using System.Web.Script.Serialization;
using System.Data;
using System.Collections.Generic;
using System;

#endregion

namespace WebApi.Helpers
{
    public static class JSONHelper
    {
         #region Public extension methods.
        /// <summary>
        /// Extened method of object class, Converts an object to a json string.
        /// </summary>
        /// <paramname="obj"></param>
        /// <returns></returns>
        public static string ToJSON(this object obj)
        {
            var serializer = new JavaScriptSerializer();
            try
            {
                return serializer.Serialize(obj);
            }
            catch(Exception ex)
            {
                return "";
            }
        }
         #endregion
    }
}

在上面的代码中,“ToJSON()”方法是基对象类的扩展,它将对象序列化为一个JSON字符串。使用“JavaScriptSerializer”类的方法,该类存在于“System.Web.Script.Serialization”中。 修改NLogger类 对于异常处理,我修改了NLogger的Log()方法,它现在将处理不同的API异常。 隐藏,收缩,复制Code

/// <summary>
/// Logs info/Error to Log file
/// </summary>
/// <paramname="record"></param>
private void Log(TraceRecord record)
{
var message = new StringBuilder();

      if (!string.IsNullOrWhiteSpace(record.Message))
                message.Append("").Append(record.Message + Environment.NewLine);

            if (record.Request != null)
            {
                if (record.Request.Method != null)
                    message.Append("Method: " + record.Request.Method + Environment.NewLine);

                if (record.Request.RequestUri != null)
                    message.Append("").Append("URL: " + record.Request.RequestUri + Environment.NewLine);

                if (record.Request.Headers != null && record.Request.Headers.Contains("Token") && record.Request.Headers.GetValues("Token") != null && record.Request.Headers.GetValues("Token").FirstOrDefault() != null)
                    message.Append("").Append("Token: " + record.Request.Headers.GetValues("Token").FirstOrDefault() + Environment.NewLine);
            }

            if (!string.IsNullOrWhiteSpace(record.Category))
                message.Append("").Append(record.Category);

            if (!string.IsNullOrWhiteSpace(record.Operator))
                message.Append(" ").Append(record.Operator).Append(" ").Append(record.Operation);

            if (record.Exception != null && !string.IsNullOrWhiteSpace(record.Exception.GetBaseException().Message))
            {
                var exceptionType = record.Exception.GetType();
                message.Append(Environment.NewLine);
                if (exceptionType == typeof(ApiException))
                {
                    var exception = record.Exception as ApiException;
                    if (exception != null)
                    {
                        message.Append("").Append("Error: " + exception.ErrorDescription + Environment.NewLine);
                        message.Append("").Append("Error Code: " + exception.ErrorCode + Environment.NewLine);
                    }
                }
                else if (exceptionType == typeof(ApiBusinessException))
                {
                    var exception = record.Exception as ApiBusinessException;
                    if (exception != null)
                    {
                        message.Append("").Append("Error: " + exception.ErrorDescription + Environment.NewLine);
                        message.Append("").Append("Error Code: " + exception.ErrorCode + Environment.NewLine);
                    }
                }
                else if (exceptionType == typeof(ApiDataException))
                {
                    var exception = record.Exception as ApiDataException;
                    if (exception != null)
                    {
                        message.Append("").Append("Error: " + exception.ErrorDescription + Environment.NewLine);
                        message.Append("").Append("Error Code: " + exception.ErrorCode + Environment.NewLine);
                    }
                }
                else
                    message.Append("").Append("Error: " + record.Exception.GetBaseException().Message + Environment.NewLine);
            }

            Logger[record.Level](Convert.ToString(message) + Environment.NewLine);
        }

上面的代码检查TraceRecord的异常对象,并根据异常类型更新日志程序。 修改GlobalExceptionAttribute 因为我们已经创建了GlobalExceptionAttribute来处理所有异常并在任何异常情况下创建响应。现在我添加了一些新代码,以使GlobalExceptionAttribute类能够处理定制异常。我在这里只添加了修改过的方法供你参考。 隐藏,收缩,复制Code

public override void OnException(HttpActionExecutedContext context)
{
         GlobalConfiguration.Configuration.Services.Replace(typeof(ITraceWriter), new NLogger());
            var trace = GlobalConfiguration.Configuration.Services.GetTraceWriter();
            trace.Error(context.Request, "Controller : " + context.ActionContext.ControllerContext.ControllerDescriptor.ControllerType.FullName + Environment.NewLine + "Action : " + context.ActionContext.ActionDescriptor.ActionName, context.Exception);

            var exceptionType = context.Exception.GetType();

            if (exceptionType == typeof(ValidationException))
            {
                var resp = new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent(context.Exception.Message), ReasonPhrase = "ValidationException", };
                throw new HttpResponseException(resp);

            }
            else if (exceptionType == typeof(UnauthorizedAccessException))
            {
                throw new HttpResponseException(context.Request.CreateResponse(HttpStatusCode.Unauthorized, new ServiceStatus() { StatusCode = (int)HttpStatusCode.Unauthorized, StatusMessage = "UnAuthorized", ReasonPhrase = "UnAuthorized Access" }));
            }
            else if (exceptionType == typeof(ApiException))
            {
                var webapiException = context.Exception as ApiException;
                if (webapiException != null)
                    throw new HttpResponseException(context.Request.CreateResponse(webapiException.HttpStatus, new ServiceStatus() { StatusCode = webapiException.ErrorCode, StatusMessage = webapiException.ErrorDescription, ReasonPhrase = webapiException.ReasonPhrase }));
            }
            else if (exceptionType == typeof(ApiBusinessException))
            {
                var businessException = context.Exception as ApiBusinessException;
                if (businessException != null)
                    throw new HttpResponseException(context.Request.CreateResponse(businessException.HttpStatus, new ServiceStatus() { StatusCode = businessException.ErrorCode, StatusMessage = businessException.ErrorDescription, ReasonPhrase = businessException.ReasonPhrase }));
            }
            else if (exceptionType == typeof(ApiDataException))
            {
                var dataException = context.Exception as ApiDataException;
                if (dataException != null)
                    throw new HttpResponseException(context.Request.CreateResponse(dataException.HttpStatus, new ServiceStatus() { StatusCode = dataException.ErrorCode, StatusMessage = dataException.ErrorDescription, ReasonPhrase = dataException.ReasonPhrase }));
            }
            else
            {
                throw new HttpResponseException(context.Request.CreateResponse(HttpStatusCode.InternalServerError));
            }
        }

在上面的代码中,我修改了重写的方法OnExeption(),并基于不同的异常类型创建了新的Http响应异常。 修改产品控制器 现在修改产品控制器抛出我们的自定义异常形式,请查看我已经修改的Get方法抛出APIDataException,如果没有发现数据和APIException任何其他类型的错误。 隐藏,复制Code

// GET api/product/5
[GET("productid/{id?}")]
[GET("particularproduct/{id?}")]
[GET("myproduct/{id:range(1, 3)}")]
public HttpResponseMessage Get(int id)
{
if (id != null)
      {
      	var product = _productServices.GetProductById(id);
            if (product != null)
            	return Request.CreateResponse(HttpStatusCode.OK, product);

throw new ApiDataException(1001, "No product found for this id.", HttpStatusCode.NotFound);
      }
      throw new ApiException() { ErrorCode = (int)HttpStatusCode.BadRequest, ErrorDescription = "Bad Request..." };
}

运行应用程序 运行应用程序并点击Product/all API: 将参数id值加到1,头标牌的当前值加到1,点击send按钮,得到结果: 现在我们可以看到状态是200/OK,并且我们还在响应体中获得了一个具有所提供id的产品。现在让我们看看API日志- 日志已经捕捉到产品API的调用,现在提供一个新的产品id作为参数,数据库中没有,我使用12345作为产品id,结果是: 我们可以看到,现在有一个自定义错误状态代码“1001”和消息“没有找到这个id的产品”。通用状态代码“500/Internal Server Error”现在被我们提供的代码“404/ Not Found”替换,这对客户端或消费者更有意义。 现在让我们来看看APILog: 现在日志了相同的事件和错误调用服务器上,你可以看到通话记录的细节和日志中的错误和错误信息提供与我们的自定义错误代码,我只有捕获的错误描述和错误代码,但是您可以添加更多的细节在日志中根据您的应用程序的需求。 为新的异常处理更新控制器 下面是实现了自定义异常处理和日志记录的控制器代码: 产品控制器 隐藏,收缩,复制Code

using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using AttributeRouting;
using AttributeRouting.Web.Http;
using BusinessEntities;
using BusinessServices;
using WebApi.ActionFilters;
using WebApi.Filters;
using System;
using WebApi.ErrorHelper;

namespace WebApi.Controllers
{
    [AuthorizationRequired]
    [RoutePrefix("v1/Products/Product")]
    public class ProductController : ApiController
    {
        #region Private variable.

        private readonly IProductServices _productServices;

        #endregion

        #region Public Constructor

        /// <summary>
        /// Public constructor to initialize product service instance
        /// </summary>
        public ProductController(IProductServices productServices)
        {
            _productServices = productServices;
        }

        #endregion

        // GET api/product
        [GET("allproducts")]
        [GET("all")]
        public HttpResponseMessage Get()
        {
            var products = _productServices.GetAllProducts();
            var productEntities = products as List<ProductEntity> ?? products.ToList();
            if (productEntities.Any())
                return Request.CreateResponse(HttpStatusCode.OK, productEntities);
            throw new ApiDataException(1000, "Products not found", HttpStatusCode.NotFound);
        }

        // GET api/product/5
        [GET("productid/{id?}")]
        [GET("particularproduct/{id?}")]
        [GET("myproduct/{id:range(1, 3)}")]
        public HttpResponseMessage Get(int id)
        {
            if (id != null)
            {
                var product = _productServices.GetProductById(id);
                if (product != null)
                    return Request.CreateResponse(HttpStatusCode.OK, product);

                throw new ApiDataException(1001, "No product found for this id.", HttpStatusCode.NotFound);
            }
            throw new ApiException() { ErrorCode = (int)HttpStatusCode.BadRequest, ErrorDescription = "Bad Request..." };
        }

        // POST api/product
        [POST("Create")]
        [POST("Register")]
        public int Post([FromBody] ProductEntity productEntity)
        {
            return _productServices.CreateProduct(productEntity);
        }

        // PUT api/product/5
        [PUT("Update/productid/{id}")]
        [PUT("Modify/productid/{id}")]
        public bool Put(int id, [FromBody] ProductEntity productEntity)
        {
            if (id > 0)
            {
                return _productServices.UpdateProduct(id, productEntity);
            }
            return false;
        }

        // DELETE api/product/5
        [DELETE("remove/productid/{id}")]
        [DELETE("clear/productid/{id}")]
        [PUT("delete/productid/{id}")]
        public bool Delete(int id)
        {
            if (id != null && id > 0)
            {
                var isSuccess = _productServices.DeleteProduct(id);
                if (isSuccess)
                {
                    return isSuccess;
                }
                throw new ApiDataException(1002, "Product is already deleted or not exist in system.", HttpStatusCode.NoContent );
            }
            throw new ApiException() {ErrorCode = (int) HttpStatusCode.BadRequest, ErrorDescription = "Bad Request..."};
        }
    }
}

现在您可以看到,我们的应用程序是如此丰富和可扩展,没有任何异常或事务可以逃避日志记录。一旦安装完成,现在您不必担心每次都要为日志记录或请求和异常编写代码,但是您可以放松下来,只关注业务逻辑。 图片来源:https://pixabay.com/en/kermit-frog-meadow-daisy-concerns-383370/ 结论 在本文中,我们学习了如何在WebPI中执行请求日志和异常日志。可以有多种方式来执行这些操作,但我尽量用最简单的方式来表示。我的方法是将我们的企业级开发带到下一个层次,在这个层次中开发人员不应该总是担心异常处理和日志。我们的解决方案提供了一种将操作集中在一个地方的通用方法;所有的请求和异常都会被自动处理。在我的新文章中,我将通过解释WebAPI中的单元测试和WebAPI中的OData来改进这个应用程序。您可以从GitHub下载本文的完整源代码和包。编码快乐:) 其他系列 我的其他系列文章: MVC: http://www.codeproject.com/Articles/620195/Learning-MVC-Part-Introduction-to-MVC-Architectu OOP: http://www.codeproject.com/Articles/771455/Diving-in-OOP-Day-Polymorphism-and-Inheritance-Ear 本文转载于:http://www.diyabc.com/frontweb/news410.html

posted @ 2020-08-06 17:06  Dincat  阅读(510)  评论(0编辑  收藏  举报