From b7fb7abd8fb5657055b9c904ee0b66a4fb22c799 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Mon, 3 Jun 2024 22:11:38 +0200 Subject: [PATCH] Avoid a few allocations in StringContent --- .../src/System/Net/Http/StringContent.cs | 26 +++++++- .../tests/UnitTests/StringContentTests.cs | 66 +++++++++++++++++++ .../System.Net.Http.Unit.Tests.csproj | 1 + 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 src/libraries/System.Net.Http/tests/UnitTests/StringContentTests.cs 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 519d15a60012fa..6d276819e43315 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/UnitTests/StringContentTests.cs b/src/libraries/System.Net.Http/tests/UnitTests/StringContentTests.cs new file mode 100644 index 00000000000000..bd2e98ed380a88 --- /dev/null +++ b/src/libraries/System.Net.Http/tests/UnitTests/StringContentTests.cs @@ -0,0 +1,66 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Net.Http.Headers; +using System.Text; +using Xunit; + +namespace System.Net.Http.Tests +{ + public class StringContentTests + { + [Fact] + public void StringContent_SetsDefaultContentTypeHeader() + { + var content = new StringContent("Foo"); + Assert.Equal("text/plain; charset=utf-8", content.Headers.ContentType.ToString()); + + 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 StringContent_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()); + } + } +} diff --git a/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj b/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj index e8a6d2776a0dd2..ef0214d66ab6c3 100755 --- a/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj +++ b/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj @@ -329,6 +329,7 @@ +