目录
在WebApiConfig类中注册 UnhandledExceptionLogger
将ApiVersion属性和路由属性添加到Values API控制器
将ApiVersion属性和路由属性添加到Values2 API控制器
介绍
在本文中,我们将学习如何记录API的每个请求和响应,以帮助维护日志。接下来,我们将处理所有API异常,这样如果发生错误,我们可以存储错误并尽快修复它,最后一部分是API的版本控制。
- 异常处理
- 日志记录
- 版本控制
当您开发生产API时,所有这些部分都是关键。
Freepik从www.flaticon.com制作的图标由CC 3.0 BY授权:
1.异常处理
首先,我们创建一个简单的Web API应用程序“WebDemoAPI”。
创建一个简单的Web API解决方案后,您将获得一个默认的Home控制器和Values API控制器。让我们首先运行应用程序并调用get请求。
注意:您可以使用任何Rest Client发送此演示的请求,我将使用POSTMAN Rest客户端。
URL: http://localhost:50664/api/values
发送Get请求
向API发送请求后,我们得到了响应。
现在让我们改变Get方法,在这里,我要抛出一个异常。
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
throw new NotImplementedException("");
//return new string[] { "value1", "value2" };
}
}
现在,如果我们向values API请求发送get请求,那么它将在响应中抛出错误。
处理异常前的响应
现在我们得到了错误,让我们看看如何全局处理这个错误。
使用ExceptionHandler类处理API异常:
为了处理异常,我们将创建一个“GlobalExceptionHandler”类,其继承自的“ExceptionHandler”abstract类。在这里面,我们将实现Handle方法。在此之前,我们将创建“CustomHandler”文件夹。在这个文件夹中,我们将添加“GlobalExceptionHandler”类。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using System.Web.Http.ExceptionHandling;
namespace WebDemoAPI.CustomHandler
{
public class GlobalExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
var result = new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent("Internal Server Error Occurred"),
ReasonPhrase = "Exception"
};
context.Result = new ErrorMessageResult(context.Request, result);
}
public class ErrorMessageResult : IHttpActionResult
{
private HttpRequestMessage _request;
private readonly HttpResponseMessage _httpResponseMessage;
public ErrorMessageResult
(HttpRequestMessage request, HttpResponseMessage httpResponseMessage)
{
_request = request;
_httpResponseMessage = httpResponseMessage;
}
public Task<HttpResponseMessage>
ExecuteAsync(CancellationToken cancellationToken)
{
return Task.FromResult(_httpResponseMessage);
}
}
}
}
现在我们已经实现了ExceptionHandler类中的Handle方法。
在做之前,首先我们去创建HttpResponseMessage。为此,我们将添加一个继承自“IHttpActionResult”接口的类“ErrorMessageResult”。这个类将有一个带两个参数的参数化构造函数:
- HttpRequestMessage
- HttpResponseMessage
我们带参数的HttpResponseMessage将被ExecuteAsync用来创建HttpResponseMessage。
然后,这个HttpResponseMessage我们将把它赋值给“context.Result”。
处理完异常后,接下来我们需要注册这个处理程序。
注册异常处理程序
我们将在WebApiConfig类中注册“GlobalExceptionHandler”,以便可以全局处理任何Web API异常。
//Registering GlobalExceptionHandler
config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using System.Web.Http.ExceptionHandling;
using WebDemoAPI.CustomHandler;
namespace WebDemoAPI
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
//Registering GlobalExceptionHandler
config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
现在让我们运行这个应用程序并检查我们现在是否处理异常。
抛出异常的快照
抛出异常后,现在我们向消费者显示正确的错误消息而不是错误堆栈跟踪。
异常处理后的响应
现在我们已经处理了异常,但我们还没有记录异常。
异常记录
在这一部分中,我们将把异常存储到数据库中,为此,让我们首先看看我们将存储它的表结构。
API_错误
在查看了表结构之后,我进一步编写了一个简单的过程来将此异常存储在表中。
现在我们已经完成了数据库部分,接下来,让我们添加类和方法以将异常写入数据库。
APIError类
public class ApiError
{
public string Message { get; set; }
public string RequestMethod { get; set; }
public string RequestUri { get; set; }
public DateTime TimeUtc { get; set; }
}
注意:存储过程和表脚本可供下载。
SqlErrorLogging类
在这一部分中,我们将在数据库中写入错误,在这个类中,我们有将ApiError类作为输入参数的InsertErrorLog方法。
public class SqlErrorLogging
{
public void InsertErrorLog(ApiError apiError)
{
try
{
using (var sqlConnection = new SqlConnection
(ConfigurationManager.ConnectionStrings
["APILoggingConnection"].ConnectionString))
{
sqlConnection.Open();
var cmd =
new SqlCommand("API_ErrorLogging", connection: sqlConnection)
{
CommandType = CommandType.StoredProcedure
};
cmd.Parameters.AddWithValue("@TimeUtc", apiError.TimeUtc);
cmd.Parameters.AddWithValue("@RequestUri", apiError.RequestUri);
cmd.Parameters.AddWithValue("@Message", apiError.Message);
cmd.Parameters.AddWithValue("@RequestMethod", apiError.RequestMethod);
cmd.ExecuteNonQuery();
}
}
catch (Exception)
{
throw;
}
}
}
添加完类和方法后,接下来我们要添加“UnhandledExceptionLogger”类,其继承自的“ExceptionLogger”abstract类。
UnhandledExceptionLogger类
我们将添加一个类“UnhandledExceptionLogger”,它将继承自“ExceptionLogger”一个abstract类,因为我们将覆盖“Log”方法,在此方法中,我们将获得从该异常发生的异常,我们将提取Source,StackTrace,TargetSite等信息,并将其分配给ApiError类以存储在数据库中。
using System;
using System.Web.Http.ExceptionHandling;
using WebDemoAPI.Models;
namespace WebDemoAPI.CustomHandler
{
public class UnhandledExceptionLogger : ExceptionLogger
{
public override void Log(ExceptionLoggerContext context)
{
var ex = context.Exception;
string strLogText = "";
strLogText += Environment.NewLine + "Source ---\n{0}" + ex.Source;
strLogText += Environment.NewLine + "StackTrace ---\n{0}" + ex.StackTrace;
strLogText += Environment.NewLine + "TargetSite ---\n{0}" + ex.TargetSite;
if (ex.InnerException != null)
{
strLogText += Environment.NewLine +
"Inner Exception is {0}" + ex.InnerException;//error prone
}
if (ex.HelpLink != null)
{
strLogText += Environment.NewLine + "HelpLink ---\n{0}" +
ex.HelpLink;//error prone
}
var requestedURi = (string)context.Request.RequestUri.AbsoluteUri;
var requestMethod = context.Request.Method.ToString();
var timeUtc = DateTime.Now;
SqlErrorLogging sqlErrorLogging = new SqlErrorLogging();
ApiError apiError = new ApiError()
{
Message = strLogText,
RequestUri = requestedURi,
RequestMethod = requestMethod,
TimeUtc = DateTime.Now
};
sqlErrorLogging.InsertErrorLog(apiError);
}
}
}
创建“UnhandledExceptionLogger”类并将错误写入数据库后,接下来我们将在WebApiConfig类中全局注册该类。
//Registering UnhandledExceptionLogger
config.Services.Replace(typeof(IExceptionLogger), new UnhandledExceptionLogger());
在WebApiConfig类中注册 UnhandledExceptionLogger
using System.Web.Http;
using System.Web.Http.ExceptionHandling;
using WebDemoAPI.CustomHandler;
namespace WebDemoAPI
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
//Registering GlobalExceptionHandler
config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
//Registering UnhandledExceptionLogger
config.Services.Replace(typeof(IExceptionLogger), new UnhandledExceptionLogger());
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
注册UnhandledExceptionLogger类之后,现在让我们运行应用程序,看看它是否存储了数据库中发生的异常。
收到错误后,我们对其进行了处理并向用户显示了正确的错误消息,并将错误记录在数据库中。
处理异常和Logging异常后的响应:
存储异常
在处理和记录异常之后,接下来我们将记录每个请求和对Web API的响应。
2. 记录请求和响应
在这一部分中,我们将记录WEB API的每个请求和响应。
为此,我们将继承一个abstract类“DelegatingHandler”并覆盖SendAsync方法。
如果您看到下表,您将清楚地了解我们从请求和响应存储到数据库中的所有数据。
让我们首先创建一个“API_Log”表,我们将在其中存储此请求以作为响应。
创建表后,我们创建了一个用于将Log插入API_Log表的简单存储过程。此存储过程可供下载。
接下来,我们将添加“ApiLog”类来将数据传递给存储过程。
namespace WebDemoAPI.Models
{
public class ApiLog
{
public string Host { get; set; }
public string Headers { get; set; }
public string StatusCode { get; set; }
public string RequestBody { get; set; }
public string RequestedMethod { get; set; }
public string UserHostAddress { get; set; }
public string Useragent { get; set; }
public string AbsoluteUri { get; set; }
public string RequestType { get; set; }
}
}
添加ApiLog类后,接下来我们要Add一个ApiLogging类。在该类中,我们将添加InsertLog方法,它将采用ApiLog类作为参数,ApiLog类数据将映射到SQL参数,将数据插入数据库。插入数据库。
public class ApiLogging
{
public void InsertLog(ApiLog apiLog)
{
try
{
using (var sqlConnection = new SqlConnection
(ConfigurationManager.ConnectionStrings
["APILoggingConnection"].ConnectionString))
{
sqlConnection.Open();
var cmd =
new SqlCommand("API_Logging", connection: sqlConnection)
{
CommandType = CommandType.StoredProcedure
};
cmd.Parameters.AddWithValue("@Host", apiLog.Host);
cmd.Parameters.AddWithValue("@Headers", apiLog.Headers);
cmd.Parameters.AddWithValue("@StatusCode", apiLog.StatusCode);
cmd.Parameters.AddWithValue("@RequestBody", apiLog.RequestBody);
cmd.Parameters.AddWithValue("@RequestedMethod", apiLog.RequestedMethod);
cmd.Parameters.AddWithValue("@UserHostAddress", apiLog.UserHostAddress);
cmd.Parameters.AddWithValue("@Useragent", apiLog.Useragent);
cmd.Parameters.AddWithValue("@AbsoluteUri", apiLog.AbsoluteUri);
cmd.Parameters.AddWithValue("@RequestType", apiLog.RequestType);
cmd.ExecuteNonQuery();
}
}
catch (Exception)
{
throw;
}
}
}
完成添加ApiLogging类后,接下来我们将编写此过程的主要核心,即添加自定义处理程序。
创建自定义处理程序
我们将添加一个名为“RequestResponseHandler”的类,然后我们将从DelegatingHandler abstract类和override SendAsync方法继承。
public class RequestResponseHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage>
SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
}
}
在实现SendAsync方法之前,我编写了一个简单的类MessageLogging,其中有两个方法,IncomingMessageAsync和OutgoingMessageAsync。我创建此方法仅用于分配请求类型并分别调用这两种方法。
public class MessageLogging
{
public void IncomingMessageAsync(ApiLog apiLog)
{
apiLog.RequestType = "Request";
var sqlErrorLogging = new ApiLogging();
sqlErrorLogging.InsertLog(apiLog);
}
public void OutgoingMessageAsync(ApiLog apiLog)
{
apiLog.RequestType = "Response";
var sqlErrorLogging = new ApiLogging();
sqlErrorLogging.InsertLog(apiLog);
}
}
现在添加MessageLogging类之后,接下来我们将实现DelegatingHandler abstract类中的SendAsync方法。
public class RequestResponseHandler: DelegatingHandler
{
protected override async Task<HttpResponseMessage>
SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
var requestedMethod = request.Method;
var userHostAddress = HttpContext.Current != null ?
HttpContext.Current.Request.UserHostAddress : "0.0.0.0";
var useragent = request.Headers.UserAgent.ToString();
var requestMessage = await request.Content.ReadAsByteArrayAsync();
var uriAccessed = request.RequestUri.AbsoluteUri;
var responseHeadersString = new StringBuilder();
foreach (var header in request.Headers)
{
responseHeadersString.Append($"{header.Key}:
{String.Join(", ", header.Value)}{Environment.NewLine}");
}
var messageLoggingHandler = new MessageLogging();
var requestLog = new ApiLog()
{
Headers = responseHeadersString.ToString(),
AbsoluteUri = uriAccessed,
Host = userHostAddress,
RequestBody = Encoding.UTF8.GetString(requestMessage),
UserHostAddress = userHostAddress,
Useragent = useragent,
RequestedMethod = requestedMethod.ToString(),
StatusCode = string.Empty
};
messageLoggingHandler.IncomingMessageAsync(requestLog);
var response = await base.SendAsync(request, cancellationToken);
byte[] responseMessage;
if (response.IsSuccessStatusCode)
responseMessage = await response.Content.ReadAsByteArrayAsync();
else
responseMessage = Encoding.UTF8.GetBytes(response.ReasonPhrase);
var responseLog = new ApiLog()
{
Headers = responseHeadersString.ToString(),
AbsoluteUri = uriAccessed,
Host = userHostAddress,
RequestBody = Encoding.UTF8.GetString(responseMessage),
UserHostAddress = userHostAddress,
Useragent = useragent,
RequestedMethod = requestedMethod.ToString(),
StatusCode = string.Empty
};
messageLoggingHandler.OutgoingMessageAsync(responseLog);
return response;
}
}
让我们了解我们在SendAsync方法中写了什么。
请求方法
var requestedMethod = request.Method;
我们正在存储request方法,无论它是POST PUT DELETE还是GET。
主机地址
var userHostAddress = HttpContext.Current != null ?
HttpContext.Current.Request.UserHostAddress : "0.0.0.0";
我们正在获取此请求的IP地址。
用户代理
var useragent = request.Headers.UserAgent.ToString();
UserAgent给你一个关于浏览器的原始字符串。
请求正文
var requestMessage = await request.Content.ReadAsByteArrayAsync();
绝对URI
var uriAccessed = request.RequestUri.AbsoluteUri;
标头
var responseHeadersString = new StringBuilder();
foreach (var header in request.Headers)
{
responseHeadersString.Append($"{header.Key}: {String.Join(", ", header.Value)}
{Environment.NewLine}");
}
为ApiLog类赋值
var messageLoggingHandler = new MessageLogging();
var requestLog = new ApiLog()
{
Headers = responseHeadersString.ToString(),
AbsoluteUri = uriAccessed,
Host = userHostAddress,
RequestBody = Encoding.UTF8.GetString(requestMessage),
UserHostAddress = userHostAddress,
Useragent = useragent,
RequestedMethod = requestedMethod.ToString(),
StatusCode = string.Empty
};
传入请求记录
messageLoggingHandler.IncomingMessageAsync(requestLog);
传出响应记录
messageLoggingHandler.OutgoingMessageAsync(responseLog);
注册RequestResponseHandler
using System.Web.Http;
using System.Web.Http.ExceptionHandling;
using WebDemoAPI.CustomHandler;
namespace WebDemoAPI
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
//Registering GlobalExceptionHandler
config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
//Registering UnhandledExceptionLogger
config.Services.Replace(typeof(IExceptionLogger), new UnhandledExceptionLogger());
//Registering RequestResponseHandler
config.MessageHandlers.Add(new RequestResponseHandler());
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
现在我们知道了这个过程是如何工作的。让我们运行应用程序,看看它是否有效。
访问Values API控制器
请求和响应Web API日志记录
3. 版本控制
由Freepik从www.flaticon.com制作的图标由CC 3.0 BY许可
它是Web API开发中最重要的部分,因为我们不断改进应用程序,我们不断对应用程序进行更改,如果我们对已经在生产中的API进行更改并且许多用户正在使用它将破坏工作应用程序,解决方案是对您的API进行版本控制,以便使用您的API的老用户不会对其产生任何影响。
让我们通过简单的步骤开始在ASP.NET Web API中实现版本控制。
首先,我们将向应用程序添加“Microsoft.AspNet.WebApi.Versioning”NuGet 包。
接下来安装NuGet包后,我们将在WebApiConfig.cs文件中注册AddApiVersioning方法。
当您向应用程序添加API版本控制时,该ApiVersioningOptions类允许您配置、自定义和扩展默认行为。
引用自:https ://github.com/Microsoft/aspnet-api-versioning/wiki/API-Versioning-Options
AddApiVersioning方法的代码片段
config.AddApiVersioning(o =>
{
o.ReportApiVersions = true;
o.AssumeDefaultVersionWhenUnspecified = true;
o.DefaultApiVersion = new ApiVersion(2, 0);
o.ApiVersionReader = new HeaderApiVersionReader("version");
o.ApiVersionSelector = new CurrentImplementationApiVersionSelector(o);
}
);
WebApiConfig的完整代码片段
在这一部分中,我们将注释默认路由并启用属性基础路由。
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.AddApiVersioning(o =>
{
o.ReportApiVersions = true;
o.AssumeDefaultVersionWhenUnspecified = true;
o.DefaultApiVersion = new ApiVersion(2, 0);
o.ApiVersionReader = new HeaderApiVersionReader("version");
o.ApiVersionSelector = new CurrentImplementationApiVersionSelector(o);
}
);
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
//Registering GlobalExceptionHandler
config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
//Registering UnhandledExceptionLogger
config.Services.Replace(typeof(IExceptionLogger), new UnhandledExceptionLogger());
//Registering RequestResponseHandler
config.MessageHandlers.Add(new RequestResponseHandler());
//config.Routes.MapHttpRoute(
// name: "DefaultApi",
// routeTemplate: "api/{controller}/{id}",
// defaults: new { id = RouteParameter.Optional }
//);
}
}
完成注册方法后,接下来我们将添加另一个名为“Values2Controller”的API控制器。
添加Values2Controller API控制器
如果你看到,我们已经添加了Values2名称API控制器,我们已经添加了一个版本的控制器的名称不是必须添加的,但名称必须是唯一的,容易理解。
public class Values2Controller : ApiController
{
// GET: api/Values2
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET: api/Values2/5
public string Get(int id)
{
return "value";
}
// POST: api/Values2
public void Post([FromBody]string value){}
// PUT: api/Values2/5
public void Put(int id, [FromBody]string value) {}
// DELETE: api/Values2/5
public void Delete(int id) {}
}
添加Values2 API控制器后,接下来我们将向旧的API控制器和新的API控制器添加路由属性。
将ApiVersion属性和路由属性添加到Values API控制器
[ApiVersion("1.0")]
[Route("api/values")]
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
//throw new NotImplementedException("");
return new string[] { "value1", "value2" };
}
// GET api/values/5
public string Get(int id) { return "value";}
// POST api/values
public void Post([FromBody]string value){}
// PUT api/values/5
public void Put(int id, [FromBody]string value){}
// DELETE api/values/5
public void Delete(int id) {}
}
将ApiVersion属性和路由属性添加到Values2 API控制器
[ApiVersion("2.0")]
[Route("api/values")]
public class Values2Controller : ApiController
{
// GET api/Values2
public IEnumerable<string> Get()
{
//throw new NotImplementedException("");
return new string[] { "version2", " version2" };
}
// GET api/Values2/5
public string Get(int id){return "value";}
// POST api/Values2
public void Post([FromBody]string value) { }
// PUT api/Values2/5
public void Put(int id, [FromBody]string value) { }
// DELETE api/Values2/5
public void Delete(int id) { }
}
添加路由和版本属性后,接下来保存并运行应用程序。
现在要调用API,我们需要从标头中传递一个API版本,标头的名称是“version”。
我们将把标题名称“version”和值作为1.0传递给调用values控制器。
请求Values API
在调用完1.0版本的值API之后,接下来我们将以相同的方式调用values2 2.0版本标头的API。
我们将把标题名称“version”和值作为2.0传递给调用Values2控制器。
在使用2.0版标头访问值控制器(values2controller)后,我们得到了预期的有效响应。
结论
在本文中,我们学习了如何“处理异常”、“记录异常”,还学习了如何记录每个传入和传出的Web API请求和响应,我们如何对Web API进行版本控制,我应该不破坏现有的工作API,这一切都是我们逐步详细了解的,以便我们可以直接与Live Projects集成。
https://www.codeproject.com/Articles/1250932/Logging-and-Exception-handling-Versioning-in-ASP-N