I'm working on a ASP.NET Web Api project and made it accept version information in the url.
For example:
- api/v1/MyController
- api/v2/MyController
Now I would like to get the request version v1, v2 inside a custom LayoutRenderer for Nlog
. Normally I would do this like the below example.
[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);
}
}
The problem: HttpContext.Current
is NULL
I believe this is because I use Async wrappers for NLog
and some calls before the Logger are also Async
.
A example of the logger being called Async inside Ninject.Extensions.WebApi.UsageLogger. At this point the HttpRequestMessage
has all info we need to get 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;
}
The question
What would be the best way to make the VersionLayoutRenderer
work in a generic way? Could I add a MessageHandler and Bind the HttpRequest to some Async scope? If so any guidelines would be much appreciated cause I'm still getting used to Ninject
.
For the time being I add the version information directly to the Log Call in the UsageHandler, but I would really like a more generic solution, where I can always rely on version information inside my logging.
Edit: Updated the question to be more specific and included more details.
Try injecting the context using something like:
kernel.Bind<IDependency>()
.To<Mydependency>()
.InRequestScope()
.WithConstructorArgument("context",c=>HttpContext.Current);
The actual issue is really neutral wrt what you should do with Ninject - you just need to get the phasing of your processing such that any objects that are going be running async have everything they need without relying on the magic HttpContext.Current
. Get that working with no DI Container first.
Then, to use Ninject the major steps are:-
Your Bind
statements need to be run once. See the Ninject.MV3 wiki for the best approach (until it gets merged in, there is not OOTB with the NuGet-based edition)
as @rickythefox (+1'd) says, your registration should bake the thread/context-relative data into the object and you config the registration such that it can happen early in request processing, when you're still on the thread that's 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 constructor of the handler take a ILogger
, which can be assigned to .Log
(which I hope isnt static
:D)
NB, the aim is for you never to write a kernel.Get()
, ever, period.
The real problem here though, is that proper use of WebApi does not involve using HttpContext.Current
or any other magic static
methods or anything similar (for testability, to make yourself independent of the hosting context (self hosting, OWIN etc), and many more reasons).
Also, if you are using NLog (or Log4Net) you should also look at the Ninject.Extensions.Logging
package (and source).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With