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 = newLazy<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