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 : ContinueWithResult위험 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.