Web Api — параметры запроса вне контроллера

Я работаю над проектом ASP.NET Web Api и заставил его принимать информацию о версии в URL-адресе.

Например:

  • API/v1/МойКонтроллер
  • API/v2/МойКонтроллер

Теперь я хотел бы получить версию запроса v1, v2 внутри пользовательского LayoutRenderer для Nlog. Обычно я бы сделал это, как в приведенном ниже примере.

[LayoutRenderer("Version")]
public class VersionLayoutRenderer : LayoutRenderer
{
    protected override void Append(System.Text.StringBuilder builder, NLog.LogEventInfo logEvent)
    {
        var version = HttpContext.Current.Request.RequestContext.RouteData.Values["Version"];
        builder.Append(version);
    }
}

Проблема: HttpContext.Current равно NULL

Я полагаю, это потому, что я использую асинхронные оболочки. для NLog и некоторые вызовы перед регистратором также Async.

Пример логгера, вызываемого Async внутри Ninject.Extensions.WebApi.UsageLogger. На данный момент у HttpRequestMessage есть вся информация, необходимая для получения версии.

/// <summary>
/// Initializes a new instance of the <see cref="UsageHandler" /> class.
/// </summary>
public UsageHandler()
{
    var kernel = new StandardKernel();

    var logfactory = kernel.Get<ILoggerFactory>();

    this.Log = logfactory.GetCurrentClassLogger();
}

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var startTime = DateTime.Now;

        // Log request
        await request.Content.ReadAsStringAsync().ContinueWith(c =>
            {
                this.Log.Info("{0}: {1} called from {2}", request.Method, HttpUtility.UrlDecode(request.RequestUri.AbsoluteUri), ((HttpContextBase)request.Properties["MS_HttpContext"]).Request.UserHostAddress);
                this.Log.Info("Content-Type: {0}, Content-Length: {1}", request.Content.Headers.ContentType != null ? request.Content.Headers.ContentType.MediaType : string.Empty, request.Content.Headers.ContentLength);
                this.Log.Info("Accept-Encoding: {0}, Accept-Charset: {1}, Accept-Language: {2}", request.Headers.AcceptEncoding, request.Headers.AcceptCharset, request.Headers.AcceptLanguage);

                if (!string.IsNullOrEmpty(c.Result))
                {
                    if (this.MaxContentLength > 0 && c.Result.Length > this.MaxContentLength)
                    {
                        this.Log.Info("Data: {0}", HttpUtility.UrlDecode(c.Result).Substring(0, this.MaxContentLength - 1));
                    }
                    else 
                    {
                        this.Log.Info("Data: {0}", HttpUtility.UrlDecode(c.Result));
                    }
                }
            });

        var response = await base.SendAsync(request, cancellationToken);

        // Log the error if it returned an error
        if (!response.IsSuccessStatusCode)
        {
            this.Log.Error(response.Content.ReadAsStringAsync().Result);
        }

        // Log performance
        this.Log.Info("Request processing time: " + DateTime.Now.Subtract(startTime).TotalSeconds + "s");

        return response;
    }

Вопрос Как лучше всего заставить VersionLayoutRenderer работать универсально? Могу ли я добавить MessageHandler и привязать HttpRequest к некоторой асинхронной области? Если да, то любые рекомендации будут высоко оценены, потому что я все еще привыкаю к ​​Ninject.

В настоящее время я добавляю информацию о версии непосредственно в вызов журнала в UsageHandler, но мне бы очень хотелось более универсальное решение, в котором я всегда могу полагаться на информацию о версии в своем журнале.

Изменить: обновлен вопрос, чтобы он стал более конкретным и содержал больше деталей.


person Jos Vinke    schedule 18.10.2012    source источник
comment
Можете ли вы опубликовать код, где вы используете асинхронный   -  person Jonathan    schedule 19.10.2012
comment
Джонатан, пожалуйста, ознакомьтесь с обновленным вопросом. Я надеюсь, что он содержит всю необходимую вам информацию, в противном случае, пожалуйста, спросите.   -  person Jos Vinke    schedule 19.10.2012


Ответы (3)


Фактическая проблема действительно нейтральна по отношению к тому, что вы должны делать с Ninject - вам просто нужно получить поэтапную обработку, чтобы любые объекты, которые будут работать асинхронно, имели все, что им нужно, не полагаясь на магию HttpContext.Current. Сначала начните работать без DI-контейнера.

