Необходимо зарегистрировать тело запроса и ответа asp.net webapi 2 в базе данных

103

Я использую Microsoft Asp.net WebApi2, размещенный на IIS. Я очень просто хотел бы регистрировать тело запроса (XML или JSON) и тело ответа для каждого сообщения.

Нет ничего особенного ни в этом проекте, ни в контроллере, обрабатывающем пост. Меня не интересует использование фреймворков для ведения журналов, таких как nLog, elmah, log4net, или встроенных функций трассировки веб-API, если в этом нет необходимости.

Я просто хочу знать, где разместить мой код ведения журнала и как получить фактический JSON или XML из входящего и исходящего запроса и ответа.

Мой метод сообщения контроллера:

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

}
user2315985
источник
Вы хотите регистрировать запрос / ответ для определенного действия, набора или всех ваших действий в конкретном контроллере?
LB2
Интересует только регистрация поста. (a) Время публикации (b) тело отправленного xml или json (c) ответ (содержимое xml или json) вместе с кодом состояния Http
user2315985,
Причина, по которой я спрашивал, - предложить, вводить ли код непосредственно в действие или общее решение для всех действий. Смотрите мой ответ ниже.
LB2
К вашему сведению, я удалил asp.net, так как он не имеет отношения к этому вопросу
Dalorzo
создание фильтра - не вариант?
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 для обработчиков сообщений .

SoftwareFactor
источник
3
task.Result.Contentвозвращается System.Net.Http.ObjectContent. Есть ли способ вместо этого получить необработанный xml / json?
ПК.
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 .

Венкатеш Муниянди
источник
Вы можете назначить любой фильтр действий следующим образом: public static class WebApiConfig {public static void Register (HttpConfiguration config) {// Конфигурация веб-API и конфигурация сервисов.Filters.Add (new MyFilter ()) // Конфигурация маршрутов веб-API.MapHttpAttributeRoutes (); config.Routes.MapHttpRoute (имя: "DefaultApi", routeTemplate: "api / {controller} / {id}", значения по умолчанию: новый {id = RouteParameter.Optional}); }}
Мика Карьюнен
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. Проблема в том, что запрос в этот момент уже преобразован в мой POCO, а не в xml или json. Есть предположения?
user2315985
Первоначально я имел в виду регистрацию данных в OnActionExecuting, а затем просто позволить посту делать свою работу. Как я понял из вашего вопроса, вы просто хотите регистрировать данные для каждого сделанного сообщения.
Prerak K
3
Я хочу регистрировать и запрос, и данные ответа каждый раз, когда кто-то публикует.
user2315985
2
вы можете использовать OnActionExecuted и попробовать "(actionExecutedContext.ActionContext.Response.Content как 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);
}
LB2
источник
Я делаю это, и я еще не тестировал это, просто моя интуиция подсказывает мне, что это может быть очень медленно?
Маркус
Как вы думаете, почему это будет медленно? ExecuteAsyncэто то, что вызывается фреймворком, а реализация базового класса контроллера - это то, что фактически выполняет действие. Это просто вызов вашего журнала как часть уже происходящего выполнения. Единственное наказание - это время для фактической регистрации.
LB2
Нет, я имею в виду «очень медленно», как при регистрации каждого запроса.
Маркус
2
Ну, это вопрос требований, и это требование, заявленное OP. Это вопрос объема, который обрабатывается сайтом, производительности средства ведения журнала и т. Д. Это выходит за рамки публикации OP.
LB2
0

Кажется, это довольно старый поток, но вам нужно поделиться другим решением.

Вы можете добавить этот метод в свой файл global.asax, который будет запускаться каждый раз после завершения 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
источник
0

Это действительно старая тема, но я потратил много времени (поискал в Интернете), чтобы сделать это, поэтому я просто опубликую здесь свое решение.

Концепция

  1. Переопределите ExecuteAsync метода APicontroller для отслеживания входящего запроса, в своем решении я создаю 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);

        }
    }
}
user3682728
источник