Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Web Api - Request Parameters Outside Controller

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.

like image 286
Jos Vinke Avatar asked Oct 18 '12 21:10

Jos Vinke


2 Answers

Try injecting the context using something like:

kernel.Bind<IDependency>()
    .To<Mydependency>()
    .InRequestScope()
    .WithConstructorArgument("context",c=>HttpContext.Current);
like image 58
rickythefox Avatar answered Oct 10 '22 20:10

rickythefox


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:-

  1. 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)

  2. 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).

like image 25
Ruben Bartelink Avatar answered Oct 10 '22 21:10

Ruben Bartelink