web-dev-qa-db-fra.com

Existe-t-il un moyen d'obtenir le corps de la demande dans .NET Core FilterAttribute?

Échantillon de ma demande

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

et la réponse devrait être

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

L'action

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

Afin que chaque demande soit automatiquement enregistrée, je crée un attribut pour effectuer ce travail. L'attribut ressemble à: (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
        }
    }
}

J'essaie d'utiliser streamReader pour que le corps de la requête obtienne uniquement une chaîne vide.

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

Est-ce parce que le corps a été lu par [fromBody] à partir du contrôleur afin que le flux ne puisse pas être lu deux fois? Si oui, comment suis-je censé obtenir le corps de la demande et la réponse dans la méthode OnActionExecuted?


Mettre à jour:

Je viens de copier le code de Set dans mon projet, mais cela ne fonctionne pas. Voici le gif de débogage  enter image description here

3
wtf512

En conséquence, " Le meilleur moyen de consigner/lire le corps de la requête dans un thread " du middleware, ce qui suit devrait fonctionner:

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

Également une approche similaire décrite dans Lire le corps de la requête deux fois SO post


Update : L'approche ci-dessus dans ReadBodyAsString avec fonctionnera si elle est utilisée dans le middleware, pas dans le filtre d'action. La différence est que lorsque le filtre d'action appelle (même pour OnActionExecuting), le flux de corps a déjà été lu et [FromBody] model a été rempli.

La bonne chose est qu’il est donc possible d’obtenir le modèle directement dans le filtre d’action en utilisant context.ActionArguments["<model_name>"]. Dans ton cas:

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

Si vous utilisez IActionResult dans vos contrôleurs et que vous voulez les objets .NET, vous pouvez écrire un filtre comme celui-ci:

public class SampleFilter : IActionFilter
{
    public void OnActionExecuted(ActionExecutedContext context)
    {
        if (context.Result is ObjectResult)
        {
            var objResult = (ObjectResult)context.Result;
        }
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {

    }
}

Au moment où il atteint OnActionExecuted, la tâche ObjectResult est déjà terminée, vous pouvez donc simplement extraire la valeur. Vous pouvez également obtenir le StatusCode avec objResult.StatusCode.

Dans le contrôleur, return Ok (...) crée en fait un OkObjectResult, etc.

Si vous voulez spécifiquement le résultat sérialisé, alors la réponse de Set est plus valide.

0
John