Web Api - request parameters outside the controller

I am working on an ASP.NET Web Api project and getting it to accept version information in the url.

For example:

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

Now I would like to get the version of the request v1, v2 inside the custom LayoutRenderer for Nlog . I usually did this, as in the example below.

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

Problem: HttpContext.Current - NULL

I believe this is because I use Async wrappers for Nlog , and some calls in front of Logger are also Async .

An example of a log called Async inside Ninject.Extensions.WebApi.UsageLogger. At this point, HttpRequestMessage has all the information needed to obtain the Version.

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

Question What would be the best way to make VersionLayoutRenderer work in a generic way? Can I add a MessageHandler and bind an HttpRequest to some Async area? If so, then any recommendations would be greatly appreciated, because I'm still used to Ninject .

For now, I am adding version information directly to Log Call in UsageHandler, but I would like to get a more general solution where I can always rely on version information inside my logging.

Edit: Updated a question that will be more specific and will contain more detailed information.

+7
source share
3 answers

The actual problem is really neutral in what you should do with Ninject - you just need to get the phasing of your processing so that any objects that will work async have everything they need without relying on the magic of HttpContext.Current . Get it by working without a DI container first.

Then, to use Ninject, the main steps are: -

  • Your Bind statements should be run once. See the Ninject.MV3 wiki for a better approach (until it is merged, there is no OOTB with a NuGet-based version)

  • as @rickythefox (+ 1'd) says, your registration must bake the stream / context-sensitive data into the object, and you configure the registration so that it can happen at the beginning of the processing of the request when you are still on the stream that is 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); 

Then just make the handler constructor take an ILogger , which can be assigned to .Log (which hopefully isnt static : D)

NB, the goal is that you should never write kernel.Get() , ever, period.

The real problem here is that the correct use of WebApi does not imply the use of HttpContext.Current or any other magic static methods or anything similar (to check, to make yourself independent of the hosting context (own hosting, OWIN, etc.) and many other reasons).

In addition, if you are using NLog (or Log4Net), you should also look at the Ninject.Extensions.Logging package (and source).

+1
source

Try entering a context using something like:

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

The GlobalConfiguration class can give you access to the routing configuration.

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

I am not sure if you are trying to solve a version with a known value (controller name) or if you are trying to dynamically resolve it. If you enter the current HttpContext, you can use the context request URI to filter routes through the route pattern.

Edit: After your comments, I realized that the routing configuration is not what you need.

If the ultimate goal is to implement logging inside your controllers, you can take a look at Tracing in the ASP.NET web interface as there is support for tracking embedded in the web API infrastructure.

0
source

All Articles