É necessário registrar o corpo da solicitação e resposta do asp.net webapi 2 em um banco de dados


103

Estou usando o Microsoft Asp.net WebApi2 hospedado no IIS. Eu gostaria muito simplesmente de registrar o corpo da solicitação (XML ou JSON) e o corpo da resposta para cada postagem.

Não há nada de especial neste projeto ou no controlador que processa a postagem. Não estou interessado em usar estruturas de registro como nLog, elmah, log4net ou os recursos de rastreamento integrados da API da web, a menos que seja necessário fazer isso.

Estou simplesmente querendo saber onde colocar meu código de registro e como obter o JSON ou XML real da solicitação e resposta de entrada e saída.

Meu método de postagem do controlador:

public HttpResponseMessage Post([FromBody])Employee employee)
{
   if (ModelState.IsValid)
   {
      // insert employee into to the database
   }

}

Você está procurando registrar Solicitação / Resposta para uma ação específica, um conjunto ou todas as suas ações em um controlador específico?
— LB2

Interessado apenas em registrar Post. (a) Hora da postagem (b) corpo do xml ou json postado (c) resposta (o conteúdo xml ou json) junto com o código de status Http
— usuário 2315985

A razão pela qual eu estava perguntando é para sugerir se colocar o código diretamente em ação ou uma solução genérica para todas as ações. Veja minha resposta abaixo.
— LB2

Para sua informação, removi o asp.net, pois não tem relação com esta questão
— Dalorzo

criar um arquivador não é uma opção?
— Prerak K de

Respostas:


193

Eu recomendaria usar um DelegatingHandler. Então você não precisará se preocupar com nenhum código de registro em seus controladores.

public class LogRequestAndResponseHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (request.Content != null)
        {
            // log request body
            string requestBody = await request.Content.ReadAsStringAsync();
            Trace.WriteLine(requestBody);
        }
        // let other handlers process the request
        var result = await base.SendAsync(request, cancellationToken);

        if (result.Content != null)
        {
            // once response body is ready, log it
            var responseBody = await result.Content.ReadAsStringAsync();
            Trace.WriteLine(responseBody);
        }

        return result;
    }
}

Basta substituir Trace.WriteLinepor seu código de registro e registrar o manipulador WebApiConfigassim:

config.MessageHandlers.Add(new LogRequestAndResponseHandler());

Aqui está a documentação completa da Microsoft para manipuladores de mensagens .


3
task.Result.Contentretorna System.Net.Http.ObjectContent. Existe uma maneira de obter o xml / json bruto em vez disso?
— PC.

4
@SoftwareFactor: ContinueWithe Resultsão APIs perigosas. Seria muito melhor usar em awaitvez disso, ou seja,var result = await base.SendAsync(request, cancellationToken); var resposeBody = await response.Content.ReadAsStringAsync(); Trace.WriteLine(responseBody); return response;
— Stephen Cleary

9
Esta é uma solução muito interessante, mas gerará um erro quando a resposta não tiver corpo. Mas isso é fácil de verificar e corrigir :)
— buddybubble

6
A chamada para await request.Content.ReadAsStringAsync();não resulta em um erro informando que o fluxo de solicitação já foi lido em certas circunstâncias?
— Gavin

6
Se o manipulador de delegação ler o corpo da solicitação, isso não o tornaria indisponível para o manipulador de terminal real (ou seja, mvc / webapi)?
— LB2

15

