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.


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


    尝试使用类似以下内容注入上下文:

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

    The GlobalConfiguration class can provide 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 resolve the version with a known value (the name of the controller) or if you are trying to dynamically resolve it. If you inject the current HttpContext, you can use the request URI of the context to filter the routes via the route template.

    Edit: After your comments I realized the routing configuration isn't what you were after.

    If the end goal is to implement logging within your controllers, you may want to take a look at Tracing in ASP.NET Web API as there is support for tracing built-in to the Web API infrastructure.

    链接地址: http://www.djcxy.com/p/11294.html

    上一篇: > RGB转换是硬件加速的?

    下一篇: 请求参数外部控制器