【问题标题】:How to get hold of Content that is already read如何获取已阅读的内容
【发布时间】:2012-08-07 18:28:48
【问题描述】:

我有一个继承自 ApiController 的类。它有一个像这样的 Put 方法:

[PUT("user/{UserId}")]
public HttpResponseMessage Put(string userId, PaymentRequest paymentRequest)
{
    // Calling business logic and so forth here
    // Return proper HttpResponseMessage here
}

该方法与上面一样工作正常。现在我需要验证方法调用的签名,但是在这里我遇到了问题。签名本质上是方法+url+body的组合。我可以通过调用 Request.Method 获得的方法和我可以通过调用 Request.RequestUri.ToString() 获得的 url,但我无法掌握正文,因为它是 之前 它被 asp.net MVC4 框架自动反序列化为 PaymentRequest 对象。

我的第一次尝试: 正如我现在所理解的那样, Request.Content.ReadAsStringAsync().Result 什么也不返回。这是因为内容只能读取一次。

我的第二次尝试: 我试图将其序列化回 JSON 字符串。

var serializer = new JavaScriptSerializer();
var paymentRequestAsJson = serializer.Serialize(paymentRequest);

问题在于格式与签名的正文部分略有不同。它具有相同的数据,但更多的空间。

我无法更改 Put 方法的调用者所做的事情,因为这是第三方组件。我该怎么办?

【问题讨论】:

标签: c# asp.net asp.net-web-api


【解决方案1】:

我的答案是上述 Darin Dimitrov 的变体......所有 C# 变体(ASP.NET、Core、MVC 等)的变体答案应该是预期的......也许是 Visual Studio 的一年(2017 年) , 2019, etc.)...如果使用 Visual Basic 而不是 C#....

与 Anders Arpi 的评论类似,我无法使用所提供的 Darin 的答案(尽管显然我的答案是从他的直接推导而来)。在我的情况下 Request.Properties 不是一个选项,但 Request.InputStream 是。我怀疑这在很大程度上取决于第一段中提到的项目类型。我的解决方案在 VS2017 中对我有用。

页面上也有类似的答案:How to get raw request body in ASP.NET? 但是,这些对我不起作用。但是,他们确实提供了重要的线索,例如“请求对象未在 BeginRequest 事件中填充。您需要在事件生命周期的后期访问此对象'...

如果您在 HTTPRequests 构建/分析方面有经验,请跳过这一段。

尽管回想起来非常明显,但请求正文内容仅出现在某些 HTTPRequest 中。如果您只是在执行 GET(例如单击导航选项卡将您带到新页面),您通常没有请求正文。因此,在测试解决方案时,请确保您正在查看应该包含 RequestBody 的 HttpRequest。在我的例子中,任何实际从 UI 接收数据的提交按钮都将是一个包含 RequestBody 的帖子,因此存在(并且长度大于 0)。如果您没有 Fiddler 或 BurpSuite,或者正在使用 Shibboleth(这增加了 Fiddler/BurpSuite 测试的复杂性),您通常可以在 Firefox 开发人员工具中看到 HTTPRequest(通过 F12)......我在哪里调用请求正文将被称为参数和/或请求有效负载。

注意Application_AuthenticateRequest 在 Global.asax.cs 中。

(in addition to other using statements)
using System.IO;
using System.Text;
using System.Web;  

protected void Application_AuthenticateRequest(object sender, EventArgs e){

HttpApplication app = (HttpApplication)sender;
// Do a quick check to make sure that we are not on local. 
// I've had some odd errors on local, though I suspect general implementation will not have this issue.
// So if we're actually on a 'real' (a.k.a. non-local) website, I do the following.

      using (var stream = new MemoryStream())
                {
                    app.Request.InputStream.Seek(0, SeekOrigin.Begin);
                    app.Request.InputStream.CopyTo(stream);
                    string requestBody = Encoding.UTF8.GetString(stream.ToArray());
                    app.Request.InputStream.Seek(0, SeekOrigin.Begin); 
                    if (requestBody.Length > 0) Response.Write("requestBody: " + requestBody + "<br>");
                }

}

请注意,我正在使用“Reinstate Monica Cellio”的建议在复制后重置 InputStream。

【讨论】:

    【解决方案2】:

    一个非常延迟的响应,但最近我有同样的挑战要克服。

    我在不必从 httpContext 获取数据的情况下处理了一些不同的问题(对于大容量事务 Web 应用程序来说可能会非常昂贵)。

    我创建了一个简单的接口并让每个控制器实现它:

    public interface IBaseControllerData
    {
        object Entity { get; set; }
    }
    

    然后,我将控制器的实体属性设置为每个帖子的 Json 有效负载并执行操作。最后,我在 ActionFilterAttribute.OnActionExecuted 重写方法中检索了 Entity 数据,并将其序列化为 Json,然后再注入 MongoDB:

    object entity = ((IBaseControllerData)actionExecutedContext.ActionContext.ControllerContext.Controller).Entity;
                        requestBody = Newtonsoft.Json.JsonConvert.SerializeObject(entity);
    

    希望有帮助!

    干杯

    【讨论】:

      【解决方案3】:

      不要在签名中包含 body 参数,这将允许您缓冲内容并根据需要多次读取内容。

      [PUT("user/{UserId}")]
      public HttpResponseMessage Put(string userId)
      {
          Request.Content.LoadIntoBufferAsync().Wait();
          var paymentRequest = Request.Content.ReadAsAsync<PaymentRequest>().Result;
          var requestBody = Request.Content.ReadAsStringAsync().Result;
          // Calling business logic and so forth here
          // Return proper HttpResponseMessage here
      }
      

      【讨论】:

      • @Halvard 它也可以在自托管模式下工作。我不认为 HttpContextBase 可以在自主机模式下以相同的方式访问。
      【解决方案4】:

      您可以从底层请求中读取:

      using (var stream = new MemoryStream())
      {
          var context = (HttpContextBase)Request.Properties["MS_HttpContext"];
          context.Request.InputStream.Seek(0, SeekOrigin.Begin);
          context.Request.InputStream.CopyTo(stream);
          string requestBody = Encoding.UTF8.GetString(stream.ToArray());
      }
      

      【讨论】:

      • 为什么Request.InputStream 不在这里工作?为什么需要上下文?
      • 因为没有Request.InputStream这样的属性。不要忘记在 ApiController 中 Request 属性的类型是 HttpRequestMessage 而不是 HttpRequestBase
      • MS_HttpContext 对我来说不存在
      • @JobaDiniz 改用MS_OwinContext
      • @AlexZhukovskiy 这会在 Seek Specified method is not supported. 上产生错误
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-11
      相关资源
      最近更新 更多