【问题标题】:HttpContent Headers inconsistent enumerationHttpContent Headers 枚举不一致
【发布时间】:2016-11-28 00:18:22
【问题描述】:

我正在将HttpContent 转换为以下 dto:

public class ContentDto 
{
     public string ContentType {get; set;}
     public string Headers {get; set; }
     public object Data { get; set; }

     public ContentDto(HttpContent content)
     {
          Headers = content.Headers.Flatten();
          // rest of the setup
     }
}

并且正在对其进行一些单元测试:

[Fact]
public void CanBuild()
{
     var content = new StringContent("some json", Enconding.UTF8, "application/json");
     var dto = new ContentDto(content);

     var contentHeaders = content.Headers.Flatten();

     Assert.Equal(contentHeaders, dto.Headers);
}

并且该测试失败,因为Content-Length 标头未在我的 dto 上捕获。但是,如果我这样做:

[Fact]
public void CanBuild()
{
     var content = new StringContent("some json", Enconding.UTF8, "application/json");

     var contentHeaders = content.Headers.Flatten();

     var dto = new ContentDto(content);

     Assert.Equal(contentHeaders, dto.Headers);
}

测试通过并捕获所有标头。更何况我也试过这个:

 [Fact]
 public void CanBuild()
 {
     var content = new StringContent("some json", Enconding.UTF8, "application/json");

     var dto = new ContentDto(content);

     var contentHeaders = content.Headers.Flatten();

     var dto1 = new ContentDto(content);

     Assert.Equal(contentHeaders, dto.Headers);                
     Assert.Equal(contentHeaders, dto1.Headers);
}

它失败了,因为dto 没有Content-Length 标头,但dto1 有。我什至尝试在 Factory-like 方法中获取标题,如下所示:

 public static ContentDto FromContent<T>(T content) where T : HttpContent
 {
      // same as the constructor
 }

看看StringContent 类的Content-Length 标头是否有什么特别之处,但无论我使用构造函数(使用基类HttpContent)还是泛型方法,都没有区别FromContent(在这种情况下使用实际的 StringContent)结果是一样的。

所以我的问题是:

这是HttpContent.Headers 的预期行为吗?
是否有一些特定于实际 HttpContent 类型的标头?
我在这里想念什么?

注意:这是Flatten扩展方法的代码:

 public static string Flatten(this HttpHeaders headers)
 {
      var data = headers.ToDictionary(h => h.Key, h => string.Join("; ", h.Value))
                        .Select(kvp => $"{kvp.Key}: {kvp.Value}");

      return string.Join(Environment.NewLine, data)
 }

【问题讨论】:

  • ToDictionary 中的项目顺序无法保证,这是否会产生相同的结果headers.ToDictionary(h =&gt; h.Key, h =&gt; string.Join("; ", h.Value)) .Orderby(x=&gt;x.Key).Select(kvp =&gt; $"{kvp.Key}: {kvp.Value}")
  • @AkashKava 问题与订单无关。问题是 Content-Length 标头并不总是存在。
  • @Luiso,您能否显示minimal reproducible example,以便可以准确地复制您的问题。这将有助于为您的问题找到解决方案。

标签: c# http-headers dotnet-httpclient httpcontent


【解决方案1】:

您的示例不完整。只有在调用扩展方法之前访问 ContentLength 属性时,我才能重新创建您的问题。在您的代码中的某处(很可能是 //rest of setup) 您正在直接或间接调用该属性,该属性很可能遵循延迟加载模式,然后在您下次调用时将其包含在标题中您的扩展方法,它包含在构造的字符串中。它们不匹配,因为您在访问内容长度属性之前生成了手动字符串。

HttpContentHeaders.ContentLength的源代码中

public long? ContentLength
{
    get
    {
        // 'Content-Length' can only hold one value. So either we get 'null' back or a boxed long value.
        object storedValue = GetParsedValues(HttpKnownHeaderNames.ContentLength);

        // Only try to calculate the length if the user didn't set the value explicitly using the setter.
        if (!_contentLengthSet && (storedValue == null))
        {
            // If we don't have a value for Content-Length in the store, try to let the content calculate
            // it's length. If the content object is able to calculate the length, we'll store it in the
            // store.
            long? calculatedLength = _calculateLengthFunc();

            if (calculatedLength != null)
            {
                SetParsedValue(HttpKnownHeaderNames.ContentLength, (object)calculatedLength.Value);
            }

            return calculatedLength;
        }

        if (storedValue == null)
        {
            return null;
        }
        else
        {
            return (long)storedValue;
        }
    }
    set
    {
        SetOrRemoveParsedValue(HttpKnownHeaderNames.ContentLength, value); // box long value
        _contentLengthSet = true;
    }
}

您可以看到,如果您没有明确设置内容长度,那么它会在您第一次尝试访问它时将其添加(延迟加载)到标题中。

这证明了我在生成/展平字符串然后访问ContentLength 属性并解释不一致的枚举之后添加它的原始理论。

【讨论】:

  • 我刚刚仔细检查了我的代码,但看不到可以访问Content-Length 属性的位置,至少不是一个明显的属性,但我可能只是错过了它。非常感谢
  • 我在您的ContentDto 中注意到您有一个Data 属性。您如何以及在何处填充该属性。如果您正在读取 htto 内容的流或字符串,源代码显示在读取流之前它会调用内容长度以了解要读取多少内容。那可能是你的罪魁祸首。
  • 是的,你是对的,这正是发生的事情。你真的很有帮助,非常感谢你
【解决方案2】:

似乎 HttpContent 类的 headers 属性有一个非常奇怪的行为。不知何故,内容长度似乎是按照here 所述计算的。它没有具体解决您的问题,但您可以使用类似于初始对象的新 httpContent 对象进行测试。我很确定您将能够毫无问题地获得内容长度。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多