Existem várias abordagens para lidar genericamente com o registro de Solicitação / Resposta para todas as chamadas de método WebAPI:

  1. ActionFilterAttribute: Pode-se escrever ActionFilterAttributemétodos personalizados e decorar os métodos de controle / ação para habilitar o registro.

    Contra: Você precisa decorar cada controlador / método (ainda pode fazer isso no controlador de base, mas ainda não aborda questões transversais.

  2. Substitua BaseControllere controle o registro lá.

    Contra: Estamos esperando / forçando os controladores a herdar de um controlador de base personalizado.

  3. Usando DelegatingHandler.

    Vantagem: Não estamos mexendo no controlador / método aqui com esta abordagem. O manipulador de delegação fica isolado e trata perfeitamente o registro de solicitações / respostas.

Para obter mais artigos detalhados, consulte este http://weblogs.asp.net/fredriknormen/log-message-request-and-response-in-asp-net-webapi .


Você pode atribuir qualquer filtro de ação da seguinte forma: public static class WebApiConfig {public static void Register (HttpConfiguration config) {// Configuração e serviços da API da Web config.Filters.Add (new MyFilter ()) // Rotas da API da Web config.MapHttpAttributeRoutes (); config.Routes.MapHttpRoute (nome: "DefaultApi", routeTemplate: "api / {controlador} / {id}", padrões: novo {id = RouteParameter.Optional}); }}
— Mika Karjunen

11

Uma das opções que você tem é criar um filtro de ação e decorar seu WebApiController / ApiMethod com ele.

Atributo de filtro

public class MyFilterAttribute : System.Web.Http.Filters.ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            if (actionContext.Request.Method == HttpMethod.Post)
            {
                var postData = actionContext.ActionArguments;
                //do logging here
            }
        }
    }

Controlador WebApi

[MyFilterAttribute]
public class ValuesController : ApiController{..}

ou

[MyFilterAttribute]
public void Post([FromBody]string value){..}

Espero que isto ajude.


Gosto dessa abordagem, mas, para obter a resposta, preciso substituir OnActionExecuted. O problema é que a solicitação nesse ponto já foi convertida para meu POCO em vez de ser o xml ou json. Alguma ideia?
— user2315985

Originalmente, eu queria dizer registrar dados em OnActionExecuting e simplesmente deixar o post fazer seu trabalho. O que eu entendi da sua pergunta é que você só quer registrar os dados de cada postagem feita.
— Prerak K de

3
Quero registrar a solicitação e os dados de resposta sempre que alguém postar.
— user2315985

2
você pode usar OnActionExecuted e tentar "(actionExecutedContext.ActionContext.Response.Content as ObjectContent) .Value.ToString ()" para obter a resposta e registrá-la.
— Prerak K de

Como obtenho a solicitação de OnActionExecuted?
— user2315985

3

Obter acesso para solicitar mensagem é fácil. Sua classe baseApiController contém .Requestpropriedade que, como o nome sugere, contém a solicitação na forma analisada. Você simplesmente examina o que deseja registrar e passa para a sua instalação de registro, seja ele qual for. Este código você pode colocar no início de sua ação, se precisar fazê-lo por apenas um ou um punhado.

Se você precisa fazer isso em todas as ações (todas significando mais do que um punhado gerenciável), então o que você pode fazer é substituir o .ExecuteAsyncmétodo para capturar cada chamada de ação para seu controlador.

public override Task<HttpResponseMessage> ExecuteAsync(
    HttpControllerContext controllerContext,
    CancellationToken cancellationToken
)
{
    // Do logging here using controllerContext.Request
    return base.ExecuteAsync(controllerContext, cancellationToken);
}

Estou fazendo isso, e ainda não fiz o benchmarking, só minha intuição me diz que pode ser muito lento?
— Marcus de

Por que você acha que seria lento? ExecuteAsyncé o que é chamado pelo framework, e a implementação da classe do controlador base é o que realmente executa a ação. Isso é apenas chamar o seu registro como parte da execução que já está acontecendo. A única penalidade aqui é o momento de fazer o registro real.
— LB2

Não, quero dizer, 'muito lento', como registrar todas as solicitações.
— Marcus de

2
Bem, isso é uma questão de requisitos, e esse é o requisito declarado pelo OP. É uma questão de volume que o site lida, o desempenho da facilidade de registro, etc. Isso está além da publicação dos OPs.
— LB2

0

Este parece ser um tópico muito antigo, mas vale a pena compartilhar outra solução.

Você pode adicionar este método em seu arquivo global.asax, que será acionado a cada após o término da solicitação HTTP.

void Application_EndRequest(Object Sender, EventArgs e)
    {
        var request = (Sender as HttpApplication).Request;
        var response = (Sender as HttpApplication).Response;

        if (request.HttpMethod == "POST" || request.HttpMethod == "PUT")
        {


            byte[] bytes = request.BinaryRead(request.TotalBytes);
            string body = Encoding.UTF7.GetString(bytes);
            if (!String.IsNullOrEmpty(body))
            {


                // Do your logic here (Save in DB, Log in IIS etc.)
            }
        }
    }

0

Esse é um tópico muito antigo, mas eu gastei muito tempo (pesquise na internet) fazendo essas coisas, então irei apenas postar minha solução aqui.

Conceito

  1. Substitua ExecuteAsync do método APicontroller para rastrear a solicitação de entrada, em minha solução eu crio Base_ApiController como um pai dos controladores de API do meu projeto.
  2. Use System.Web.Http.Filters.ActionFilterAttribute para rastrear a resposta de saída do controlador de API
  3. *** (Adicional) *** Use System.Web.Http.Filters.ExceptionFilterAttribute para registrar quando ocorrer uma exceção.

1. MyController.cs

    [APIExceptionFilter]  // use 3.
    [APIActionFilter]     // use 2.
    public class Base_APIController : ApiController
    {
        public   bool  IsLogInbound
        {
            get
            { return   ConfigurationManager.AppSettings["LogInboundRequest"] =="Y"? true:false ;     }
        }
        /// <summary>
        /// for logging exception
        /// </summary>
        /// <param name="controllerContext"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override Task<HttpResponseMessage> ExecuteAsync(
         HttpControllerContext controllerContext,
         CancellationToken cancellationToken
         )
        {
            // Do logging here using controllerContext.Request
            // I don't know why calling the code below make content not null Kanit P.
            var content = controllerContext.Request.Content.ReadAsStringAsync().Result.ToString(); // keep request json content
             // Do your own logging!
            if (IsLogInbound)
            {
                try
                {
                    ErrLog.Insert(ErrLog.type.InboundRequest, controllerContext.Request,
                         controllerContext.Request.RequestUri.AbsoluteUri
                         , content);
                }
                catch (Exception e) { }
            }

            // will not log err when go to wrong controller's action (error here but not go to APIExceptionFilter)
            var t = base.ExecuteAsync(controllerContext, cancellationToken);
            if (!t.Result.IsSuccessStatusCode)
            { 
            }
            return t;

        }

2. APIActionFilter.cs

    public class APIActionFilter : System.Web.Http.Filters.ActionFilterAttribute
    {
        public bool LogOutboundRequest
        {
            get
            { return ConfigurationManager.AppSettings["LogInboundRequest"] == "Y" ? true : false; }
        }

        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
            try {

                var returndata = actionExecutedContext.Response.Content.ReadAsStringAsync().Result.ToString(); 
             //keep Json response content
             // Do your own logging!
                if (LogOutboundRequest)
                {
                    ErrLog.Insert(ErrLog.type.OutboundResponse, actionExecutedContext.Response.Headers,
                       actionExecutedContext.ActionContext.ControllerContext.ControllerDescriptor.ControllerName
                      + "/"
                      + actionExecutedContext.ActionContext.ActionDescriptor.ActionName
                      , returndata );
                }
            } catch (Exception e) {

            }
     

        } 
    }
}

3. APIExceptionFilter.cs

    public class APIExceptionFilter : ExceptionFilterAttribute
    {
    public bool IsLogErr
    {
        get
        { return ConfigurationManager.AppSettings["LogExceptionRequest"] == "Y" ? true : false; }
    }


    public override void OnException(HttpActionExecutedContext context)
    {
        try
        { 
            //Do your own logging!
            if (IsLogErr)
            {
                ErrLog.Insert(ErrLog.type.APIFilterException, context.Request,
                    context.ActionContext.ControllerContext.ControllerDescriptor.ControllerName
                    + "/"
                    + context.ActionContext.ActionDescriptor.ActionName
                    , context.Exception.ToString() + context.Exception.StackTrace);
            }
        }catch(Exception e){

        }

        if (context.Exception is NotImplementedException)
        {
            context.Response = new HttpResponseMessage(HttpStatusCode.NotImplemented);
        }
        else {
            context.Response = new HttpResponseMessage(HttpStatusCode.InternalServerError);

        }
    }
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.