操作过滤器中的Web Api请求内容为空

时间:2014-01-25 14:13:22

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

我有一个名为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
    }
}

问题是在ActionExecuting或ActionExecuted中内容始终为空。

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

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

4 个答案:

答案 0 :(得分:30)

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

你可以尝试:

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
            }
        }
    }

答案 1 :(得分:15)

这种方法对我有用:

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

答案 2 :(得分: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());

答案 3 :(得分:0)

这对我有用:

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));
                    }
                }                   
    }