介绍
性能是API的流行语。API性能的最重要且可衡量的参数之一是响应时间。在本文中,我们将了解如何添加代码以测量API的响应时间,然后将响应时间数据返回给最终客户端。

需要些什么?
让我们花点时间思考为什么我们将需要这样的功能来测量API的响应时间。以下是一些要点,它们是编写代码以捕获响应时间的灵感。
您需要为与客户的API定义SLA(服务水平协议)。客户需要了解API花费多少时间来回复。随着时间的过去,响应时间数据可以帮助我们确定API的SLA。
管理层对有关应用程序快慢的报告感兴趣。您需要数据来证实您的主张。值得报告应用程序的性能并与利益相关者共享。
客户端需要具有API响应时间的信息,以便他们可以跟踪在客户端和服务器上花费了多少时间。
您可能在项目中也遇到过类似的请求,因此值得研究一种捕获API响应时间的方法。
在哪里添加代码?
让我们探索几种方法来捕获API的响应时间,主要集中在捕获API内部花费的时间。我们的目标是计算从Asp.net Core接收到请求到服务器处理响应并将其发送回之间所经过的时间(以毫秒为单位)。
我们忽略什么因素?
让我们对讨论的范围作一些说明,此处的讨论不包括花费在网络上的时间和花费在IIS上的时间以及应用程序池启动。如果应用程序池未启动并且正在运行,则第一个请求可能会影响API的总体响应时间。我们可以使用一个应用程序初始化模块解决这个问题,但这超出了本文的范围。
第一次尝试
捕获API响应时间的一种非常容易想到的方法是在开始和结束时向每个API方法添加代码,然后测量增量以计算响应时间,如下所示。
// GET api/values/5
[HttpGet]
public IActionResult Get() {
// Start the watch
var watch = new Stopwatch();
watch.Start();
// Your actual Business logic
// End the watch
watch.Stop();
var responseTimeForCompleteRequest = watch.ElapsedMilliseconds;
}
该代码应该能够计算出操作所花费的时间。但是,由于以下原因,这似乎不是一个良好的方法。
如果一个API有很多操作,那么我们需要将此代码添加到多个位置,这将不利于维护。
此代码仅测量方法花费的时间,不测量在中间件,过滤器,控制器选择,操作方法选择,模型绑定等其他活动上花费的时间。
第二次尝试
让我们尝试通过将代码集中在一个地方来改进上述代码,以便于维护。我们需要在执行方法之前和之后执行响应时间计算代码。如果您使用过早期版本的Asp.net Web API,您将熟悉过滤器的概念。过滤器使您可以在请求处理管道中的特定阶段之前或之后运行代码。
我们将实现一个过滤器,用于计算响应时间,如下所示。我们将创建一个过滤器并使用OnActionExecuting启动计时器,然后在OnActionExecuted方法中停止计时器,从而计算API的响应时间。
public class ResponseTimeActionFilter: IActionFilter {
private const string ResponseTimeKey = "ResponseTimeKey";
public void OnActionExecuting(ActionExecutingContext context) {
// Start the timer
context.HttpContext.Items[ResponseTimeKey] = Stopwatch.StartNew();
}
public void OnActionExecuted(ActionExecutedContext context) {
Stopwatch stopwatch = (Stopwatch) context.HttpContext.Items[ResponseTimeKey];
// Calculate the time elapsed
var timeElapsed = stopwatch.Elapsed;
}
}
该代码仍然不是用于计算响应时间的最可靠技术,因为它没有解决计算执行中间件,控制器选择,操作方法选择,模型绑定等所花费的时间的问题。过滤器管道在MVC选择要执行的动作。因此,它实际上无法衡量花费在Other Asp.net管道中的时间。

第三次尝试
哪么最终我们选择使用Asp.net Core中间件来计算API的响应时间。
那么,什么是中间件?
基本上,中间件是处理请求/响应的软件组件。中间件被组装到应用程序管道中并服务于传入的请求。每个组件执行以下操作。
选择是否将请求传递到管道中的下一个组件。 可以在调用管道中的下一个组件之前和之后执行工作。
如果您在ASP.NET中使用过HTTPModules或HTTPHandlers,则可以将中间件视为ASP.NET Core中的替代品。中间件的一些示例是-
MVC Middleware
Authentication
Static File Serving
Caching
CORS

我们希望添加代码以在请求进入ASP.NET Core管道后启动计时器,并在管道处理响应后停止计时器。在请求管道的开始处使用自定义中间件似乎是最好的方法,它可以尽早获取对请求的访问,并在管道中执行最后一步之前进行访问。
我们将构建一个响应时间中间件,将其作为第一个中间件添加到请求管道中,以便我们可以在请求进入Asp.net核心管道后立即启动计时器。
响应时间数据怎么办?
捕获响应时间数据后,我们可以通过以下方式处理数据。
将响应时间数据添加到报告数据库或分析解决方案。 将响应时间数据写入日志文件。 将响应时间数据传递到消息队列,该消息队列可以由另一个应用程序进一步处理以进行报告和分析。 使用响应头将响应时间信息发送到使用我们的Rest API的客户端应用程序。
使用响应时间数据可能还有其他有用的方法。请发表评论,告诉我您如何处理应用程序中的响应时间数据。
让我们写代码
我们将考虑以下几点来编写代码。
计算API的响应时间数据 通过在Response Header中传递数据,将数据返回客户端应用程序。
下面显示了ResponseTimeMiddleware的完整代码段。
public class ResponseTimeMiddleware {
// Name of the Response Header, Custom Headers starts with "X-"
private const string RESPONSE_HEADER_RESPONSE_TIME = "X-Response-Time-ms";
// Handle to the next Middleware in the pipeline
private readonly RequestDelegate _next;
public ResponseTimeMiddleware(RequestDelegate next) {
_next = next;
}
public Task InvokeAsync(HttpContext context) {
// Start the Timer using Stopwatch
var watch = new Stopwatch();
watch.Start();
context.Response.OnStarting(() => {
// Stop the timer information and calculate the time
watch.Stop();
var responseTimeForCompleteRequest = watch.ElapsedMilliseconds;
// Add the Response time information in the Response headers.
context.Response.Headers[RESPONSE_HEADER_RESPONSE_TIME] = responseTimeForCompleteRequest.ToString();
return Task.CompletedTask;
});
// Call the next delegate/middleware in the pipeline
return this._next(context);
}
}
代码说明
有趣的部分发生在InvokeAsync方法中,一旦请求进入请求的第一个中间件,我们就使用Stopwatch类来启动秒表,然后在处理完请求并将响应准备好发送回给应用程序后停止秒表。OnStarting方法提供了编写自定义代码的机会,以添加将在响应标头发送到客户端之前要调用的委托。
最后,我们在“自定义标头”中添加“响应时间”信息。我们使用X-Response-Time-ms作为响应标头。按照惯例,自定义标头以X开头。
结论
在本文中,我们了解了如何利用ASP.NET中间件来管理横切关注点,例如测量API的响应时间。使用中间件还有许多其他有用的用例,它们可以帮助重用代码并提高应用程序的可维护性。