2012-10-18 17 views
7

Sto lavorando a un progetto ASP.NET Web Api e l'ho fatto accettare le informazioni sulla versione nell'URL.Web Api - Parametri richiesta esterno controller

Ad esempio:

  • api/v1/MyController
  • api/v2/MyController

Ora vorrei ottenere la richiesta di versione v1, v2 all'interno di un personalizzato LayoutRenderer per Nlog. Normalmente lo farei come nell'esempio seguente.

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

Il problema:HttpContext.Current è NULL

Credo che questo sia perché io uso Async wrappers per NLog e alcune chiamate prima del Logger sono anche Async.

Un esempio del logger chiamato async all'interno di Ninject.Extensions.WebApi.UsageLogger. A questo punto lo HttpRequestMessage ha tutte le informazioni necessarie per ottenere la versione.

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

La domanda quale sarebbe il modo migliore per rendere il VersionLayoutRenderer lavoro in un generico modo ? Posso aggiungere un MessageHandler e associare HttpRequest ad un ambito Async? Se così fosse, qualsiasi linea guida sarebbe molto apprezzata perché mi sto ancora abituando allo Ninject.

Per ora aggiungo le informazioni sulla versione direttamente al registro chiamate in UsageHandler, ma mi piacerebbe davvero una soluzione più generica, in cui posso sempre fare affidamento sulle informazioni sulla versione all'interno della mia registrazione.

Modifica: Aggiornato la domanda per essere più specifici e inclusi ulteriori dettagli.

+1

Puoi pubblicare il codice dove stai usando async – Jonathan

+0

Jonathan, per favore vedi la domanda aggiornata Spero che contenga tutte le informazioni che ti servono e altrimenti chiedi informazioni. –

risposta

1

Il problema reale è davvero neutro rispetto a quello che dovresti fare con Ninject - devi solo ottenere il phasing dell'elaborazione in modo tale che qualsiasi oggetto che sta eseguendo async abbia tutto ciò di cui ha bisogno senza fare affidamento sulla magia HttpContext.Current. Prendi quello che funziona senza DI Container prima.

Poi, da usare Ninject i passaggi principali sono: -

  1. vostri Bind dichiarazioni devono essere eseguiti una volta. Vedi il wiki di Ninject.MV3 per l'approccio migliore (fino a quando non viene unito, non c'è OOTB con l'edizione basata su NuGet)

  2. come @rickythefox (+ 1'd) dice, la tua registrazione dovrebbe cuocere il thread/dati di contesto-relativi nell'oggetto e si config la registrazione in modo che possa accadere presto nella elaborazione delle richieste, quando si è ancora sul filo che è 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); 
    

Poi basta fare il costruttore della gestore prendere un ILogger, che può essere assegnato a .Log (che spero non sia static: D)

NB, l'obiettivo è di non scrivere mai un periodo di kernel.Get() mai.

Il vero problema qui, però, è che l'uso corretto di WebAPI non comporta usando HttpContext.Current o qualsiasi altra magia static metodi o qualcosa di simile (per testability, per farti indipendente dal contesto di hosting (self hosting, OWIN ecc), e molte altre ragioni).

Inoltre, se si utilizza NLog (o Log4Net), è necessario considerare anche il pacchetto Ninject.Extensions.Logging (e l'origine).

+0

Ruben Grazie per la risposta. L'ho accettato perché è costruttivo e mi ha fatto capire che preferirei non andare nel modo in cui stavo pensando.Volevo una soluzione 'generic' per avere alcuni dati extra quando stavo registrando. Ma dal momento che questi dati non sono sempre disponibili, sarà sempre un po 'complicato. Farò semplicemente un messagehandler che scrive alcune informazioni di chiamata in un database con Nlog. Inoltre sto usando 'Ninject.Extensions.Logging' insieme al pacchetto' Nlog2', sono ottimi pacchetti. –

+0

@JosVinke: bello sapere che sei in ordine. Di sicuro può essere difficile trovare la soluzione semplice quando il contenitore è seduto lì e offre di fare ogni sorta di trucchi per te! ... hai già letto http://manning.com/seemann? Domande come queste si presenteranno a malapena per te se lo fai - è molto più di un "manuale di 200 pagine per un contenitore DI" si potrebbe pensare che potrebbe essere se non hai letto i post più votati di Mark Seemann da queste parti ... –

+0

Non l'ho letto, l'ho appena ordinato. Sono curioso ma sicuramente sembra un bel libro. Comunque grazie per il consiglio e il feedback. –

2

Prova iniettando contesto usando qualcosa come:

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

Grazie per la risposta, ma potresti essere un po 'più specifico su come implementare questo? Sono nuovo di Ninject e un po 'più di aiuto sarebbe molto apprezzato! Quale sarebbe il posto migliore per legare "IDependency" a "MyDependency"? Dovrei creare un nuovo 'MessageHandler' e ricollegare HttpContext ad ogni chiamata? –

+0

Ho scoperto perché la mia depurazione non viene iniettata come supposto, le chiamate provengono da un evento asincrono. Qualsiasi aiuto sarebbe ancora molto apprezzato. –

0

La classe GlobalConfiguration in grado di fornire l'accesso alla configurazione del routing.

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

Non sono sicuro se si sta cercando di risolvere la versione con un valore noto (il nome del controller), o se si sta cercando di risolvere dinamicamente. Se si inserisce l'HttpContext corrente, è possibile utilizzare l'URI della richiesta del contesto per filtrare i percorsi tramite il modello di percorso.

Modifica: Dopo i commenti mi sono reso conto che la configurazione del routing non era quella che cercavi.

Se l'obiettivo finale è implementare la registrazione all'interno dei controller, è possibile dare un'occhiata a Tracing in ASP.NET Web API in quanto è disponibile il supporto per la traccia integrata nell'infrastruttura API Web.

+0

Grazie mille per la risposta, ma (correggimi se sbaglio) Non penso che questo mi aiuti ad ottenere la versione per la Richiesta corrente. Ho pensato che la mia domanda potesse essere un po 'vaga a riguardo, quindi l'ho modificata. –

+0

Penso di aver frainteso allora. Se hai bisogno della richiesta corrente, l'iniezione di HttpRequestMessage o HttpContext nel renderer ha più senso; quindi devi solo analizzare la versione dall'URL di richiesta. – Oppositional

+0

Qualche idea su come dovrei iniettare HttpRequestMessage o HttpContext dopo una chiamata asincrona? L'analisi della versione dalla richiesta non è il problema. –

Problemi correlati