Search code examples
c#asp.net-coreasp.net-core-mvc.net-core

Is there any way to get request body in .NET Core FilterAttribute?


Sample of my request

http://localhost:8065/api/note
POST
content-type:application/json
request body: { "id" : "1234", "title" : "test", "status" : "draft"}

and the response should be

{ "msg" : "ok", "code" : 1}

The action

public async Task<IActionResult> Post([FromBody]NoteModel model)

To have every request logged automatically, I created an attribute to do this job. The attribute looks like this: (from Microsoft Docs)

public class SampleActionFilterAttribute : TypeFilterAttribute
{
    public SampleActionFilterAttribute():base(typeof(SampleActionFilterImpl))
    {
    }

    private class SampleActionFilterImpl : IActionFilter
    {
        private readonly ILogger _logger;
        public SampleActionFilterImpl(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger<SampleActionFilterAttribute>();
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
        
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            _logger.LogDebug("[path]" + context.HttpContext.Request.Path);
            _logger.LogDebug("[method]" + context.HttpContext.Request.Method);
            _logger.LogDebug("[body]"); //log request body, expectation: { "id" : "1234", "title" : "test", "status" : "draft"}
            _logger.LogDebug("[statuscode]" + context.HttpContext.Response.StatusCode);
            _logger.LogDebug("[response]"); //log response
        }
    }
}

I tried to use streamReader to get the request body only got an empty string.

StreamReader reader = new StreamReader(context.HttpContext.Request.Body);
string text = reader.ReadToEnd();

Is that because the body was read by [fromBody] from the controller so the stream can not be read twice? If so, how am I supposed to get the request body and response in OnActionExecuted method?


Update:

I've just copied Set's code into my project, not working. Here is the debug gifenter image description here


Solution

  • Accordingly to this "Best way to log/read request body in a middleware" thread, the following should work:

    // using Microsoft.AspNetCore.Http.Internal;
    
    public class SampleActionFilterAttribute : TypeFilterAttribute
    {
        ... 
    
        public void OnActionExecuting(ActionExecutedContext context)
        {
            // read body before MVC action execution
            string bodyData = ReadBodyAsString(context.HttpContext.Request);
        }
    
        private string ReadBodyAsString(HttpRequest request)
        {
            var initialBody = request.Body; // Workaround
    
            try
            {
                request.EnableRewind();
    
                using (StreamReader reader = new StreamReader(request.Body))
                {
                    string text = reader.ReadToEnd();
                    return text;
                }
            }
            finally
            {
                // Workaround so MVC action will be able to read body as well
                request.Body = initialBody; 
            }
    
            return string.Empty;
        }
     }
    

    Also similar approach described in Read request body twice SO post


    Update: above approach in ReadBodyAsString with will work if used in middleware, not in action filter. The difference is that when action filter is calling (even for OnActionExecuting), the body stream already has been read and [FromBody] model has been populated.

    The good nesw is that so it is possible to get model directly in action filter by using context.ActionArguments["<model_name>"]. In your case:

    public void OnActionExecuted(ActionExecutedContext context)
    {
       var model = context.ActionArguments["model"] as NoteModel;
    }