中间件引起的接口请求参数被拦截,导致参数一直是null,这问题困扰了我很久,值得记录
1.场景
1.1 客户端使用framework4.8做一个接口请求发送:
- public static class ApiHelper
- {
- private static string Internal_ApiUrl = string.Empty;
- private static string Client_ApiUrl = string.Empty;
- static ApiHelper()
- {
- Internal_ApiUrl = ConfigurationManager.AppSettings["Internal_ApiUrl"];
- Client_ApiUrl = ConfigurationManager.AppSettings["Client_ApiUrl"];
- }
- public static string GetLicenseUrl()
- {
- return Internal_ApiUrl + "/Api/License/GetLicense";
- }
- public static WebApiCallBack GetLicense(string enterpriseName, string uniqueCode,bool IsExistLicense)
- {
- FMLicense fMLicense = new FMLicense { enterpriseName = enterpriseName, uniqueCode = uniqueCode, isExistLicense = IsExistLicense };
- var jsonBody = JsonConvert.SerializeObject(fMLicense, new JsonSerializerSettings
- {
- ContractResolver = new CamelCasePropertyNamesContractResolver()
- });
- return RequestSend(GetLicenseUrl(), "POST", jsonBody);
- }
- public static WebApiCallBack RequestSend(string serviceUrl, string method, string bodyJson)
- {
- ServicePointManager.Expect100Continue = false;
- var handler = new HttpClientHandler();
- using (var client = new HttpClient(handler))
- {
- Console.WriteLine(bodyJson);
- var content = new StringContent(bodyJson, Encoding.UTF8, "application/json");
- content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
- var response = client.PostAsync(serviceUrl, content).Result;
- string result = response.Content.ReadAsStringAsync().Result;
- Console.WriteLine(result);
- return JsonConvert.DeserializeObject<WebApiCallBack>(result);
- }
- }
- }
复制代码 1.2 服务端
- [ApiController]
- [Route("Api/[controller]/[action]")]
- public class LicenseController : ControllerBase
- {
- private readonly IHttpContextAccessor _httpContextAccessor;
- private readonly ILicenseService _licenseService;
- public LicenseController(ILicenseService licenseService, IHttpContextAccessor httpContextAccessor)
- {
- this._httpContextAccessor = httpContextAccessor;
- this._licenseService = licenseService;
- }
- [HttpPost]
- public async Task<WebApiCallBack> GetLicense(FMLicense License)
- {
- FMLicense fMLicense = License;
- var result = new WebApiCallBack();
- if (fMLicense == null)
- {
- result.code = GlobalStatusCodes.Status400BadRequest;
- result.msg = "实体参数为空";
- return result;
- }
- else
- {
- #region # 验证
- if (string.IsNullOrEmpty(fMLicense.enterpriseName))
- {
- result.code = GlobalStatusCodes.Status400BadRequest;
- result.msg = "实体参数为空";
- result.otherData = fMLicense;
- return result;
- }
- if (string.IsNullOrEmpty(fMLicense.uniqueCode))
- {
- result.code = GlobalStatusCodes.Status400BadRequest;
- result.msg = "机器唯一码不可为空!";
- result.otherData = fMLicense;
- return result;
- }
- #endregion
- //业务逻辑
-
- return result;
- }
- }
复制代码 1.3 写了一个中间件RequRespLogMildd ,记录请求和返回数据的日志
- public class RequRespLogMildd
- {
- private readonly RequestDelegate _next;
- public RequRespLogMildd(RequestDelegate next)
- {
- _next = next;
- }
- public async Task InvokeAsync(HttpContext context)
- {
- if (AppSettingsConstVars.MiddlewareRequestResponseLogEnabled)
- {
- // 过滤,只有接口
- if (context.Request.Path.Value.Contains("api") || context.Request.Path.Value.Contains("Api"))
- {
- //context.Request.EnableBuffering();
- Stream originalBody = context.Response.Body;
- try
- {
- // 存储请求数据
- await RequestDataLog(context);
- using (var ms = new MemoryStream())
- {
- context.Response.Body = ms;
- await _next(context);
- // 存储响应数据
- ResponseDataLog(context.Response, ms);
- ms.Position = 0;
- await ms.CopyToAsync(originalBody);
- }
- }
- catch (Exception ex)
- {
- // 记录异常
- //ErrorLogData(context.Response, ex);
- Parallel.For(0, 1, e =>
- {
- LogLockHelper.OutErrorLog("ErrorLog", "ErrorLog" + DateTime.Now.ToString("yyyy-MM-dd-HH"), new string[] { "Request Data:", ex.Message, ex.StackTrace });
- });
- }
- finally
- {
- context.Response.Body = originalBody;
- }
- }
- else
- {
- await _next(context);
- }
- }
- else
- {
- await _next(context);
- }
- }
- private async Task RequestDataLog(HttpContext context)
- {
- var request = context.Request;
- var sr = new StreamReader(request.Body);
- var content = $" QueryData:{request.Path + request.QueryString}\r\n BodyData:{await sr.ReadToEndAsync()}";
- if (!string.IsNullOrEmpty(content))
- {
- Parallel.For(0, 1, e =>
- {
- LogLockHelper.OutSql2Log("RequestResponseLog", "RequestResponseLog" + DateTime.Now.ToString("yyyy-MM-dd-HH"), new string[] { "Request Data:", content });
- });
- //request.Body.Position = 0;
- }
- }
- private void ResponseDataLog(HttpResponse response, MemoryStream ms)
- {
- ms.Position = 0;
- var ResponseBody = new StreamReader(ms).ReadToEnd();
- // 去除 Html
- var reg = "<[^>]+>";
- var isHtml = Regex.IsMatch(ResponseBody, reg);
- if (!string.IsNullOrEmpty(ResponseBody))
- {
- Parallel.For(0, 1, e =>
- {
- LogLockHelper.OutSql2Log("RequestResponseLog", "RequestResponseLog" + DateTime.Now.ToString("yyyy-MM-dd-HH"), new string[] { "Response Data:", ResponseBody });
- });
- }
- }
- }
复制代码 以上中间件,在后端Program类中使用 app.UseRequestResponseLog();
不管使用客户端/postman/apifox 调用接口GetLicense时都会报错,请求的json格式一直错误,错误信息如下- {
- "errors": {
- "": [
- "A non-empty request body is required."
- ],
- "license": [
- "The License field is required."
- ]
- },
- "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
- "title": "One or more validation errors occurred.",
- "status": 400,
- "traceId": "00-deaf4252040738321b26fc1fd3718696-ea7ecf0fa2bb0491-00"
- }
复制代码 1.4 错误原因是 Web API pipeline 被修改
某些中间件可能拦截请求流(body),例如使用了某些日志中间件或反复读取 body 的 filter,可能会导致模型绑定失败。检查 Startup.cs 或 Program.cs 中是否有读取 Request.Body 的地方。
ASP.NET Core 的模型绑定器只能读取一次 HttpRequest.Body。你在 RequestDataLog() 中读取了 Body,但没有重置流的位置:- var sr = new StreamReader(request.Body);
- var content = $"... {await sr.ReadToEndAsync()}";
复制代码 之后没有重置 request.Body.Position = 0;,所以模型绑定器读到的是空流。
2 解决方案
要 读取并保留请求体内容供后续使用,你需要:
- 启用请求体缓冲:context.Request.EnableBuffering();
- 读取后重置流的位置:request.Body.Position = 0;
优化后的代码:- public class RequRespLogMildd
- {
- private readonly RequestDelegate _next;
- public RequRespLogMildd(RequestDelegate next)
- {
- _next = next;
- }
- public async Task InvokeAsync(HttpContext context)
- {
- if (AppSettingsConstVars.MiddlewareRequestResponseLogEnabled)
- {
- if (context.Request.Path.Value.Contains("api", StringComparison.OrdinalIgnoreCase))
- {
- Stream originalBody = context.Response.Body;
- try
- {
- // ✅ 启用请求体缓冲
- context.Request.EnableBuffering();
- // 存储请求数据
- await RequestDataLog(context);
- using (var ms = new MemoryStream())
- {
- context.Response.Body = ms;
- await _next(context);
- // 存储响应数据
- ResponseDataLog(context.Response, ms);
- ms.Position = 0;
- await ms.CopyToAsync(originalBody);
- }
- }
- catch (Exception ex)
- {
- Parallel.For(0, 1, e =>
- {
- LogLockHelper.OutErrorLog("ErrorLog", "ErrorLog" + DateTime.Now.ToString("yyyy-MM-dd-HH"), new[] { "Request Data:", ex.Message, ex.StackTrace });
- });
- }
- finally
- {
- context.Response.Body = originalBody;
- }
- }
- else
- {
- await _next(context);
- }
- }
- else
- {
- await _next(context);
- }
- }
- private async Task RequestDataLog(HttpContext context)
- {
- var request = context.Request;
- // ✅ 读取前先设置 Position = 0
- request.Body.Position = 0;
- //// leaveOpen: true 确保读取后流还可以被使用
- //是否根据字节顺序标记(BOM)来检测编码:true(默认)检测 BOM,如果发现 BOM,则用它指定的编码代替传入的 Encoding.UTF8;false 不检测 BOM,严格使用你传入的 Encoding.UTF8。
- using var reader = new StreamReader(request.Body, encoding: Encoding.UTF8, detectEncodingFromByteOrderMarks: false, leaveOpen: true);
- var body = await reader.ReadToEndAsync();
- // ✅ 读取完之后重置位置供后续使用
- request.Body.Position = 0;
- var content = $" QueryData:{request.Path + request.QueryString}\r\n BodyData:{body}";
- if (!string.IsNullOrEmpty(content))
- {
- Parallel.For(0, 1, e =>
- {
- LogLockHelper.OutSql2Log("RequestResponseLog", "RequestResponseLog" + DateTime.Now.ToString("yyyy-MM-dd-HH"), new[] { "Request Data:", content });
- });
- }
- }
- private void ResponseDataLog(HttpResponse response, MemoryStream ms)
- {
- ms.Position = 0;
- var ResponseBody = new StreamReader(ms).ReadToEnd();
- var reg = "<[^>]+>";
- var isHtml = Regex.IsMatch(ResponseBody, reg);
- if (!string.IsNullOrEmpty(ResponseBody))
- {
- Parallel.For(0, 1, e =>
- {
- LogLockHelper.OutSql2Log("RequestResponseLog", "RequestResponseLog" + DateTime.Now.ToString("yyyy-MM-dd-HH"), new[] { "Response Data:", ResponseBody });
- });
- }
- }
- }
复制代码 如果本文介绍对你有帮助,可以一键四连:点赞+评论+收藏+推荐,谢谢!
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |