asp.net webapi 2 요청 및 응답 본문을 데이터베이스에 기록해야합니다.


103

IIS에서 호스팅되는 Microsoft Asp.net WebApi2를 사용하고 있습니다. 요청 본문 (XML 또는 JSON)과 각 게시물에 대한 응답 본문을 기록하고 싶습니다.

이 프로젝트 또는 게시물을 처리하는 컨트롤러에는 특별한 것이 없습니다. 필요한 경우가 아니면 nLog, elmah, log4net 또는 웹 API의 기본 제공 추적 기능과 같은 로깅 프레임 워크를 사용하는 데 관심이 없습니다.

로깅 코드를 어디에 넣을지, 수신 및 발신 요청 및 응답에서 실제 JSON 또는 XML을 얻는 방법을 알고 싶습니다.

내 컨트롤러 게시 방법 :

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

}

특정 컨트롤러의 특정 작업, 집합 또는 모든 작업에 대한 요청 / 응답을 기록하려고합니까?
— LB2

게시물 로깅에만 관심이 있습니다. (a) 게시 시간 (b) xml 또는 json 게시 된 본문 (c) Http 상태 코드와 함께 응답 (xml 또는 json 콘텐츠)
— user2315985 2014 년

내가 요청한 이유는 코드를 직접 행동에 넣을 것인지 아니면 모든 행동에 대한 일반적인 해결책을 제시 할 것인지 제안하기 위해서입니다. 아래 내 대답을 참조하십시오.
— LB2

참고로이 질문과 관련이 없기 때문에 asp.net을 제거했습니다
— Dalorzo 2014 년

파일러를 만드는 것이 옵션이 아닌가?
— Prerak K

답변:


194

나는 DelegatingHandler. 그러면 컨트롤러의 로깅 코드에 대해 걱정할 필요가 없습니다.

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

Trace.WriteLine로깅 코드로 바꾸고 다음 과 WebApiConfig같이 핸들러를 등록하십시오 .

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

다음은 메시지 처리기에 대한 전체 Microsoft 설명서입니다 .


3
task.Result.Content를 반환합니다 System.Net.Http.ObjectContent. 대신 원시 xml / json을 얻는 방법이 있습니까?
— PC.

4
@SoftwareFactor : ContinueWith및 Result위험 API가 있습니다. 그것은 사용하는 것이 훨씬 더 나은 것 await, 즉, 대신var result = await base.SendAsync(request, cancellationToken); var resposeBody = await response.Content.ReadAsStringAsync(); Trace.WriteLine(responseBody); return response;
— 스티븐 클리어 리

9
이것은 매우 멋진 솔루션이지만 응답에 본문이 없으면 오류가 발생합니다. 하지만 확인하고 고칠 수있을만큼 쉽습니다. :)
— buddybubble

6
await request.Content.ReadAsStringAsync();요청 스트림이 이미 특정 상황에서 읽 혔다는 오류가 발생하지 않도록 호출합니까 ?
— 개빈

6
위임 처리기가 요청 본문을 읽는 경우 실제 터미널 처리기 (예 : mvc / webapi)에서 사용할 수 없게되지 않습니까?
— LB2

15

모든 WebAPI 메서드 호출에 대한 요청 / 응답 로깅을 일반적으로 처리하는 방법에는 여러 가지가 있습니다.

  1. ActionFilterAttribute: 사용자 정의를 작성 ActionFilterAttribute하고 로깅을 활성화하기 위해 컨트롤러 / 액션 메서드를 장식 할 수 있습니다.

    단점 : 모든 컨트롤러 / 방법을 장식해야합니다 (여전히 기본 컨트롤러에서 할 수 있지만 교차 절단 문제는 해결되지 않습니다.

  2. BaseController거기에서 로깅을 무시 하고 처리합니다.

    단점 : 컨트롤러가 사용자 지정 기본 컨트롤러에서 상속 할 것으로 예상 / 강제합니다.

  3. 사용 DelegatingHandler.

    장점 : 여기서는이 접근 방식으로 컨트롤러 / 방법을 건드리지 않습니다. 위임 처리기는 격리되어 있으며 요청 / 응답 로깅을 정상적으로 처리합니다.

더 자세한 기사는이 http://weblogs.asp.net/fredriknormen/log-message-request-and-response-in-asp-net-webapi를 참조 하십시오 .


다음과 같이 모든 actionfilter를 할당 할 수 있습니다. public static class WebApiConfig {public static void Register (HttpConfiguration config) {// 웹 API 구성 및 서비스 config.Filters.Add (new MyFilter ()) // 웹 API 경로 config.MapHttpAttributeRoutes (); config.Routes.MapHttpRoute (name : "DefaultApi", routeTemplate : "api / {controller} / {id}", 기본값 : new {id = RouteParameter.Optional}); }}
— 미카 Karjunen

11

옵션 중 하나는 액션 필터를 만들고 WebApiController / ApiMethod를 장식하는 것입니다.

필터 속성

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

WebApi 컨트롤러

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

또는

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

도움이 되었기를 바랍니다.


이 접근 방식을 좋아하지만 응답을 얻으려면 대신 OnActionExecuted를 재정의해야합니다. 문제는 해당 시점의 요청이 이미 xml 또는 json이 아닌 내 POCO로 변환되었다는 것입니다. 이견있는 사람?
— user2315985

원래는 OnActionExecuting에 데이터를 기록한 다음 게시물이 작업을 수행하도록하는 것을 의미했습니다. 귀하의 질문에서 이해 한 것은 완료된 각 게시물에 대한 데이터를 기록하고 싶다는 것입니다.
— Prerak K

3
누군가 게시 할 때마다 요청 및 응답 데이터를 모두 기록하고 싶습니다.
— user2315985

2
OnActionExecuted를 사용하고 "(actionExecutedContext.ActionContext.Response.Content as ObjectContent) .Value.ToString ()"을 시도하여 응답을 받고 기록 할 수 있습니다.
— Prerak K

OnActionExecuted 내에서 요청을 받으려면 어떻게해야합니까?
— user2315985

3

요청 메시지에 액세스하는 것은 쉽습니다. 귀하의 기본 클래스는ApiController 포함 .Request속성을 이름에서 알 수 있듯이, 구문 분석 된 형태로 요청을 포함. 로깅하려는 것이 무엇이든간에 검사하고 로깅 시설로 전달하기 만하면됩니다. 이 코드는 한 번 또는 몇 번만 수행해야하는 경우 작업 시작 부분에 넣을 수 있습니다.

모든 작업에 대해 수행해야하는 경우 (모두 관리 할 수있는 소수 이상의 의미), .ExecuteAsync컨트롤러에 대한 모든 작업 호출을 캡처하는 메서드를 재정의 하는 것입니다.

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

나는 이것을하고 있고 아직 벤치마킹하지 않았습니다. 단지 내 직감이 이것이 매우 느릴 수 있다고 말합니까?
— Marcus

왜 느릴 것이라고 생각합니까? ExecuteAsync프레임 워크에 의해 호출되는 것이고 기본 컨트롤러 클래스의 구현은 실제로 액션을 실행하는 것입니다. 이것은 이미 발생하는 실행의 일부로 로깅을 호출하는 것입니다. 여기서 벌칙은 실제 로깅을 수행하는 시간입니다.
— LB2

아니요, 모든 요청을 기록하는 것처럼 '매우 느립니다'.
— Marcus

2
글쎄, 그것은 요구 사항의 문제이며 OP에 의해 명시된 요구 사항입니다. 그것은 사이트가 처리하는 양, 로깅 시설의 성능 등의 문제입니다. 그것은 OP 포스트를 넘어선 것입니다.
— LB2

0

이것은 꽤 오래된 스레드처럼 보이지만 다른 솔루션을 공유하고 있습니다.

HTTP 요청이 종료 될 때마다 트리거되는 global.asax 파일에이 메서드를 추가 할 수 있습니다.

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

이것은 정말 오래된 주제이지만 이러한 작업을 수행하는 데 많은 시간 (인터넷 검색)을 보냈으므로 여기에 솔루션을 게시하겠습니다.

개념

  1. 인바운드 요청을 추적하기 위해 APicontroller 메서드의 ExecuteAsync를 재정의합니다. 솔루션에서 Base_ApiController를 프로젝트 API 컨트롤러의 부모로 만듭니다.
  2. System.Web.Http.Filters.ActionFilterAttribute를 사용하여 API 컨트롤러의 아웃 바운드 응답 추적
  3. *** (추가) *** System.Web.Http.Filters.ExceptionFilterAttribute를 사용하여 예외 발생시 기록합니다.

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);

        }
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.