Web Api Request Content is empty in action filter
This approach worked for me:
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());
}
Returned for me the json representation of my action parameter object triggering the logging or exception case.
Found as accepted answer here
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);
}
}
and add in Startup
httpConfiguration.MessageHandlers.Add(new ContentInterceptorHandler());
The request body is a non-rewindable stream; it can be read only once. The formatter has already read the stream and populated the model. We're not able to read the stream again in the action filter.
You could try:
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"];
}
}
Actually, ActionArguments
is just a dictionary, we can loop though it if we need to avoid hardcoded parameter name ("myModel"
). When we create a generic action filter that needs to work on a class of similar objects for some specific requirements, we could have our models implement an interface => know which argument is the model we need to work on and we can call the methods though the interface.
Example code:
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
}
}
}