7

我正在开发一个 ASP.NET Web Api 项目,并使其接受 url 中的版本信息。

例如:

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

现在我想在自定义 LayoutRenderer 中获取请求版本v1、v2Nlog。通常我会像下面的例子那样做。

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

问题: HttpContext.Current是NULL

我相信这是因为我使用Async 包装器NLog并且 Logger 之前的一些调用也是Async.

在 Ninject.Extensions.WebApi.UsageLogger 中调用 Async 的记录器示例。此时,HttpRequestMessage我们需要获取版本的所有信息。

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

问题以通用VersionLayoutRenderer方式制作作品 的最佳方式是什么?我可以添加一个 MessageHandler 并将 HttpRequest 绑定到某个异步范围吗?如果是这样,任何指导方针将不胜感激,因为我仍然习惯。 Ninject

目前我将版本信息直接添加到 UsageHandler 中的日志调用中,但我真的想要一个更通用的解决方案,我总是可以依赖日志中的版本信息。

编辑:将问题更新为更具体并包含更多详细信息。

4

3 回答 3

2

尝试使用以下方式注入上下文:

kernel.Bind<IDependency>()
    .To<Mydependency>()
    .InRequestScope()
    .WithConstructorArgument("context",c=>HttpContext.Current);
于 2012-10-18T21:35:54.890 回答
1

实际的问题是你应该用 Ninject 做什么——你只需要对你的处理进行分阶段,这样任何将要异步运行的对象都拥有他们需要的一切,而不依赖于魔法HttpContext.Current。首先在没有 DI 容器的情况下使用它。

然后,使用 Ninject 的主要步骤是:-

  1. 您的Bind语句需要运行一次。请参阅 Ninject.MV3 wiki 了解最佳方法(在它被合并之前,基于 NuGet 的版本没有 OOTB)

  2. 正如@rickythefox(+1'd)所说,您的注册应该将线程/上下文相关数据烘焙到对象中,并且您配置注册以便它可以在请求处理的早期发生,当您仍在线程上时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);
    

然后只需让处理程序的构造函数接受一个ILogger,它可以分配给.Log(我希望不是static:D)

注意,我们的目标是让你永远不要写一个kernel.Get(), ever, 句号。

但这里真正的问题是,正确使用 WebApi 不涉及使用HttpContext.Current或任何其他魔术static方法或任何类似的东西(为了可测试性,使自己独立于托管上下文(自托管、OWIN 等),以及更多原因)。

此外,如果您使用的是 NLog(或 Log4Net),您还应该查看Ninject.Extensions.Logging包(和源代码)。

于 2012-10-20T00:41:30.797 回答
0

GlobalConfiguration类可以让您访问路由配置。

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

我不确定您是尝试使用已知值(控制器名称)解析版本还是尝试动态解析它。如果注入当前的 HttpContext,则可以使用上下文的请求 URI 通过路由模板过滤路由。

编辑:在您发表评论后,我意识到路由配置不是您想要的。

如果最终目标是在控制器中实现日志记录,您可能需要查看ASP.NET Web API中的跟踪,因为 Web API 基础结构支持内置跟踪。

于 2012-10-18T23:22:40.560 回答