From 3e3772eecd4cc57399c28a3f899e6b0406ef2e1b Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Mon, 6 Nov 2017 20:03:49 -0800 Subject: [PATCH] Implement read-only HeaderDictionary (#958) --- .../HeaderDictionary.cs | 38 +++++++++++-------- .../HeaderDictionaryTests.cs | 35 +++++++++++++++++ 2 files changed, 57 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.AspNetCore.Http/HeaderDictionary.cs b/src/Microsoft.AspNetCore.Http/HeaderDictionary.cs index b0224d25ee..841cfa2642 100644 --- a/src/Microsoft.AspNetCore.Http/HeaderDictionary.cs +++ b/src/Microsoft.AspNetCore.Http/HeaderDictionary.cs @@ -58,13 +58,13 @@ namespace Microsoft.AspNetCore.Http } return StringValues.Empty; } - set { if (key == null) { throw new ArgumentNullException(nameof(key)); } + ThrowIfReadOnly(); if (StringValues.IsNullOrEmpty(value)) { @@ -90,7 +90,11 @@ namespace Microsoft.AspNetCore.Http StringValues IDictionary.this[string key] { get { return Store[key]; } - set { this[key] = value; } + set + { + ThrowIfReadOnly(); + this[key] = value; + } } public long? ContentLength @@ -110,6 +114,7 @@ namespace Microsoft.AspNetCore.Http } set { + ThrowIfReadOnly(); if (value.HasValue) { this[HeaderNames.ContentLength] = HeaderUtilities.FormatNonNegativeInt64(value.Value); @@ -125,25 +130,13 @@ namespace Microsoft.AspNetCore.Http /// Gets the number of elements contained in the ;. /// /// The number of elements contained in the . - public int Count - { - get - { - return Store?.Count ?? 0; - } - } + public int Count => Store?.Count ?? 0; /// /// Gets a value that indicates whether the is in read-only mode. /// /// true if the is in read-only mode; otherwise, false. - public bool IsReadOnly - { - get - { - return false; - } - } + public bool IsReadOnly { get; set; } public ICollection Keys { @@ -179,6 +172,7 @@ namespace Microsoft.AspNetCore.Http { throw new ArgumentNullException("The key is null"); } + ThrowIfReadOnly(); if (Store == null) { Store = new Dictionary(1, StringComparer.OrdinalIgnoreCase); @@ -197,6 +191,7 @@ namespace Microsoft.AspNetCore.Http { throw new ArgumentNullException(nameof(key)); } + ThrowIfReadOnly(); if (Store == null) { @@ -210,6 +205,7 @@ namespace Microsoft.AspNetCore.Http /// public void Clear() { + ThrowIfReadOnly(); Store?.Clear(); } @@ -270,6 +266,7 @@ namespace Microsoft.AspNetCore.Http /// true if the specified object was removed from the collection; otherwise, false. public bool Remove(KeyValuePair item) { + ThrowIfReadOnly(); if (Store == null) { return false; @@ -291,6 +288,7 @@ namespace Microsoft.AspNetCore.Http /// true if the specified object was removed from the collection; otherwise, false. public bool Remove(string key) { + ThrowIfReadOnly(); if (Store == null) { return false; @@ -356,6 +354,14 @@ namespace Microsoft.AspNetCore.Http return Store.GetEnumerator(); } + private void ThrowIfReadOnly() + { + if (IsReadOnly) + { + throw new InvalidOperationException("The response headers cannot be modified because the response has already started."); + } + } + public struct Enumerator : IEnumerator> { // Do NOT make this readonly, or MoveNext will not work diff --git a/test/Microsoft.AspNetCore.Http.Tests/HeaderDictionaryTests.cs b/test/Microsoft.AspNetCore.Http.Tests/HeaderDictionaryTests.cs index 46f8a89e06..03d642a018 100644 --- a/test/Microsoft.AspNetCore.Http.Tests/HeaderDictionaryTests.cs +++ b/test/Microsoft.AspNetCore.Http.Tests/HeaderDictionaryTests.cs @@ -68,5 +68,40 @@ namespace Microsoft.AspNetCore.Http var result = headers.GetCommaSeparatedValues("Header1"); Assert.Equal(new[] { "Value1", "Value2" }, result); } + + [Fact] + public void ReadActionsWorkWhenReadOnly() + { + var headers = new HeaderDictionary( + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "Header1", "Value1" } + }); + + headers.IsReadOnly = true; + + Assert.Single(headers); + Assert.Equal(new[] { "Header1" }, headers.Keys); + Assert.True(headers.ContainsKey("header1")); + Assert.False(headers.ContainsKey("header2")); + Assert.Equal("Value1", headers["header1"]); + Assert.Equal(new[] { "Value1" }, headers["header1"].ToArray()); + } + + [Fact] + public void WriteActionsThrowWhenReadOnly() + { + var headers = new HeaderDictionary(); + headers.IsReadOnly = true; + + Assert.Throws(() => headers["header1"] = "value1"); + Assert.Throws(() => ((IDictionary)headers)["header1"] = "value1"); + Assert.Throws(() => headers.ContentLength = 12); + Assert.Throws(() => headers.Add(new KeyValuePair("header1", "value1"))); + Assert.Throws(() => headers.Add("header1", "value1")); + Assert.Throws(() => headers.Clear()); + Assert.Throws(() => headers.Remove(new KeyValuePair("header1", "value1"))); + Assert.Throws(() => headers.Remove("header1")); + } } } \ No newline at end of file