【发布时间】:2020-04-21 11:43:08
【问题描述】:
我有一个 dotnetcore 3.1 API 项目,它接收 HTTP 请求,将相关数据传递给服务层,然后服务层执行业务逻辑并将数据写入数据库。很标准的东西。此外,我有一个自定义中间件类,它使用秒表来分析传入请求的执行时间,并将 URI、时间戳、经过时间、请求标头/正文、响应状态代码和响应标头/正文记录到我的数据库中以进行分析和调试目的。
当我在 IIS 中建立 API 并使用 Postman 发布具有给定 JSON 正文的请求时,一切正常;中间件记录请求,数据按预期写入数据库。但是,仅在使用 .net 的内存中 TestServer 运行我的集成测试套件时,POST 请求由于请求正文为空(它是一个空字符串,甚至不是一个空的 JSON 对象)而失败.
我怀疑这是由于在中间件读取请求流后以某种方式错误地重置了请求流,但令人费解的是 HttpContext.Request 在中间件运行之前已经为空。单步执行代码时,我已确认 HttpRequestMessage 内容设置正确,JSON 正确,并且在未使用中间件时测试通过,因此据我所知,测试逻辑不是问题.在中间件 InvokeAsync 中设置断点并检查 pHttpContext 的值表明内容已经为空。
这是我的控制器代码的简化版本以及集成测试方法和中间件的代码。任何帮助将不胜感激。
控制器方法:
[HttpPost]
public IActionResult Create([FromBody] Widget pWidget)
{
_WidgetService.CreateWidget(new CreateWidget()
{
WidgetNo = pWidget.WidgetNo,
StatusId = pWidget.StatusId,
WidgetTypeId = pWidget.WidgetTypeId,
CreatedBy = pWidget.CreatedBy
});
return Ok();
}
测试方法:
[Theory]
[InlineData("/api/v1/widget")]
public async Task PostCreatesWidget(String pUrl)
{
// Arrange
List<SelectWidget> originalWidgets = _widgetService.GetAllWidgets().ToList();
Widget createdWidget = _widgetGenerator.GenerateModel();
String json = JsonConvert.SerializeObject(createdWidget);
Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpRequestMessage request = new HttpRequestMessage()
{
RequestUri = new Uri(pUrl, UriKind.Relative),
Content = new StringContent(json, Encoding.UTF8, "application/json"),
Method = HttpMethod.Post,
};
// Act
HttpResponseMessage response = await Client.SendAsync(request);
String responseContent = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
List<SelectWidget> newWidgets = _widgetService.GetAllWidgets().ToList();
Assert.True(newWidgets.Count == originalWidgets.Count + 1);
}
中间件:
public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly IRequestLoggingService _requestLoggingService;
public RequestLoggingMiddleware(RequestDelegate pNext, IRequestLoggingService pRequestLoggingService)
{
_next = pNext;
_requestLoggingService = pRequestLoggingService;
}
public async Task InvokeAsync(HttpContext pHttpContext)
{
try
{
HttpRequest request = pHttpContext.Request;
if (request.Path.StartsWithSegments(new PathString("/api")))
{
Stopwatch stopwatch = Stopwatch.StartNew();
DateTime requestTime = DateTime.UtcNow;
String requestBodyContent = await ReadRequestBody(request);
Stream bodyStream = pHttpContext.Response.Body;
using (MemoryStream responseBody = new MemoryStream())
{
HttpResponse response = pHttpContext.Response;
response.Body = responseBody;
await _next(pHttpContext);
stopwatch.Stop();
String responseBodyContent = null;
responseBodyContent = await ReadResponseBody(response);
await responseBody.CopyToAsync(bodyStream);
await _requestLoggingService.LogRequest(new InsertRequestLog()
{
DateRequested = requestTime,
ResponseDuration = stopwatch.ElapsedMilliseconds,
StatusCode = response.StatusCode,
Method = request.Method,
Path = request.Path,
QueryString = request.QueryString.ToString(),
RequestBody = requestBodyContent,
ResponseBody = responseBodyContent
});
}
}
else
{
await _next(pHttpContext);
}
}
catch (Exception)
{
await _next(pHttpContext);
}
}
private async Task<String> ReadRequestBody(HttpRequest pRequest)
{
pRequest.EnableBuffering();
Byte[] buffer = new Byte[Convert.ToInt32(pRequest.ContentLength)];
await pRequest.Body.ReadAsync(buffer, 0, buffer.Length);
String bodyAsText = Encoding.UTF8.GetString(buffer);
pRequest.Body.Seek(0, SeekOrigin.Begin);
return bodyAsText;
}
private async Task<String> ReadResponseBody(HttpResponse pResponse)
{
pResponse.Body.Seek(0, SeekOrigin.Begin);
String bodyAsText = await new StreamReader(pResponse.Body).ReadToEndAsync();
pResponse.Body.Seek(0, SeekOrigin.Begin);
return bodyAsText;
}
}
【问题讨论】:
-
1990 年代打来电话,他们想要恢复他们的匈牙利符号。 ;)
-
我猜这与异步状态正确流向您的“中间件”有关。我将首先阅读有关正确使用 HttpContext + async/await 的信息,看看您是否有问题。
标签: c# .net-core asp.net-core-webapi asp.net-core-middleware