Implement read-only HeaderDictionary (#958)

This commit is contained in:
Chris Ross 2017-11-06 20:03:49 -08:00 committed by GitHub
parent f287c46bad
commit 3e3772eecd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 57 additions and 16 deletions

View File

@ -58,13 +58,13 @@ namespace Microsoft.AspNetCore.Http
} }
return StringValues.Empty; return StringValues.Empty;
} }
set set
{ {
if (key == null) if (key == null)
{ {
throw new ArgumentNullException(nameof(key)); throw new ArgumentNullException(nameof(key));
} }
ThrowIfReadOnly();
if (StringValues.IsNullOrEmpty(value)) if (StringValues.IsNullOrEmpty(value))
{ {
@ -90,7 +90,11 @@ namespace Microsoft.AspNetCore.Http
StringValues IDictionary<string, StringValues>.this[string key] StringValues IDictionary<string, StringValues>.this[string key]
{ {
get { return Store[key]; } get { return Store[key]; }
set { this[key] = value; } set
{
ThrowIfReadOnly();
this[key] = value;
}
} }
public long? ContentLength public long? ContentLength
@ -110,6 +114,7 @@ namespace Microsoft.AspNetCore.Http
} }
set set
{ {
ThrowIfReadOnly();
if (value.HasValue) if (value.HasValue)
{ {
this[HeaderNames.ContentLength] = HeaderUtilities.FormatNonNegativeInt64(value.Value); this[HeaderNames.ContentLength] = HeaderUtilities.FormatNonNegativeInt64(value.Value);
@ -125,25 +130,13 @@ namespace Microsoft.AspNetCore.Http
/// Gets the number of elements contained in the <see cref="HeaderDictionary" />;. /// Gets the number of elements contained in the <see cref="HeaderDictionary" />;.
/// </summary> /// </summary>
/// <returns>The number of elements contained in the <see cref="HeaderDictionary" />.</returns> /// <returns>The number of elements contained in the <see cref="HeaderDictionary" />.</returns>
public int Count public int Count => Store?.Count ?? 0;
{
get
{
return Store?.Count ?? 0;
}
}
/// <summary> /// <summary>
/// Gets a value that indicates whether the <see cref="HeaderDictionary" /> is in read-only mode. /// Gets a value that indicates whether the <see cref="HeaderDictionary" /> is in read-only mode.
/// </summary> /// </summary>
/// <returns>true if the <see cref="HeaderDictionary" /> is in read-only mode; otherwise, false.</returns> /// <returns>true if the <see cref="HeaderDictionary" /> is in read-only mode; otherwise, false.</returns>
public bool IsReadOnly public bool IsReadOnly { get; set; }
{
get
{
return false;
}
}
public ICollection<string> Keys public ICollection<string> Keys
{ {
@ -179,6 +172,7 @@ namespace Microsoft.AspNetCore.Http
{ {
throw new ArgumentNullException("The key is null"); throw new ArgumentNullException("The key is null");
} }
ThrowIfReadOnly();
if (Store == null) if (Store == null)
{ {
Store = new Dictionary<string, StringValues>(1, StringComparer.OrdinalIgnoreCase); Store = new Dictionary<string, StringValues>(1, StringComparer.OrdinalIgnoreCase);
@ -197,6 +191,7 @@ namespace Microsoft.AspNetCore.Http
{ {
throw new ArgumentNullException(nameof(key)); throw new ArgumentNullException(nameof(key));
} }
ThrowIfReadOnly();
if (Store == null) if (Store == null)
{ {
@ -210,6 +205,7 @@ namespace Microsoft.AspNetCore.Http
/// </summary> /// </summary>
public void Clear() public void Clear()
{ {
ThrowIfReadOnly();
Store?.Clear(); Store?.Clear();
} }
@ -270,6 +266,7 @@ namespace Microsoft.AspNetCore.Http
/// <returns>true if the specified object was removed from the collection; otherwise, false.</returns> /// <returns>true if the specified object was removed from the collection; otherwise, false.</returns>
public bool Remove(KeyValuePair<string, StringValues> item) public bool Remove(KeyValuePair<string, StringValues> item)
{ {
ThrowIfReadOnly();
if (Store == null) if (Store == null)
{ {
return false; return false;
@ -291,6 +288,7 @@ namespace Microsoft.AspNetCore.Http
/// <returns>true if the specified object was removed from the collection; otherwise, false.</returns> /// <returns>true if the specified object was removed from the collection; otherwise, false.</returns>
public bool Remove(string key) public bool Remove(string key)
{ {
ThrowIfReadOnly();
if (Store == null) if (Store == null)
{ {
return false; return false;
@ -356,6 +354,14 @@ namespace Microsoft.AspNetCore.Http
return Store.GetEnumerator(); 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<KeyValuePair<string, StringValues>> public struct Enumerator : IEnumerator<KeyValuePair<string, StringValues>>
{ {
// Do NOT make this readonly, or MoveNext will not work // Do NOT make this readonly, or MoveNext will not work

View File

@ -68,5 +68,40 @@ namespace Microsoft.AspNetCore.Http
var result = headers.GetCommaSeparatedValues("Header1"); var result = headers.GetCommaSeparatedValues("Header1");
Assert.Equal(new[] { "Value1", "Value2" }, result); Assert.Equal(new[] { "Value1", "Value2" }, result);
} }
[Fact]
public void ReadActionsWorkWhenReadOnly()
{
var headers = new HeaderDictionary(
new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase)
{
{ "Header1", "Value1" }
});
headers.IsReadOnly = true;
Assert.Single(headers);
Assert.Equal<string>(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<InvalidOperationException>(() => headers["header1"] = "value1");
Assert.Throws<InvalidOperationException>(() => ((IDictionary<string, StringValues>)headers)["header1"] = "value1");
Assert.Throws<InvalidOperationException>(() => headers.ContentLength = 12);
Assert.Throws<InvalidOperationException>(() => headers.Add(new KeyValuePair<string, StringValues>("header1", "value1")));
Assert.Throws<InvalidOperationException>(() => headers.Add("header1", "value1"));
Assert.Throws<InvalidOperationException>(() => headers.Clear());
Assert.Throws<InvalidOperationException>(() => headers.Remove(new KeyValuePair<string, StringValues>("header1", "value1")));
Assert.Throws<InvalidOperationException>(() => headers.Remove("header1"));
}
} }
} }