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)
In order to have every request logged automatically, I create an attribute to do this job. The attribute looks like: (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 try to use streamReader to get request body only get empty string.
StreamReader reader = new StreamReader(context.HttpContext.Request.Body);
string text = reader.ReadToEnd();
Is that because the body was read by [fromBody] from controller so the stream can not be read twice? If so, how am I supposed to get request body and response in OnActionExecuted
method?
Update:
I've just copied Set's code into my project, not working. Here is the debug gif
Accordingly to this "Best way to log/read request body in a middleware" thread, the following should work:
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 forOnActionExecuting
), 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:If you're using IActionResult in your controllers and you want the .NET objects, you can write a filter like this:
By the point it hits OnActionExecuted, the ObjectResult task has already completed, so you can just extract the value. You can also get the StatusCode with objResult.StatusCode.
In the controller, return Ok(...) actually creates an OkObjectResult, etc.
If you specifically want the serialzied result, then Set's answer is more valid.