É 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
   }

}
user2315985
fonte
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 .

SoftwareFactor
fonte
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 .

Venkatesh Muniyandi
fonte
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.

Prerak K
fonte
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);
}
LB2
fonte
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.)
            }
        }
    }
EMC
fonte
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);

        }
    }
}
user3682728
fonte