Затем, чтобы использовать Ninject, основные шаги:

  1. Ваши операторы Bind необходимо запустить один раз. См. вики Ninject.MV3 для лучшего подхода (пока он не будет объединен, в версии на основе NuGet нет OOTB)

  2. как говорит @rickythefox (+1'd), ваша регистрация должна запекать данные, относящиеся к потоку/контексту, в объект, и вы настраиваете регистрацию таким образом, чтобы это могло произойти на ранней стадии обработки запроса, когда вы все еще находитесь в потоке, который HttpContext.Current

    kernel.Bind<ILogger>()
    // TODO replace GCCL with something like GetClassLogger(ctx.Request.Service.ReflectedType) - see the wiki for examples
      .ToMethod( ctx=> ctx.Get<ILoggerFactory>().GetCurrentClassLogger()) 
      .InRequestScope()
      .WithConstructorArgument("context",c=>HttpContext.Current);
    

Затем просто сделайте так, чтобы конструктор обработчика принимал ILogger, который можно присвоить .Log (надеюсь, это не static: D)

NB, цель состоит в том, чтобы вы никогда не писали kernel.Get(), когда-либо, точку.

Однако реальная проблема заключается в том, что правильное использование WebApi не предполагает использования HttpContext.Current или каких-либо других магических методов static или чего-либо подобного (для удобства тестирования, чтобы сделать себя независимым от контекста хостинга (самостоятельный хостинг, OWIN и т. д.) и по многим другим причинам. ).

Кроме того, если вы используете NLog (или Log4Net), вам также следует посмотреть пакет Ninject.Extensions.Logging (и исходный код).

person Ruben Bartelink    schedule 20.10.2012
comment
Рубен Спасибо за ответ. Я принял его, потому что он был конструктивным и заставил меня понять, что я предпочел бы не идти тем путем, о котором думал. Я хотел, чтобы решение generic имело дополнительные данные при ведении журнала. Но поскольку эти данные не всегда доступны, это всегда будет немного сложно. Я просто сделаю обработчик сообщений, который записывает некоторую информацию о вызовах в базу данных с помощью Nlog. Также я использую Ninject.Extensions.Logging вместе с пакетом Nlog2, это отличные пакеты. - person Jos Vinke; 22.10.2012
comment
@JosVinke: Рад слышать, что с тобой все в порядке. Конечно, может быть трудно найти простое решение, когда контейнер сидит и предлагает делать для вас всевозможные трюки! ... вы уже прочитали manning.com/seemann? Подобные вопросы вряд ли возникнут у вас, если вы это сделаете - это НАМНОГО больше, чем «200-страничное руководство по контейнеру DI», как можно подумать, если вы не читали здесь самые популярные посты Марка Симанна... - person Ruben Bartelink; 22.10.2012
comment
Не читал, только заказал. Мне любопытно, но это определенно похоже на хорошую книгу. В любом случае спасибо за совет и отзыв. - person Jos Vinke; 22.10.2012

Попробуйте ввести контекст, используя что-то вроде:

kernel.Bind<IDependency>()
    .To<Mydependency>()
    .InRequestScope()
    .WithConstructorArgument("context",c=>HttpContext.Current);
person rickythefox    schedule 18.10.2012
comment
Спасибо за ответ, но не могли бы вы поподробнее рассказать, как бы вы это реализовали? Я новичок в Ninject, и буду очень признателен за помощь! Где лучше всего связать IDependency с MyDependency? Должен ли я сделать новый MessageHandler и перепривязывать HttpContext при каждом вызове? - person Jos Vinke; 19.10.2012
comment
Я выяснил, почему моя зависимость не получает Injected, как предполагалось, вызовы исходят из асинхронного события. Любая помощь по-прежнему будет высоко оценена. - person Jos Vinke; 19.10.2012

GlobalConfiguration class может предоставить вам доступ к конфигурации маршрутизации.

// The code below assumes a map routing convention of api/{version}/{controller}/....

// This will give you the configured routes
var routes      = GlobalConfiguration.Configuration.Routes;

// This will give you the route templates
var templates   = routes
    .Select(route => route.RouteTemplate);

// This will give you the distinct versions for all controllers
var versions    = routes
    .Select(route => route.RouteTemplate)
    .Select(template => template.Split("/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries))
    .Select(values => values[1])
    .Distinct();

// This will give you the distinct versions for a controller with the specified name
var name                = "MyController";

var controllerVersions  = routes
    .Select(route => route.RouteTemplate)
    .Select(template => template.Split("/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries))
    .Where(values => String.Equals(values[2], name, StringComparison.OrdinalIgnoreCase))
    .Select(values => values[1])
    .Distinct();

Я не уверен, пытаетесь ли вы разрешить версию с известным значением (имя контроллера) или пытаетесь динамически разрешить ее. Если вы вводите текущий HttpContext, вы можете использовать URI запроса контекста для фильтрации маршрутов через шаблон маршрута.

Редактировать: После ваших комментариев я понял, что конфигурация маршрутизации — это не то, что вам нужно.

Если конечной целью является реализация ведения журнала в ваших контроллерах, вы можете взглянуть на Трассировка в веб-API ASP.NET, так как имеется встроенная поддержка трассировки в инфраструктуре веб-API.

person Oppositional    schedule 18.10.2012
comment
Большое спасибо за ваш ответ, но (поправьте меня, если я ошибаюсь) я не думаю, что это поможет мне получить версию для текущего запроса. Я подумал, что мой вопрос может быть немного расплывчатым, поэтому я отредактировал его. - person Jos Vinke; 19.10.2012
comment
Я думаю, что я неправильно понял тогда. Если вам нужен текущий запрос, то внедрение HttpRequestMessage или HttpContext в средство визуализации имеет наибольший смысл; тогда вам просто нужно проанализировать версию из URI запроса. - person Oppositional; 19.10.2012
comment
Любая мысль о том, как я должен вводить HttpRequestMessage или HttpContext после асинхронного вызова? Разобрать версию из запроса не проблема, раз она у меня есть. - person Jos Vinke; 19.10.2012
comment
Оппозиционер, см. обновленный вопрос, я сделал его более конкретным. Я не пытаюсь получить журналы трассировки. У меня уже настроена трассировка, и я могу регистрировать трассировки с помощью Nlog. Я хочу иметь возможность получать информацию о версии для каждого зарегистрированного элемента, трассировки, использования, сообщения журнала. - person Jos Vinke; 19.10.2012
comment
Теперь это более точно вопрос Ninject, с которым я, к сожалению, не знаком. Надеемся, что эксперт Ninject может помочь вам. - person Oppositional; 19.10.2012