HttpContent Headers - несогласованное перечисление

Я преобразую 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 -подобного метода следующим образом:

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

Ответ 1

Ваш пример неполный. Я только смог восстановить вашу проблему, когда я обратился к свойству ContentLength, прежде чем вызывать метод расширения. Где-то в вашем коде (скорее всего,//остальной настройке) вы прямо или косвенно вызываете это свойство, которое, скорее всего, следует за ленивым шаблоном загрузки, а затем включается в заголовок, когда вы вызываете ваш метод расширения и включаете его в построенной строке. Они не совпадают, потому что вы генерируете свою ручную строку перед доступом к свойству длины содержимого.

В исходном коде 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 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 и объяснили несогласованное перечисление.

Ответ 2

Кажется, что класс HttpContent имеет довольно странное поведение с свойствами заголовков. Как-то длина контента, по-видимому, вычисляется, как указано здесь. Это не касается конкретной проблемы, но вы можете сделать тест с новым объектом httpContent, аналогичным исходному. Я уверен, что вы сможете получить длину контента без проблем.