diff --git a/src/Microsoft.AspNet.Http.Abstractions/IHeaderDictionary.cs b/src/Microsoft.AspNet.Http.Abstractions/IHeaderDictionary.cs deleted file mode 100644 index b6483c15..00000000 --- a/src/Microsoft.AspNet.Http.Abstractions/IHeaderDictionary.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using Microsoft.Extensions.Primitives; - -namespace Microsoft.AspNet.Http -{ - /// - /// Represents request and response headers - /// - public interface IHeaderDictionary : IReadableStringCollection, IDictionary - { - // This property is duplicated to resolve an ambiguity between IReadableStringCollection and IDictionary - /// - /// - /// - /// - /// The stored value, or StringValues.Empty if the key is not present. - new StringValues this[string key] { get; set; } - - // This property is duplicated to resolve an ambiguity between IReadableStringCollection.Count and IDictionary.Count - /// - /// Gets the number of elements contained in the collection. - /// - new int Count { get; } - - // This property is duplicated to resolve an ambiguity between IReadableStringCollection.Keys and IDictionary.Keys - /// - /// Gets a collection containing the keys. - /// - new ICollection Keys { get; } - } -} diff --git a/src/Microsoft.AspNet.Http.Features/IHeaderDictionary.cs b/src/Microsoft.AspNet.Http.Features/IHeaderDictionary.cs new file mode 100644 index 00000000..0c03c29d --- /dev/null +++ b/src/Microsoft.AspNet.Http.Features/IHeaderDictionary.cs @@ -0,0 +1,21 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNet.Http +{ + /// + /// Represents request and response headers + /// + public interface IHeaderDictionary : IDictionary + { + /// + /// IHeaderDictionary has a different indexer contract than IDictionary, where it will return StringValues.Empty for missing entries. + /// + /// + /// The stored value, or StringValues.Empty if the key is not present. + new StringValues this[string key] { get; set; } + } +} diff --git a/src/Microsoft.AspNet.Http.Features/IHttpRequestFeature.cs b/src/Microsoft.AspNet.Http.Features/IHttpRequestFeature.cs index d67143e0..988f2b61 100644 --- a/src/Microsoft.AspNet.Http.Features/IHttpRequestFeature.cs +++ b/src/Microsoft.AspNet.Http.Features/IHttpRequestFeature.cs @@ -1,9 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; using System.IO; -using Microsoft.Extensions.Primitives; namespace Microsoft.AspNet.Http.Features { @@ -15,7 +13,7 @@ public interface IHttpRequestFeature string PathBase { get; set; } string Path { get; set; } string QueryString { get; set; } - IDictionary Headers { get; set; } + IHeaderDictionary Headers { get; set; } Stream Body { get; set; } } } diff --git a/src/Microsoft.AspNet.Http.Features/IHttpResponseFeature.cs b/src/Microsoft.AspNet.Http.Features/IHttpResponseFeature.cs index 2a8b10ab..67cfa283 100644 --- a/src/Microsoft.AspNet.Http.Features/IHttpResponseFeature.cs +++ b/src/Microsoft.AspNet.Http.Features/IHttpResponseFeature.cs @@ -2,10 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -using Microsoft.Extensions.Primitives; namespace Microsoft.AspNet.Http.Features { @@ -13,7 +11,7 @@ public interface IHttpResponseFeature { int StatusCode { get; set; } string ReasonPhrase { get; set; } - IDictionary Headers { get; set; } + IHeaderDictionary Headers { get; set; } Stream Body { get; set; } bool HasStarted { get; } void OnStarting(Func callback, object state); diff --git a/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs b/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs index 479ead6e..b06bc6f4 100644 --- a/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs +++ b/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs @@ -123,7 +123,7 @@ public override string Protocol public override IHeaderDictionary Headers { - get { return new HeaderDictionary(HttpRequestFeature.Headers); } + get { return HttpRequestFeature.Headers; } } public override IReadableStringCollection Cookies diff --git a/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs b/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs index e5f09f7c..81c5539a 100644 --- a/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs +++ b/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs @@ -43,7 +43,7 @@ public override int StatusCode public override IHeaderDictionary Headers { - get { return new HeaderDictionary(HttpResponseFeature.Headers); } + get { return HttpResponseFeature.Headers; } } public override Stream Body diff --git a/src/Microsoft.AspNet.Http/Features/HttpRequestFeature.cs b/src/Microsoft.AspNet.Http/Features/HttpRequestFeature.cs index 900976e6..de48b71b 100644 --- a/src/Microsoft.AspNet.Http/Features/HttpRequestFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/HttpRequestFeature.cs @@ -1,10 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; using System.IO; -using Microsoft.Extensions.Primitives; +using Microsoft.AspNet.Http.Internal; namespace Microsoft.AspNet.Http.Features.Internal { @@ -12,7 +10,7 @@ public class HttpRequestFeature : IHttpRequestFeature { public HttpRequestFeature() { - Headers = new Dictionary(StringComparer.OrdinalIgnoreCase); + Headers = new HeaderDictionary(); Body = Stream.Null; Protocol = string.Empty; Scheme = string.Empty; @@ -28,7 +26,7 @@ public HttpRequestFeature() public string PathBase { get; set; } public string Path { get; set; } public string QueryString { get; set; } - public IDictionary Headers { get; set; } + public IHeaderDictionary Headers { get; set; } public Stream Body { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http/Features/HttpResponseFeature.cs b/src/Microsoft.AspNet.Http/Features/HttpResponseFeature.cs index 50a204ef..adb411ee 100644 --- a/src/Microsoft.AspNet.Http/Features/HttpResponseFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/HttpResponseFeature.cs @@ -2,10 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -using Microsoft.Extensions.Primitives; +using Microsoft.AspNet.Http.Internal; namespace Microsoft.AspNet.Http.Features.Internal { @@ -14,7 +13,7 @@ public class HttpResponseFeature : IHttpResponseFeature public HttpResponseFeature() { StatusCode = 200; - Headers = new Dictionary(StringComparer.OrdinalIgnoreCase); + Headers = new HeaderDictionary(); Body = Stream.Null; } @@ -22,7 +21,7 @@ public HttpResponseFeature() public string ReasonPhrase { get; set; } - public IDictionary Headers { get; set; } + public IHeaderDictionary Headers { get; set; } public Stream Body { get; set; } diff --git a/src/Microsoft.AspNet.Owin/DictionaryStringArrayWrapper.cs b/src/Microsoft.AspNet.Owin/DictionaryStringArrayWrapper.cs index 4aec5c9d..5fe22814 100644 --- a/src/Microsoft.AspNet.Owin/DictionaryStringArrayWrapper.cs +++ b/src/Microsoft.AspNet.Owin/DictionaryStringArrayWrapper.cs @@ -4,18 +4,19 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using Microsoft.AspNet.Http; using Microsoft.Extensions.Primitives; namespace Microsoft.AspNet.Owin { internal class DictionaryStringArrayWrapper : IDictionary { - public DictionaryStringArrayWrapper(IDictionary inner) + public DictionaryStringArrayWrapper(IHeaderDictionary inner) { Inner = inner; } - public readonly IDictionary Inner; + public readonly IHeaderDictionary Inner; private KeyValuePair Convert(KeyValuePair item) => new KeyValuePair(item.Key, item.Value); @@ -27,7 +28,7 @@ public DictionaryStringArrayWrapper(IDictionary inner) string[] IDictionary.this[string key] { - get { return Inner[key]; } + get { return ((IDictionary)Inner)[key]; } set { Inner[key] = value; } } diff --git a/src/Microsoft.AspNet.Owin/DictionaryStringValuesWrapper.cs b/src/Microsoft.AspNet.Owin/DictionaryStringValuesWrapper.cs index 51cd330d..aec1a0d9 100644 --- a/src/Microsoft.AspNet.Owin/DictionaryStringValuesWrapper.cs +++ b/src/Microsoft.AspNet.Owin/DictionaryStringValuesWrapper.cs @@ -4,11 +4,12 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using Microsoft.AspNet.Http; using Microsoft.Extensions.Primitives; namespace Microsoft.AspNet.Owin { - internal class DictionaryStringValuesWrapper : IDictionary + internal class DictionaryStringValuesWrapper : IHeaderDictionary { public DictionaryStringValuesWrapper(IDictionary inner) { @@ -25,6 +26,16 @@ public DictionaryStringValuesWrapper(IDictionary inner) private string[] Convert(StringValues item) => item; + StringValues IHeaderDictionary.this[string key] + { + get + { + string[] values; + return Inner.TryGetValue(key, out values) ? values : null; + } + set { Inner[key] = value; } + } + StringValues IDictionary.this[string key] { get { return Inner[key]; } diff --git a/src/Microsoft.AspNet.Owin/OwinEnvironment.cs b/src/Microsoft.AspNet.Owin/OwinEnvironment.cs index c6cdd639..5e4db89d 100644 --- a/src/Microsoft.AspNet.Owin/OwinEnvironment.cs +++ b/src/Microsoft.AspNet.Owin/OwinEnvironment.cs @@ -56,13 +56,13 @@ public OwinEnvironment(HttpContext context) { OwinConstants.RequestPath, new FeatureMap(feature => feature.Path, () => string.Empty, (feature, value) => feature.Path = Convert.ToString(value)) }, { OwinConstants.RequestQueryString, new FeatureMap(feature => Utilities.RemoveQuestionMark(feature.QueryString), () => string.Empty, (feature, value) => feature.QueryString = Utilities.AddQuestionMark(Convert.ToString(value))) }, - { OwinConstants.RequestHeaders, new FeatureMap(feature => Utilities.MakeDictionaryStringArray(feature.Headers), (feature, value) => feature.Headers = Utilities.MakeDictionaryStringValues((IDictionary)value)) }, + { OwinConstants.RequestHeaders, new FeatureMap(feature => Utilities.MakeDictionaryStringArray(feature.Headers), (feature, value) => feature.Headers = Utilities.MakeHeaderDictionary((IDictionary)value)) }, { OwinConstants.RequestBody, new FeatureMap(feature => feature.Body, () => Stream.Null, (feature, value) => feature.Body = (Stream)value) }, { OwinConstants.RequestUser, new FeatureMap(feature => feature.User, () => null, (feature, value) => feature.User = (ClaimsPrincipal)value) }, { OwinConstants.ResponseStatusCode, new FeatureMap(feature => feature.StatusCode, () => 200, (feature, value) => feature.StatusCode = Convert.ToInt32(value)) }, { OwinConstants.ResponseReasonPhrase, new FeatureMap(feature => feature.ReasonPhrase, (feature, value) => feature.ReasonPhrase = Convert.ToString(value)) }, - { OwinConstants.ResponseHeaders, new FeatureMap(feature => Utilities.MakeDictionaryStringArray(feature.Headers), (feature, value) => feature.Headers = Utilities.MakeDictionaryStringValues((IDictionary)value)) }, + { OwinConstants.ResponseHeaders, new FeatureMap(feature => Utilities.MakeDictionaryStringArray(feature.Headers), (feature, value) => feature.Headers = Utilities.MakeHeaderDictionary((IDictionary)value)) }, { OwinConstants.ResponseBody, new FeatureMap(feature => feature.Body, () => Stream.Null, (feature, value) => feature.Body = (Stream)value) }, { OwinConstants.CommonKeys.OnSendingHeaders, new FeatureMap( feature => new Action, object>((cb, state) => { diff --git a/src/Microsoft.AspNet.Owin/OwinFeatureCollection.cs b/src/Microsoft.AspNet.Owin/OwinFeatureCollection.cs index 72a7b19f..6d955194 100644 --- a/src/Microsoft.AspNet.Owin/OwinFeatureCollection.cs +++ b/src/Microsoft.AspNet.Owin/OwinFeatureCollection.cs @@ -106,9 +106,9 @@ string IHttpRequestFeature.QueryString set { Prop(OwinConstants.RequestQueryString, Utilities.RemoveQuestionMark(value)); } } - IDictionary IHttpRequestFeature.Headers + IHeaderDictionary IHttpRequestFeature.Headers { - get { return Utilities.MakeDictionaryStringValues(Prop>(OwinConstants.RequestHeaders)); } + get { return Utilities.MakeHeaderDictionary(Prop>(OwinConstants.RequestHeaders)); } set { Prop(OwinConstants.RequestHeaders, Utilities.MakeDictionaryStringArray(value)); } } @@ -136,9 +136,9 @@ string IHttpResponseFeature.ReasonPhrase set { Prop(OwinConstants.ResponseReasonPhrase, value); } } - IDictionary IHttpResponseFeature.Headers + IHeaderDictionary IHttpResponseFeature.Headers { - get { return Utilities.MakeDictionaryStringValues(Prop>(OwinConstants.ResponseHeaders)); } + get { return Utilities.MakeHeaderDictionary(Prop>(OwinConstants.ResponseHeaders)); } set { Prop(OwinConstants.ResponseHeaders, Utilities.MakeDictionaryStringArray(value)); } } diff --git a/src/Microsoft.AspNet.Owin/Utilities.cs b/src/Microsoft.AspNet.Owin/Utilities.cs index a7aa9a75..00789a86 100644 --- a/src/Microsoft.AspNet.Owin/Utilities.cs +++ b/src/Microsoft.AspNet.Owin/Utilities.cs @@ -46,7 +46,7 @@ internal static ClaimsPrincipal MakeClaimsPrincipal(IPrincipal principal) return new ClaimsPrincipal(principal); } - internal static IDictionary MakeDictionaryStringValues(IDictionary dictionary) + internal static IHeaderDictionary MakeHeaderDictionary(IDictionary dictionary) { var wrapper = dictionary as DictionaryStringArrayWrapper; if (wrapper != null) @@ -56,7 +56,7 @@ internal static IDictionary MakeDictionaryStringValues(IDi return new DictionaryStringValuesWrapper(dictionary); } - internal static IDictionary MakeDictionaryStringArray(IDictionary dictionary) + internal static IDictionary MakeDictionaryStringArray(IHeaderDictionary dictionary) { var wrapper = dictionary as DictionaryStringValuesWrapper; if (wrapper != null) diff --git a/test/Microsoft.AspNet.Http.Extensions.Tests/ResponseExtensionTests.cs b/test/Microsoft.AspNet.Http.Extensions.Tests/ResponseExtensionTests.cs index 383f7531..efa5f370 100644 --- a/test/Microsoft.AspNet.Http.Extensions.Tests/ResponseExtensionTests.cs +++ b/test/Microsoft.AspNet.Http.Extensions.Tests/ResponseExtensionTests.cs @@ -44,7 +44,7 @@ private class StartedResponseFeature : IHttpResponseFeature public bool HasStarted { get { return true; } } - public IDictionary Headers { get; set; } + public IHeaderDictionary Headers { get; set; } public string ReasonPhrase { get; set; } diff --git a/test/Microsoft.AspNet.Http.Tests/DefaultHttpRequestTests.cs b/test/Microsoft.AspNet.Http.Tests/DefaultHttpRequestTests.cs index 33f6a3ac..e020da61 100644 --- a/test/Microsoft.AspNet.Http.Tests/DefaultHttpRequestTests.cs +++ b/test/Microsoft.AspNet.Http.Tests/DefaultHttpRequestTests.cs @@ -65,7 +65,7 @@ public void Host_GetsHostFromHeaders() // Arrange const string expected = "localhost:9001"; - var headers = new Dictionary(StringComparer.OrdinalIgnoreCase) + var headers = new HeaderDictionary() { { "Host", expected }, }; @@ -85,7 +85,7 @@ public void Host_DecodesPunyCode() // Arrange const string expected = "löcalhöst"; - var headers = new Dictionary(StringComparer.OrdinalIgnoreCase) + var headers = new HeaderDictionary() { { "Host", "xn--lcalhst-90ae" }, }; @@ -105,7 +105,7 @@ public void Host_EncodesPunyCode() // Arrange const string expected = "xn--lcalhst-90ae"; - var headers = new Dictionary(StringComparer.OrdinalIgnoreCase); + var headers = new HeaderDictionary(); var request = CreateRequest(headers); @@ -188,7 +188,7 @@ public void Cookies_GetAndSet() Assert.Equal(new[] { "name2=value2" }, cookieHeaders); } - private static HttpRequest CreateRequest(IDictionary headers) + private static HttpRequest CreateRequest(IHeaderDictionary headers) { var context = new DefaultHttpContext(); context.Features.Get().Headers = headers; @@ -217,7 +217,7 @@ private static HttpRequest GetRequestWithAcceptCharsetHeader(string acceptCharse private static HttpRequest GetRequestWithHeader(string headerName, string headerValue) { - var headers = new Dictionary(StringComparer.OrdinalIgnoreCase); + var headers = new HeaderDictionary(); if (headerValue != null) { headers.Add(headerName, headerValue);