From b2601edb8659236858d4cbf1f368ac3be910198b Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Thu, 6 Jun 2024 16:57:41 +0200 Subject: [PATCH] Avoid a few allocations in StringContent (#103008) --- .../src/System/Net/Http/StringContent.cs | 26 ++++++++- .../FunctionalTests/StringContentTest.cs | 56 +++++++++++++++++-- 2 files changed, 75 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/StringContent.cs b/src/libraries/System.Net.Http/src/System/Net/Http/StringContent.cs index 519d15a60012f..6d276819e4331 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/StringContent.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/StringContent.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using System.IO; using System.Net.Http.Headers; using System.Text; @@ -45,8 +46,31 @@ public StringContent(string content, Encoding? encoding) /// The encoding to use for the content. /// The media type to use for the content. public StringContent(string content, Encoding? encoding, string mediaType) - : this(content, encoding, new MediaTypeHeaderValue(mediaType ?? DefaultMediaType, (encoding ?? DefaultStringEncoding).WebName)) + : base(GetContentByteArray(content, encoding)) { + Debug.Assert(DefaultStringEncoding.WebName == "utf-8"); + + encoding ??= DefaultStringEncoding; + mediaType ??= DefaultMediaType; + + // Avoid allocating MediaTypeHeaderValue and related objects for common media types. + if (ReferenceEquals(encoding, DefaultStringEncoding)) + { + string? knownValue = mediaType switch + { + "text/plain" => "text/plain; charset=utf-8", + "application/json" => "application/json; charset=utf-8", + _ => null + }; + + if (knownValue is not null) + { + Headers.TryAddWithoutValidation(KnownHeaders.ContentType.Descriptor, knownValue); + return; + } + } + + Headers.ContentType = new MediaTypeHeaderValue(mediaType, encoding.WebName); } /// Creates a new instance of the class. diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/StringContentTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/StringContentTest.cs index 462f9b2d6947c..2abf62135d5bb 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/StringContentTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/StringContentTest.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; @@ -72,14 +73,57 @@ public async Task Ctor_DefineNoEncoding_DefaultEncodingUsed() } [Fact] - public void Ctor_PassNullStringForMediaType_DefaultMediaTypeUsed() + public void Ctor_SetsDefaultContentTypeHeader() { - string sourceString = "\u00C4\u00E4\u00FC\u00DC"; - Encoding defaultStringEncoding = Encoding.GetEncoding("utf-8"); - var content = new StringContent(sourceString, defaultStringEncoding, ((string)null)!); + var content = new StringContent("Foo"); + Assert.Equal("text/plain; charset=utf-8", content.Headers.ContentType.ToString()); - // If no media is passed-in, the default is used - Assert.Equal("text/plain", content.Headers.ContentType.MediaType); + content = new StringContent("Foo", (Encoding)null); + Assert.Equal("text/plain; charset=utf-8", content.Headers.ContentType.ToString()); + + content = new StringContent("Foo", Encoding.UTF8); + Assert.Equal("text/plain; charset=utf-8", content.Headers.ContentType.ToString()); + + content = new StringContent("Foo", Encoding.UTF8, "text/plain"); + Assert.Equal("text/plain; charset=utf-8", content.Headers.ContentType.ToString()); + + content = new StringContent("Foo", null, "text/plain"); + Assert.Equal("text/plain; charset=utf-8", content.Headers.ContentType.ToString()); + + content = new StringContent("Foo", Encoding.UTF8, (string)null); + Assert.Equal("text/plain; charset=utf-8", content.Headers.ContentType.ToString()); + + content = new StringContent("Foo", null, (string)null); + Assert.Equal("text/plain; charset=utf-8", content.Headers.ContentType.ToString()); + + content = new StringContent("Foo", (MediaTypeHeaderValue)null); + Assert.Null(content.Headers.ContentType); + + content = new StringContent("Foo", null, (MediaTypeHeaderValue)null); + Assert.Null(content.Headers.ContentType); + } + + [Theory] + [InlineData("text/plain")] + [InlineData("application/json")] + [InlineData("application/xml")] + [InlineData("foo/bar")] + public void Ctor_SetsContentTypeHeader(string mediaType) + { + var content = new StringContent("foo", Encoding.UTF8, mediaType); + Assert.Equal($"{mediaType}; charset=utf-8", content.Headers.ContentType.ToString()); + + content = new StringContent("foo", null, mediaType); + Assert.Equal($"{mediaType}; charset=utf-8", content.Headers.ContentType.ToString()); + + content = new StringContent("foo", Encoding.ASCII, mediaType); + Assert.Equal($"{mediaType}; charset=us-ascii", content.Headers.ContentType.ToString()); + + content = new StringContent("foo", new MediaTypeHeaderValue(mediaType)); + Assert.Equal(mediaType, content.Headers.ContentType.ToString()); + + content = new StringContent("foo", Encoding.UTF8, new MediaTypeHeaderValue(mediaType, "ascii")); + Assert.Equal($"{mediaType}; charset=ascii", content.Headers.ContentType.ToString()); } [Fact]