2012-10-18 56 views
7

我正在研究ASP.NET Web Api項目並使其接受URL中的版本信息。Web Api - 外部控制器的請求參數

例如:

  • API/V1/myController的
  • API/V2/myController的

現在我想獲得請求的版本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

我相信這是因爲我使用Async wrappersNLog和記錄儀前幾個電話也是Async

記錄器在Ninject.Extensions.WebApi.UsageLogger中被稱爲Async的示例。在這一點上,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中的日誌調用中,但我真的很喜歡更通用的解決方案,我可以始終依賴日誌中的版本信息。

編輯:已更新的問題更加具體,包括更多細節。

+1

您可以發佈代碼,您正在使用異步 – Jonathan

+0

喬納森,請參閱更新的問題,我希望它包含您需要的所有信息,否則請提出。 –

回答

1

實際問題與Ninject應該做什麼是中性的 - 你只需要讓你的處理過程分階段進行,以便任何正在運行異步的對象都擁有他們所需的所有東西,而不依賴於魔術HttpContext.Current。先得到沒有DI容器的工作。

然後,使用Ninject的主要步驟是: -

  1. Bind語句需要運行一次。請參閱Ninject.MV3 wiki以獲取最佳方法(直到它合併爲止,基於NuGet的版本沒有OOTB)

  2. as @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); 
    

然後,只需做的構造處理程序採取​​,它可以被分配到.Log(我希望不是static: D)

注意,目標是永遠不要寫kernel.Get()這段時間。

這裏真正的問題雖然是正確使用的WebAPI不涉及使用HttpContext.Current或任何其他魔術static方法或任何類似的(可測性,讓自己獨立的託管上下文(個體經營託管,OWIN等),還有更多的原因)。另外,如果您使用的是NLog(或Log4Net),則還應該查看Ninject.Extensions.Logging程序包(和源代碼)。

+0

Ruben感謝您的回答。我接受它是因爲它具有建設性,並讓我意識到我寧願不按照我所想的方式行事。當我登錄時,我想要一個「通用」解決方案來獲得一些額外的數據。但是由於這些數據並不總是可用,它總是會有點棘手。我只會做一個消息處理程序,用Nlog將一些調用信息寫入數據庫。另外,我使用'Ninject.Extensions.Logging'和'Nlog2'軟件包,這些都是很棒的軟件包。 –

+0

@JosVinke:很高興聽到你排序。當容器坐在那裏時,確實很難找到簡單的解決方案,爲您提供各種各樣的技巧! ...你有沒有讀過http://manning.com/seemann?如果你這樣的話,這些問題幾乎不會出現 - 它遠遠超過'DI容器的200頁手冊',人們可能會認爲這可能是因爲如果你沒有閱讀Mark Seemann在這裏的最高評價的帖子...... –

+0

沒有閱讀,只是訂購了它。我很好奇,但它確實看起來像一本不錯的書。無論如何感謝您的建議和反饋。 –

2

嘗試使用類似注射的背景:

kernel.Bind<IDependency>() 
    .To<Mydependency>() 
    .InRequestScope() 
    .WithConstructorArgument("context",c=>HttpContext.Current); 
+0

感謝您的回答,但您能否詳細說明您將如何實施?我是Ninject的新手,如果有更多幫助,我們將非常感謝!將'IDependency'綁定到'MyDependency'的最佳位置是什麼?我應該每做一次新的'MessageHandler'並重新綁定HttpContext? –

+0

我發現爲什麼我的依賴沒有按照假設注入,這些調用來自異步事件。任何幫助仍將非常感激。 –

0

GlobalConfiguration類可以使您可以訪問路由配置。

// 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來通過路由模板過濾路由。

編輯:在您的意見後,我意識到路由配置不是你以後的。

如果最終目標是在您的控制器中實現日誌記錄,則可能需要查看Tracing in ASP.NET Web API,因爲支持Web API基礎結構的內置跟蹤。

+0

非常感謝您的回答,但(如果我錯了,請糾正我)我不認爲這有助於我獲取當前請求的版本。我覺得我的問題可能有點含糊,所以我編輯了它。 –

+0

我想我誤解了。如果您需要當前請求,那麼將HttpRequestMessage或HttpContext注入渲染器是最有意義的;那麼你只需要從Request URI解析版本。 – Oppositional

+0

任何有關如何在異步調用後注入HttpRequestMessage或HttpContext的想法?從請求中解析版本不是問題,因爲我擁有它。 –

相關問題