【问题标题】:Web Api - Request Parameters Outside ControllerWeb Api - 控制器外部的请求参数
【发布时间】:2012-10-18 21:03:20
【问题描述】:

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

例如:

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

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

[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 wrappers 用于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 中的日志调用中,但我真的想要一个更通用的解决方案,我可以始终依赖日志中的版本信息。

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

【问题讨论】:

  • 你能把代码贴在你使用异步的地方吗
  • 乔纳森,请查看更新后的问题,我希望它包含您需要的所有信息,否则请询问。

标签: c# asp.net-mvc ninject asp.net-web-api httpcontext


【解决方案1】:

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

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

【讨论】:

  • 感谢您的回答,但您能否更具体地说明您将如何实现此功能?我是 Ninject 的新手,非常感谢您提供更多帮助!将IDependency 绑定到MyDependency 的最佳位置是什么?我应该创建一个新的MessageHandler 并在每次调用时重新绑定 HttpContext 吗?
  • 我发现了为什么我的依赖没有像预期的那样被注入,调用来自异步事件。任何帮助仍将不胜感激。
【解决方案2】:

真正的问题是你应该用 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(),永远,句号。

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

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

【讨论】:

  • 鲁本 谢谢你的回答。我接受了它,因为它具有建设性,让我意识到我宁愿不走我想的路。我想要一个generic 解决方案,以便在我记录时有一些额外的数据。但是由于这些数据并不总是可用的,所以总是有点棘手。我将制作一个消息处理程序,使用 Nlog 将一些呼叫信息写入数据库。我也在使用Ninject.Extensions.LoggingNlog2 包,这些都是很棒的包。
  • @JosVinke:很高兴听到你的排序。当容器坐在那里为您提供各种技巧时,肯定很难找到简单的解决方案! ...你读过manning.com/seemann了吗?如果你这样做了,这样的问题几乎不会出现 - 这不仅仅是一本“200 页的 DI 容器手册”,如果你还没有阅读过 Mark Seemann 的最高评价帖子,你可能会认为它可能是这样的......
  • 没看,刚订的。我很好奇,但它看起来确实是一本好书。无论如何感谢您的建议和反馈。
【解决方案3】:

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,通过路由模板过滤路由。

编辑:在你的 cmets 之后,我意识到路由配置不是你想要的。

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

【讨论】:

  • 非常感谢您的回答,但是(如果我错了,请纠正我)我认为这对我获取当前请求的版本没有帮助。我想我的问题可能有点含糊,所以我编辑了它。
  • 我想我当时理解错了。如果您需要当前请求,那么将 HttpRequestMessage 或 HttpContext 注入渲染器是最有意义的;那么你只需要从请求 URI 中解析出版本。
  • 在异步调用之后我应该如何注入 HttpRequestMessage 或 HttpContext 有什么想法吗?从请求中解析版本不是问题,因为我有它。
  • 反对,请参阅更新后的问题,我做得更具体。我不想获取跟踪日志。我已经设置了跟踪,并且可以使用 Nlog 记录跟踪。我希望能够获取记录的每个项目的版本信息、跟踪、使用情况、日志消息。
  • 这现在更准确地说是一个 Ninject 问题,不幸的是我不熟悉。希望 Ninject 专家可以帮助您。
猜你喜欢
  • 1970-01-01
  • 2013-11-19
  • 1970-01-01
  • 2013-02-06
  • 1970-01-01
  • 2016-11-14
  • 1970-01-01
  • 1970-01-01
  • 2016-05-03
相关资源
最近更新 更多