【问题标题】:Web Api Request Content is empty in action filterWeb Api 请求内容在操作过滤器中为空
【发布时间】:2014-02-16 13:34:15
【问题描述】:

我有一个名为Log 的属性,它尝试将请求和响应的内容记录到文本文件中。我已经把它放在我的控制器上以涵盖所有操作。在 LogAttribute 中,我将内容作为字符串 (ReadAsStringAsync) 读取,因此我不会丢失请求正文。

public class LogAttribute : ActionFilterAttribute
{
    // ..
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        // stuff goes here
        var content = actionContext.Request.Content.ReadAsStringAsync().Result; 
        // content is always empty because request body is cleared
    }

    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        // other stuff goes here
        var content = actionContext.Request.Content.ReadAsStringAsync().Result;
        // content is always empty because request body is cleared
    }

    // ..
}

另一方面,我将FromBody 属性放在我的操作参数类之前,以利用它的好处。

[Log]
public class SomethingController
{
    public HttpResponseMessage Foo([FromBody] myModel)
    {
        // something
    }
}

问题是ActionExecutingActionExecuted 中的内容总是空的。

我认为这是因为 FromBody 在我的 Log 属性之前运行,这与它们在代码中的顺序不同。我再次认为这是因为根据操作参数(路由处理)找到请求的最佳操作/控制器匹配。之后我的请求正文被清除,因为请求正文在 WebApi 中没有缓冲。

我想知道是否有任何方法可以更改FromBody 属性和我的Log 属性的运行时间顺序?或其他解决问题的方法!我应该提一下,我不想删除 FromBody 并使用 HttpRequestMessage 代替我的模型或类似的东西。

【问题讨论】:

  • 除此之外,您的异步处理是错误的。使用 .Result 是危险的,如果可能的话,你应该避免这种情况。而是覆盖允许您等待的 OnActionExecutingAsync 方法。
  • @LukasK 实际上这是为了解释我的问题而写的。但你是对的。

标签: c# asp.net asp.net-mvc asp.net-web-api


【解决方案1】:
public class ContentInterceptorHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (request.Content != null)
        {
            var requestBody = await request.Content.ReadAsStringAsync();
            request.Properties["Content"] = requestBody;
            request.Content = new StringContent(requestBody, Encoding.UTF8, request.Content.Headers.ContentType.MediaType);
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

public class LogRequestAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (!actionContext.Request.Properties.TryGetValue("Content", out var body))
            return;

        Console.WriteLine(body);
    }
}

并添加启动

httpConfiguration.MessageHandlers.Add(new ContentInterceptorHandler());

【讨论】:

    【解决方案2】:

    这对我有用:

    public override async Task OnActionExecutedAsync(HttpActionExecutedContext context, CancellationToken cancellationToken) {
    
                    var requestLog = context.Request;
                    if (requestLog != null)
                    {
                        _logger.DebugFormat("Request: {0}", requestLog?.ToString());
                        var requestBody = context.ActionContext?.ActionArguments;
                        if (requestBody != null)
                        {
                            _logger.DebugFormat("Body: {0}", JsonConvert.SerializeObject(requestBody));
                        }
                    }                   
        }
    

    【讨论】:

      【解决方案3】:

      请求正文是不可回退的流;它只能读取一次。格式化程序已经读取了流并填充了模型。我们无法在操作过滤器中再次读取流。

      你可以试试:

      public class LogAttribute : ActionFilterAttribute
      {
          public override void OnActionExecuting(HttpActionContext actionContext)
          {
              var myModel = actionContext.ActionArguments["myModel"]; 
      
          }
      
          public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
          {
              var myModel = actionContext.ActionArguments["myModel"]; 
          }
      }
      

      实际上,ActionArguments 只是一个字典,如果我们需要避免硬编码的参数名称("myModel"),我们可以循环它。当我们创建一个通用动作过滤器,它需要针对某些特定要求处理类似对象的类时,我们可以让我们的模型实现一个接口 => 知道哪个参数是我们需要处理的模型,我们可以调用这些方法界面。

      示例代码:

      public class LogAttribute : ActionFilterAttribute
          {
              public override void OnActionExecuting(HttpActionContext actionContext)
              {
                  foreach(var argument in actionContext.ActionArguments.Values.Where(v => v is ILogable)))
                  {
                       ILogable model = argument as ILogable;//assume that only objects implementing this interface are logable
                       //do something with it. Maybe call model.log
                  }
              }
      
              public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
              {
                  foreach(var argument in actionContext.ActionArguments.Values.Where(v => v is ILogable)))
                  {
                       ILogable model = argument as ILogable;//assume that only objects implementing this interface are logable
                       //do something with it. Maybe call model.log
                  }
              }
          }
      

      【讨论】:

      • 谢谢兄弟。如果我有多个参数但我不知道它们的名字怎么办?在这个解决方案中,我应该设置一个“始终我的参数名称是 myModel”的合同?是吗?
      • @Reza Ahmadi:ActionArguments 只是一本字典。你可以循环它。
      • @Reza Ahmadi:通常,当您想将这种过滤器应用于针对一类类似对象的某些特定操作时。你可以让你的模型实现一个接口,当循环遍历字典时,你有一个线索表明这是你需要处理的模型。 (可能通过接口调用方法)
      【解决方案4】:

      这种方法对我有用:

      using (var stream = new MemoryStream())
      {
          var context = (HttpContextBase)Request.Properties["MS_HttpContext"];
          context.Request.InputStream.Seek(0, SeekOrigin.Begin);
          context.Request.InputStream.CopyTo(stream);
          string requestBody = Encoding.UTF8.GetString(stream.ToArray());
      }
      

      为我返回了触发日志记录或异常情况的操作参数对象的 json 表示。

      发现为已接受的答案here

      【讨论】:

      • @darasd 一种替代方法是从actionContext.ActionArguments 中提取请求对象,因为参数将保存传递给请求的所有内容。查看此链接以获取更多信息。 (github.com/aspnet/Mvc/issues/5260#issuecomment-245947206)
      • 我使用 Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll 并且没有 Popertiy "MS_HttpContext"。
      猜你喜欢
      • 1970-01-01
      • 2013-08-22
      • 2015-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-06
      相关资源
      最近更新 更多