From 94c04ffb2b22cf63ed130232eb055fa9a00ac0a8 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Thu, 22 Oct 2015 14:13:41 +0100 Subject: [PATCH 1/9] Less alloc/wrapping/boxing for Query, Forms, Cookies --- .../Extensions/MapWhenExtensions.cs | 1 - .../FragmentString.cs | 2 +- .../HostString.cs | 10 +- .../HttpRequest.cs | 34 +- .../IFormCollection.cs | 13 - .../IReadableStringCollection.cs | 39 -- .../PathString.cs | 21 +- .../QueryString.cs | 4 +- .../HeaderDictionaryExtensions.cs | 2 +- .../HeaderDictionaryTypeExtensions.cs | 33 +- .../Internal/HeaderSegment.cs | 5 +- .../Internal/HeaderSegmentCollection.cs | 5 +- .../Internal/ParsingHelpers.cs | 41 +- .../Internal/StringSegment.cs | 5 +- .../RequestHeaders.cs | 22 +- .../ResponseHeaders.cs | 22 +- .../UriHelper.cs | 4 +- .../IFormCollection.cs | 28 ++ .../IFormFile.cs | 3 + .../IFormFileCollection.cs | 3 + .../IHeaderDictionary.cs | 2 +- .../IQueryCollection.cs | 21 + .../IRequestCookies.cs | 21 + .../DefaultHttpRequest.cs | 6 +- .../DefaultHttpResponse.cs | 2 +- .../DefaultWebSocketManager.cs | 2 +- .../Features/FeatureHelpers.cs | 7 +- .../Features/FormFeature.cs | 70 ++- .../Features/FormFile.cs | 4 +- .../Features/HttpRequestLifetimeFeature.cs | 1 - .../Features/IQueryFeature.cs | 2 +- .../Features/IRequestCookiesFeature.cs | 5 +- .../Features/QueryFeature.cs | 35 +- .../Features/RequestCookiesFeature.cs | 28 +- .../Features/ResponseCookiesFeature.cs | 2 +- src/Microsoft.AspNet.Http/FormCollection.cs | 404 ++++++++++++++- src/Microsoft.AspNet.Http/HeaderDictionary.cs | 376 ++++++++++---- src/Microsoft.AspNet.Http/ItemsDictionary.cs | 7 +- src/Microsoft.AspNet.Http/ParsingHelpers.cs | 38 +- src/Microsoft.AspNet.Http/QueryCollection.cs | 412 +++++++++++++++ .../ReadableStringCollection.cs | 99 ---- .../ReferenceReadStream.cs | 2 +- src/Microsoft.AspNet.Http/RequestCookies.cs | 467 ++++++++++++++++++ .../RequestCookiesCollection.cs | 105 ---- src/Microsoft.AspNet.Http/ResponseCookies.cs | 60 ++- .../BufferedReadStream.cs | 44 +- .../FormReader.cs | 26 +- .../KeyValueAccumulator.cs | 32 +- .../MultipartReader.cs | 6 +- .../MultipartSection.cs | 6 +- .../QueryHelpers.cs | 17 +- .../HeaderDictionaryTypeExtensionsTest.cs | 9 +- .../DefaultHttpRequestTests.cs | 6 +- .../QueryHelpersTests.cs | 10 +- 54 files changed, 2035 insertions(+), 596 deletions(-) delete mode 100644 src/Microsoft.AspNet.Http.Abstractions/IFormCollection.cs delete mode 100644 src/Microsoft.AspNet.Http.Abstractions/IReadableStringCollection.cs create mode 100644 src/Microsoft.AspNet.Http.Features/IFormCollection.cs rename src/{Microsoft.AspNet.Http.Abstractions => Microsoft.AspNet.Http.Features}/IFormFile.cs (83%) rename src/{Microsoft.AspNet.Http.Abstractions => Microsoft.AspNet.Http.Features}/IFormFileCollection.cs (81%) create mode 100644 src/Microsoft.AspNet.Http.Features/IQueryCollection.cs create mode 100644 src/Microsoft.AspNet.Http.Features/IRequestCookies.cs create mode 100644 src/Microsoft.AspNet.Http/QueryCollection.cs delete mode 100644 src/Microsoft.AspNet.Http/ReadableStringCollection.cs create mode 100644 src/Microsoft.AspNet.Http/RequestCookies.cs delete mode 100644 src/Microsoft.AspNet.Http/RequestCookiesCollection.cs diff --git a/src/Microsoft.AspNet.Http.Abstractions/Extensions/MapWhenExtensions.cs b/src/Microsoft.AspNet.Http.Abstractions/Extensions/MapWhenExtensions.cs index 7bd65846..8e615d89 100644 --- a/src/Microsoft.AspNet.Http.Abstractions/Extensions/MapWhenExtensions.cs +++ b/src/Microsoft.AspNet.Http.Abstractions/Extensions/MapWhenExtensions.cs @@ -5,7 +5,6 @@ using Microsoft.AspNet.Http; using Microsoft.AspNet.Builder.Extensions; - namespace Microsoft.AspNet.Builder { using Predicate = Func; diff --git a/src/Microsoft.AspNet.Http.Abstractions/FragmentString.cs b/src/Microsoft.AspNet.Http.Abstractions/FragmentString.cs index 87b22df2..41757d29 100644 --- a/src/Microsoft.AspNet.Http.Abstractions/FragmentString.cs +++ b/src/Microsoft.AspNet.Http.Abstractions/FragmentString.cs @@ -100,7 +100,7 @@ public static FragmentString FromUriComponent(Uri uri) string fragmentValue = uri.GetComponents(UriComponents.Fragment, UriFormat.UriEscaped); if (!string.IsNullOrEmpty(fragmentValue)) { - fragmentValue = "#" + fragmentValue; + fragmentValue = $"#{fragmentValue}"; } return new FragmentString(fragmentValue); } diff --git a/src/Microsoft.AspNet.Http.Abstractions/HostString.cs b/src/Microsoft.AspNet.Http.Abstractions/HostString.cs index 59fb4f23..b5debac3 100644 --- a/src/Microsoft.AspNet.Http.Abstractions/HostString.cs +++ b/src/Microsoft.AspNet.Http.Abstractions/HostString.cs @@ -70,18 +70,18 @@ public string ToUriComponent() && _value.IndexOf(':', index + 1) >= 0) { // IPv6 without brackets ::1 is the only type of host with 2 or more colons - return "[" + _value + "]"; + return $"[{_value}]"; } else if (index >= 0) { // Has a port string port = _value.Substring(index); - IdnMapping mapping = new IdnMapping(); + var mapping = new IdnMapping(); return mapping.GetAscii(_value, 0, index) + port; } else { - IdnMapping mapping = new IdnMapping(); + var mapping = new IdnMapping(); return mapping.GetAscii(_value); } } @@ -115,12 +115,12 @@ public static HostString FromUriComponent(string uriComponent) { // Has a port string port = uriComponent.Substring(index); - IdnMapping mapping = new IdnMapping(); + var mapping = new IdnMapping(); uriComponent = mapping.GetUnicode(uriComponent, 0, index) + port; } else { - IdnMapping mapping = new IdnMapping(); + var mapping = new IdnMapping(); uriComponent = mapping.GetUnicode(uriComponent); } } diff --git a/src/Microsoft.AspNet.Http.Abstractions/HttpRequest.cs b/src/Microsoft.AspNet.Http.Abstractions/HttpRequest.cs index 283eddf4..9dbe0843 100644 --- a/src/Microsoft.AspNet.Http.Abstractions/HttpRequest.cs +++ b/src/Microsoft.AspNet.Http.Abstractions/HttpRequest.cs @@ -24,13 +24,13 @@ public abstract class HttpRequest public abstract string Method { get; set; } /// - /// Gets or set the HTTP request scheme from owin.RequestScheme. + /// Gets or set the HTTP request scheme. /// - /// The HTTP request scheme from owin.RequestScheme. + /// The HTTP request scheme. public abstract string Scheme { get; set; } /// - /// Returns true if the owin.RequestScheme is https. + /// Returns true if the RequestScheme is https. /// /// true if this request is using https; otherwise, false. public abstract bool IsHttps { get; set; } @@ -42,33 +42,33 @@ public abstract class HttpRequest public abstract HostString Host { get; set; } /// - /// Gets or set the owin.RequestPathBase. + /// Gets or set the RequestPathBase. /// - /// The owin.RequestPathBase. + /// The RequestPathBase. public abstract PathString PathBase { get; set; } /// - /// Gets or set the request path from owin.RequestPath. + /// Gets or set the request path from RequestPath. /// - /// The request path from owin.RequestPath. + /// The request path from RequestPath. public abstract PathString Path { get; set; } /// - /// Gets or set the query string from owin.RequestQueryString. + /// Gets or set the query string. /// - /// The query string from owin.RequestQueryString. + /// The query string. public abstract QueryString QueryString { get; set; } /// - /// Gets the query value collection parsed from owin.RequestQueryString. + /// Gets the query value collection parsed from RequestQueryString. /// - /// The query value collection parsed from owin.RequestQueryString. - public abstract IReadableStringCollection Query { get; set; } + /// The query value collection parsed from RequestQueryString. + public abstract IQueryCollection Query { get; set; } /// - /// Gets or set the owin.RequestProtocol. + /// Gets or set the RequestProtocol. /// - /// The owin.RequestProtocol. + /// The RequestProtocol. public abstract string Protocol { get; set; } /// @@ -81,7 +81,7 @@ public abstract class HttpRequest /// Gets the collection of Cookies for this request. /// /// The collection of Cookies for this request. - public abstract IReadableStringCollection Cookies { get; set; } + public abstract IRequestCookies Cookies { get; set; } /// /// Gets or sets the Content-Length header @@ -95,9 +95,9 @@ public abstract class HttpRequest public abstract string ContentType { get; set; } /// - /// Gets or set the owin.RequestBody Stream. + /// Gets or set the RequestBody Stream. /// - /// The owin.RequestBody Stream. + /// The RequestBody Stream. public abstract Stream Body { get; set; } /// diff --git a/src/Microsoft.AspNet.Http.Abstractions/IFormCollection.cs b/src/Microsoft.AspNet.Http.Abstractions/IFormCollection.cs deleted file mode 100644 index 68505962..00000000 --- a/src/Microsoft.AspNet.Http.Abstractions/IFormCollection.cs +++ /dev/null @@ -1,13 +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. - -namespace Microsoft.AspNet.Http -{ - /// - /// Contains the parsed form values. - /// - public interface IFormCollection : IReadableStringCollection - { - IFormFileCollection Files { get; } - } -} diff --git a/src/Microsoft.AspNet.Http.Abstractions/IReadableStringCollection.cs b/src/Microsoft.AspNet.Http.Abstractions/IReadableStringCollection.cs deleted file mode 100644 index 81c84087..00000000 --- a/src/Microsoft.AspNet.Http.Abstractions/IReadableStringCollection.cs +++ /dev/null @@ -1,39 +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 -{ - /// - /// Accessors for headers, query, forms, etc. - /// - public interface IReadableStringCollection : IEnumerable> - { - /// - /// Get the associated value from the collection. - /// Returns StringValues.Empty if the key is not present. - /// - /// - /// - StringValues this[string key] { get; } - - /// - /// Gets the number of elements contained in the collection. - /// - int Count { get; } - - /// - /// Gets a collection containing the keys. - /// - ICollection Keys { get; } - - /// - /// Determines whether the collection contains an element with the specified key. - /// - /// - /// - bool ContainsKey(string key); - } -} diff --git a/src/Microsoft.AspNet.Http.Abstractions/PathString.cs b/src/Microsoft.AspNet.Http.Abstractions/PathString.cs index ebcc9b10..8a76d8f5 100644 --- a/src/Microsoft.AspNet.Http.Abstractions/PathString.cs +++ b/src/Microsoft.AspNet.Http.Abstractions/PathString.cs @@ -12,6 +12,8 @@ namespace Microsoft.AspNet.Http /// public struct PathString : IEquatable { + private static readonly char[] splitChar = { '/' }; + /// /// Represents the empty path. This field is read-only. /// @@ -66,7 +68,24 @@ public override string ToString() public string ToUriComponent() { // TODO: Measure the cost of this escaping and consider optimizing. - return HasValue ? string.Join("/", _value.Split('/').Select(UrlEncoder.Default.Encode)) : string.Empty; + if (!HasValue) + { + return string.Empty; + } + var values = _value.Split(splitChar); + var changed = false; + for (var i = 0; i < values.Length; i++) + { + var value = values[i]; + values[i] = UrlEncoder.Default.Encode(value); + + if (!changed && value != values[i]) + { + changed = true; + } + } + + return changed ? string.Join("/", values) : _value; } /// diff --git a/src/Microsoft.AspNet.Http.Abstractions/QueryString.cs b/src/Microsoft.AspNet.Http.Abstractions/QueryString.cs index af2feeed..f3ec5df0 100644 --- a/src/Microsoft.AspNet.Http.Abstractions/QueryString.cs +++ b/src/Microsoft.AspNet.Http.Abstractions/QueryString.cs @@ -128,7 +128,7 @@ public static QueryString Create(string name, string value) throw new ArgumentNullException(nameof(value)); } - return new QueryString("?" + UrlEncoder.Default.Encode(name) + '=' + UrlEncoder.Default.Encode(value)); + return new QueryString($"?{UrlEncoder.Default.Encode(name)}={UrlEncoder.Default.Encode(value)}"); } /// @@ -157,7 +157,7 @@ public static QueryString Create(IEnumerable> param /// /// /// The resulting QueryString - public static QueryString Create(IEnumerable> parameters) + public static QueryString Create(IQueryCollection parameters) { var builder = new StringBuilder(); bool first = true; diff --git a/src/Microsoft.AspNet.Http.Extensions/HeaderDictionaryExtensions.cs b/src/Microsoft.AspNet.Http.Extensions/HeaderDictionaryExtensions.cs index 6a8d909d..fedf74ab 100644 --- a/src/Microsoft.AspNet.Http.Extensions/HeaderDictionaryExtensions.cs +++ b/src/Microsoft.AspNet.Http.Extensions/HeaderDictionaryExtensions.cs @@ -36,7 +36,7 @@ public static void AppendCommaSeparatedValues(this IHeaderDictionary headers, st /// the associated values from the collection separated into individual values, or StringValues.Empty if the key is not present. public static string[] GetCommaSeparatedValues(this IHeaderDictionary headers, string key) { - return ParsingHelpers.GetHeaderSplit(headers, key); + return ParsingHelpers.GetHeaderSplit(headers, key).ToArray(); } /// diff --git a/src/Microsoft.AspNet.Http.Extensions/HeaderDictionaryTypeExtensions.cs b/src/Microsoft.AspNet.Http.Extensions/HeaderDictionaryTypeExtensions.cs index 5b1f70f4..19029b32 100644 --- a/src/Microsoft.AspNet.Http.Extensions/HeaderDictionaryTypeExtensions.cs +++ b/src/Microsoft.AspNet.Http.Extensions/HeaderDictionaryTypeExtensions.cs @@ -80,7 +80,24 @@ internal static void SetList(this IHeaderDictionary headers, string name, ILi } else { - headers[name] = values.Select(value => value.ToString()).ToArray(); + + switch (values.Count) + { + case 0: + headers.Remove(name); + break; + case 1: + headers[name] = new StringValues(values[0].ToString()); + break; + default: + var newValues = new string[values.Count]; + for (var i = 0; i < values.Count; i++) + { + newValues[i] = values[i].ToString(); + } + headers[name] = new StringValues(newValues); + break; + } } } @@ -139,7 +156,7 @@ internal static T Get(this IHeaderDictionary headers, string name) if (KnownParsers.TryGetValue(typeof(T), out temp)) { var func = (Func)temp; - return func(headers[name]); + return func(headers[name].ToString()); } var value = headers[name]; @@ -148,7 +165,7 @@ internal static T Get(this IHeaderDictionary headers, string name) return default(T); } - return GetViaReflection(value); + return GetViaReflection(value.ToString()); } internal static IList GetList(this IHeaderDictionary headers, string name) @@ -162,7 +179,7 @@ internal static IList GetList(this IHeaderDictionary headers, string name) if (KnownListParsers.TryGetValue(typeof(T), out temp)) { var func = (Func, IList>)temp; - return func(headers[name]); + return func(headers[name].ToArray()); } var values = headers[name]; @@ -179,7 +196,7 @@ private static T GetViaReflection(string value) // TODO: Cache the reflected type for later? Only if success? var type = typeof(T); var method = type.GetMethods(BindingFlags.Public | BindingFlags.Static) - .Where(methodInfo => + .FirstOrDefault(methodInfo => { if (string.Equals("TryParse", methodInfo.Name, StringComparison.Ordinal) && methodInfo.ReturnParameter.ParameterType.Equals(typeof(bool))) @@ -191,7 +208,7 @@ private static T GetViaReflection(string value) && methodParams[1].ParameterType.Equals(type.MakeByRefType()); } return false; - }).FirstOrDefault(); + }); if (method == null) { @@ -213,7 +230,7 @@ private static IList GetListViaReflection(StringValues values) // TODO: Cache the reflected type for later? Only if success? var type = typeof(T); var method = type.GetMethods(BindingFlags.Public | BindingFlags.Static) - .Where(methodInfo => + .FirstOrDefault(methodInfo => { if (string.Equals("TryParseList", methodInfo.Name, StringComparison.Ordinal) && methodInfo.ReturnParameter.ParameterType.Equals(typeof(Boolean))) @@ -225,7 +242,7 @@ private static IList GetListViaReflection(StringValues values) && methodParams[1].ParameterType.Equals(typeof(IList).MakeByRefType()); } return false; - }).FirstOrDefault(); + }); if (method == null) { diff --git a/src/Microsoft.AspNet.Http.Extensions/Internal/HeaderSegment.cs b/src/Microsoft.AspNet.Http.Extensions/Internal/HeaderSegment.cs index 72e94d3e..74b49e67 100644 --- a/src/Microsoft.AspNet.Http.Extensions/Internal/HeaderSegment.cs +++ b/src/Microsoft.AspNet.Http.Extensions/Internal/HeaderSegment.cs @@ -1,4 +1,7 @@ -using System; +// 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; namespace Microsoft.AspNet.Http.Internal { diff --git a/src/Microsoft.AspNet.Http.Extensions/Internal/HeaderSegmentCollection.cs b/src/Microsoft.AspNet.Http.Extensions/Internal/HeaderSegmentCollection.cs index 6806dbaa..693bce0c 100644 --- a/src/Microsoft.AspNet.Http.Extensions/Internal/HeaderSegmentCollection.cs +++ b/src/Microsoft.AspNet.Http.Extensions/Internal/HeaderSegmentCollection.cs @@ -1,3 +1,6 @@ +// 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; using System.Collections.Generic; @@ -16,7 +19,7 @@ public HeaderSegmentCollection(StringValues headers) public bool Equals(HeaderSegmentCollection other) { - return Equals(_headers, other._headers); + return StringValues.Equals(_headers, other._headers); } public override bool Equals(object obj) diff --git a/src/Microsoft.AspNet.Http.Extensions/Internal/ParsingHelpers.cs b/src/Microsoft.AspNet.Http.Extensions/Internal/ParsingHelpers.cs index f829eec9..8d1e5f83 100644 --- a/src/Microsoft.AspNet.Http.Extensions/Internal/ParsingHelpers.cs +++ b/src/Microsoft.AspNet.Http.Extensions/Internal/ParsingHelpers.cs @@ -10,13 +10,13 @@ namespace Microsoft.AspNet.Http.Internal { internal static class ParsingHelpers { - public static StringValues GetHeader(IDictionary headers, string key) + public static StringValues GetHeader(IHeaderDictionary headers, string key) { StringValues value; return headers.TryGetValue(key, out value) ? value : StringValues.Empty; } - public static StringValues GetHeaderSplit(IDictionary headers, string key) + public static StringValues GetHeaderSplit(IHeaderDictionary headers, string key) { var values = GetHeaderUnmodified(headers, key); return new StringValues(GetHeaderSplitImplementation(values).ToArray()); @@ -33,7 +33,7 @@ private static IEnumerable GetHeaderSplitImplementation(StringValues val } } - public static StringValues GetHeaderUnmodified(IDictionary headers, string key) + public static StringValues GetHeaderUnmodified(IHeaderDictionary headers, string key) { if (headers == null) { @@ -44,7 +44,7 @@ public static StringValues GetHeaderUnmodified(IDictionary return headers.TryGetValue(key, out values) ? values : StringValues.Empty; } - public static void SetHeaderJoined(IDictionary headers, string key, StringValues value) + public static void SetHeaderJoined(IHeaderDictionary headers, string key, StringValues value) { if (headers == null) { @@ -61,35 +61,26 @@ public static void SetHeaderJoined(IDictionary headers, st } else { - headers[key] = string.Join(",", value.Select(QuoteIfNeeded)); + headers[key] = string.Join(",", value.Select((s) => QuoteIfNeeded(s))); } } // Quote items that contain comas and are not already quoted. private static string QuoteIfNeeded(string value) { - if (string.IsNullOrWhiteSpace(value)) - { - // Ignore - } - else if (value.Contains(',')) - { - if (value[0] != '"' || value[value.Length - 1] != '"') - { - value = '"' + value + '"'; - } + if (!string.IsNullOrWhiteSpace(value) && + value.Contains(',') && + (value[0] != '"' || value[value.Length - 1] != '"')) + { + return $"\"{value}\""; } - return value; } private static string DeQuote(string value) { - if (string.IsNullOrWhiteSpace(value)) - { - // Ignore - } - else if (value.Length > 1 && value[0] == '"' && value[value.Length - 1] == '"') + if (!string.IsNullOrWhiteSpace(value) && + (value.Length > 1 && value[0] == '"' && value[value.Length - 1] == '"')) { value = value.Substring(1, value.Length - 2); } @@ -97,7 +88,7 @@ private static string DeQuote(string value) return value; } - public static void SetHeaderUnmodified(IDictionary headers, string key, StringValues? values) + public static void SetHeaderUnmodified(IHeaderDictionary headers, string key, StringValues? values) { if (headers == null) { @@ -118,7 +109,7 @@ public static void SetHeaderUnmodified(IDictionary headers } } - public static void AppendHeaderJoined(IDictionary headers, string key, params string[] values) + public static void AppendHeaderJoined(IHeaderDictionary headers, string key, params string[] values) { if (headers == null) { @@ -135,7 +126,7 @@ public static void AppendHeaderJoined(IDictionary headers, return; } - string existing = GetHeader(headers, key); + string existing = GetHeader(headers, key).ToString(); if (existing == null) { SetHeaderJoined(headers, key, values); @@ -146,7 +137,7 @@ public static void AppendHeaderJoined(IDictionary headers, } } - public static void AppendHeaderUnmodified(IDictionary headers, string key, StringValues values) + public static void AppendHeaderUnmodified(IHeaderDictionary headers, string key, StringValues values) { if (headers == null) { diff --git a/src/Microsoft.AspNet.Http.Extensions/Internal/StringSegment.cs b/src/Microsoft.AspNet.Http.Extensions/Internal/StringSegment.cs index 83c99842..3223168a 100644 --- a/src/Microsoft.AspNet.Http.Extensions/Internal/StringSegment.cs +++ b/src/Microsoft.AspNet.Http.Extensions/Internal/StringSegment.cs @@ -1,4 +1,7 @@ -using System; +// 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; namespace Microsoft.AspNet.Http.Internal { diff --git a/src/Microsoft.AspNet.Http.Extensions/RequestHeaders.cs b/src/Microsoft.AspNet.Http.Extensions/RequestHeaders.cs index 4b31e306..74873c92 100644 --- a/src/Microsoft.AspNet.Http.Extensions/RequestHeaders.cs +++ b/src/Microsoft.AspNet.Http.Extensions/RequestHeaders.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; -using System.Linq; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Http.Headers @@ -170,7 +170,7 @@ public HostString Host { get { - return HostString.FromUriComponent(Headers[HeaderNames.Host]); + return HostString.FromUriComponent(Headers[HeaderNames.Host].ToString()); } set { @@ -319,7 +319,23 @@ public void AppendList(string name, IList values) throw new ArgumentNullException(nameof(values)); } - Headers.Append(name, values.Select(value => value.ToString()).ToArray()); + switch (values.Count) + { + case 0: + Headers.Append(name, StringValues.Empty); + break; + case 1: + Headers.Append(name, new StringValues(values[0].ToString())); + break; + default: + var newValues = new string[values.Count]; + for (var i = 0; i < values.Count; i++) + { + newValues[i] = values[i].ToString(); + } + Headers.Append(name, new StringValues(newValues)); + break; + } } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http.Extensions/ResponseHeaders.cs b/src/Microsoft.AspNet.Http.Extensions/ResponseHeaders.cs index e7cb97ad..d37bdcd3 100644 --- a/src/Microsoft.AspNet.Http.Extensions/ResponseHeaders.cs +++ b/src/Microsoft.AspNet.Http.Extensions/ResponseHeaders.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Generic; -using System.Linq; using Microsoft.AspNet.Http.Extensions; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Http.Headers @@ -135,7 +135,7 @@ public Uri Location get { Uri uri; - if (Uri.TryCreate(Headers[HeaderNames.Location], UriKind.RelativeOrAbsolute, out uri)) + if (Uri.TryCreate(Headers[HeaderNames.Location].ToString(), UriKind.RelativeOrAbsolute, out uri)) { return uri; } @@ -216,7 +216,23 @@ public void AppendList(string name, IList values) throw new ArgumentNullException(nameof(values)); } - Headers.Append(name, values.Select(value => value.ToString()).ToArray()); + switch (values.Count) + { + case 0: + Headers.Append(name, StringValues.Empty); + break; + case 1: + Headers.Append(name, new StringValues(values[0].ToString())); + break; + default: + var newValues = new string[values.Count]; + for (var i = 0; i < values.Count; i++) + { + newValues[i] = values[i].ToString(); + } + Headers.Append(name, new StringValues(newValues)); + break; + } } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http.Extensions/UriHelper.cs b/src/Microsoft.AspNet.Http.Extensions/UriHelper.cs index 84143af2..e0d07b6a 100644 --- a/src/Microsoft.AspNet.Http.Extensions/UriHelper.cs +++ b/src/Microsoft.AspNet.Http.Extensions/UriHelper.cs @@ -25,7 +25,7 @@ public static string Encode( FragmentString fragment = new FragmentString()) { string combinePath = (pathBase.HasValue || path.HasValue) ? (pathBase + path).ToString() : "/"; - return combinePath + query + fragment; + return $"{combinePath}{query.ToString()}{fragment.ToString()}"; } /// @@ -48,7 +48,7 @@ public static string Encode( FragmentString fragment = new FragmentString()) { string combinePath = (pathBase.HasValue || path.HasValue) ? (pathBase + path).ToString() : "/"; - return scheme + "://" + host + combinePath + query + fragment; + return $"{scheme}://{host.ToString()}{combinePath}{query.ToString()}{fragment.ToString()}"; } /// diff --git a/src/Microsoft.AspNet.Http.Features/IFormCollection.cs b/src/Microsoft.AspNet.Http.Features/IFormCollection.cs new file mode 100644 index 00000000..f0e98fc9 --- /dev/null +++ b/src/Microsoft.AspNet.Http.Features/IFormCollection.cs @@ -0,0 +1,28 @@ +// 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 the parsed form values sent with the HttpRequest. + /// + public interface IFormCollection : IDictionary + { + /// + /// IFormCollection 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; } + + /// + /// The file collection sent with the request. + /// + /// + /// The files included with the request. + IFormFileCollection Files { get; } + } +} diff --git a/src/Microsoft.AspNet.Http.Abstractions/IFormFile.cs b/src/Microsoft.AspNet.Http.Features/IFormFile.cs similarity index 83% rename from src/Microsoft.AspNet.Http.Abstractions/IFormFile.cs rename to src/Microsoft.AspNet.Http.Features/IFormFile.cs index e85ee75f..6f8fdaa2 100644 --- a/src/Microsoft.AspNet.Http.Abstractions/IFormFile.cs +++ b/src/Microsoft.AspNet.Http.Features/IFormFile.cs @@ -5,6 +5,9 @@ namespace Microsoft.AspNet.Http { + /// + /// Represents a file sent with the HttpRequest. + /// public interface IFormFile { string ContentType { get; } diff --git a/src/Microsoft.AspNet.Http.Abstractions/IFormFileCollection.cs b/src/Microsoft.AspNet.Http.Features/IFormFileCollection.cs similarity index 81% rename from src/Microsoft.AspNet.Http.Abstractions/IFormFileCollection.cs rename to src/Microsoft.AspNet.Http.Features/IFormFileCollection.cs index 4950758b..229b7bbb 100644 --- a/src/Microsoft.AspNet.Http.Abstractions/IFormFileCollection.cs +++ b/src/Microsoft.AspNet.Http.Features/IFormFileCollection.cs @@ -5,6 +5,9 @@ namespace Microsoft.AspNet.Http { + /// + /// Represents the collection of files sent with the HttpRequest. + /// public interface IFormFileCollection : IReadOnlyList { IFormFile this[string name] { get; } diff --git a/src/Microsoft.AspNet.Http.Features/IHeaderDictionary.cs b/src/Microsoft.AspNet.Http.Features/IHeaderDictionary.cs index 0c03c29d..303ff36b 100644 --- a/src/Microsoft.AspNet.Http.Features/IHeaderDictionary.cs +++ b/src/Microsoft.AspNet.Http.Features/IHeaderDictionary.cs @@ -7,7 +7,7 @@ namespace Microsoft.AspNet.Http { /// - /// Represents request and response headers + /// Represents HttpRequest and HttpResponse headers /// public interface IHeaderDictionary : IDictionary { diff --git a/src/Microsoft.AspNet.Http.Features/IQueryCollection.cs b/src/Microsoft.AspNet.Http.Features/IQueryCollection.cs new file mode 100644 index 00000000..b4614578 --- /dev/null +++ b/src/Microsoft.AspNet.Http.Features/IQueryCollection.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 the HttpRequest query string collection + /// + public interface IQueryCollection : IDictionary + { + /// + /// IQueryCollection 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/IRequestCookies.cs b/src/Microsoft.AspNet.Http.Features/IRequestCookies.cs new file mode 100644 index 00000000..bb876ebd --- /dev/null +++ b/src/Microsoft.AspNet.Http.Features/IRequestCookies.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 the HttpRequest cookie collection + /// + public interface IRequestCookies : IDictionary + { + /// + /// IRequestCookies 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/DefaultHttpRequest.cs b/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs index 99f39264..1aa0fec1 100644 --- a/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs +++ b/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs @@ -140,11 +140,11 @@ public override bool IsHttps public override HostString Host { - get { return HostString.FromUriComponent(Headers["Host"]); } + get { return HostString.FromUriComponent(Headers["Host"].ToString()); } set { Headers["Host"] = value.ToUriComponent(); } } - public override IReadableStringCollection Query + public override IQueryCollection Query { get { return QueryFeature.Query; } set { QueryFeature.Query = value; } @@ -161,7 +161,7 @@ public override IHeaderDictionary Headers get { return HttpRequestFeature.Headers; } } - public override IReadableStringCollection Cookies + public override IRequestCookies Cookies { get { return RequestCookiesFeature.Cookies; } set { RequestCookiesFeature.Cookies = value; } diff --git a/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs b/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs index f239261b..3eade210 100644 --- a/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs +++ b/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs @@ -87,7 +87,7 @@ public override string ContentType { get { - return Headers[HeaderNames.ContentType]; + return Headers[HeaderNames.ContentType].ToString(); } set { diff --git a/src/Microsoft.AspNet.Http/DefaultWebSocketManager.cs b/src/Microsoft.AspNet.Http/DefaultWebSocketManager.cs index 454b0fa6..e7394a53 100644 --- a/src/Microsoft.AspNet.Http/DefaultWebSocketManager.cs +++ b/src/Microsoft.AspNet.Http/DefaultWebSocketManager.cs @@ -55,7 +55,7 @@ public override IList WebSocketRequestedProtocols { get { - return ParsingHelpers.GetHeaderSplit(HttpRequestFeature.Headers, HeaderNames.WebSocketSubProtocols); + return ParsingHelpers.GetHeaderSplit(HttpRequestFeature.Headers, HeaderNames.WebSocketSubProtocols).ToArray(); } } diff --git a/src/Microsoft.AspNet.Http/Features/FeatureHelpers.cs b/src/Microsoft.AspNet.Http/Features/FeatureHelpers.cs index fc59b00e..ca6d914a 100644 --- a/src/Microsoft.AspNet.Http/Features/FeatureHelpers.cs +++ b/src/Microsoft.AspNet.Http/Features/FeatureHelpers.cs @@ -5,12 +5,13 @@ namespace Microsoft.AspNet.Http.Features { - internal sealed class FeatureHelpers + internal static class FeatureHelpers { public static T GetAndCache( IFeatureCache cache, IFeatureCollection features, ref T cachedObject) + where T : class { cache.CheckFeaturesRevision(); @@ -26,6 +27,7 @@ public static T GetAndCache( public static T GetOrCreate( IFeatureCollection features, Func factory) + where T : class { T obj = features.Get(); if (obj == null) @@ -43,6 +45,7 @@ public static T GetOrCreateAndCache( IFeatureCollection features, Func factory, ref T cachedObject) + where T : class { cache.CheckFeaturesRevision(); @@ -65,6 +68,7 @@ public static T GetOrCreateAndCache( IFeatureCollection features, Func factory, ref T cachedObject) + where T : class { cache.CheckFeaturesRevision(); @@ -88,6 +92,7 @@ public static T GetOrCreateAndCache( HttpRequest request, Func factory, ref T cachedObject) + where T : class { cache.CheckFeaturesRevision(); diff --git a/src/Microsoft.AspNet.Http/Features/FormFeature.cs b/src/Microsoft.AspNet.Http/Features/FormFeature.cs index b5e71b8c..db78289d 100644 --- a/src/Microsoft.AspNet.Http/Features/FormFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/FormFeature.cs @@ -2,14 +2,12 @@ // 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.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.WebUtilities; -using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Http.Features.Internal @@ -17,6 +15,8 @@ namespace Microsoft.AspNet.Http.Features.Internal public class FormFeature : IFormFeature { private readonly HttpRequest _request; + private Task _parsedFormTask; + private IFormCollection _form; public FormFeature(IFormCollection form) { @@ -63,7 +63,15 @@ public bool HasFormContentType } } - public IFormCollection Form { get; set; } + public IFormCollection Form + { + get { return _form; } + set + { + _parsedFormTask = null; + _form = value; + } + } public IFormCollection ReadForm() { @@ -74,41 +82,56 @@ public IFormCollection ReadForm() if (!HasFormContentType) { - throw new InvalidOperationException("Incorrect Content-Type: " + _request.ContentType); + throw new InvalidOperationException($"Incorrect Content-Type: {_request.ContentType}"); } + // TODO: Avoid Sync-over-Async http://blogs.msdn.com/b/pfxteam/archive/2012/04/13/10293638.aspx // TODO: How do we prevent thread exhaustion? - return ReadFormAsync(CancellationToken.None).GetAwaiter().GetResult(); + return ReadFormAsync().GetAwaiter().GetResult(); } - public async Task ReadFormAsync(CancellationToken cancellationToken) + public Task ReadFormAsync() => ReadFormAsync(CancellationToken.None); + + public Task ReadFormAsync(CancellationToken cancellationToken) { - if (Form != null) + // Avoid state machine and task allocation for repeated reads + if (_parsedFormTask == null) { - return Form; + if (Form != null) + { + _parsedFormTask = Task.FromResult(Form); + } + else + { + _parsedFormTask = InnerReadFormAsync(cancellationToken); + } } + return _parsedFormTask; + } + private async Task InnerReadFormAsync(CancellationToken cancellationToken) + { if (!HasFormContentType) { - throw new InvalidOperationException("Incorrect Content-Type: " + _request.ContentType); + throw new InvalidOperationException($"Incorrect Content-Type: {_request.ContentType}"); } cancellationToken.ThrowIfCancellationRequested(); _request.EnableRewind(); - IDictionary formFields = null; - var files = new FormFileCollection(); + FormCollection formFields = null; + FormFileCollection files = null; // Some of these code paths use StreamReader which does not support cancellation tokens. - using (cancellationToken.Register(_request.HttpContext.Abort)) + using (cancellationToken.Register((state) => ((HttpContext)state).Abort(), _request.HttpContext)) { var contentType = ContentType; // Check the content-type if (HasApplicationFormContentType(contentType)) { var encoding = FilterEncoding(contentType.Encoding); - formFields = await FormReader.ReadFormAsync(_request.Body, encoding, cancellationToken); + formFields = new FormCollection(await FormReader.ReadNullableFormAsync(_request.Body, encoding, cancellationToken)); } else if (HasMultipartFormContentType(contentType)) { @@ -119,9 +142,8 @@ public async Task ReadFormAsync(CancellationToken cancellationT var section = await multipartReader.ReadNextSectionAsync(cancellationToken); while (section != null) { - var headers = new HeaderDictionary(section.Headers); ContentDispositionHeaderValue contentDisposition; - ContentDispositionHeaderValue.TryParse(headers[HeaderNames.ContentDisposition], out contentDisposition); + ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition); if (HasFileContentDisposition(contentDisposition)) { // Find the end @@ -129,8 +151,12 @@ public async Task ReadFormAsync(CancellationToken cancellationT var file = new FormFile(_request.Body, section.BaseStreamOffset.Value, section.Body.Length) { - Headers = headers, + Headers = new HeaderDictionary(section.Headers), }; + if (files == null) + { + files = new FormFileCollection(); + } files.Add(file); } else if (HasFormDataContentDisposition(contentDisposition)) @@ -141,7 +167,7 @@ public async Task ReadFormAsync(CancellationToken cancellationT var key = HeaderUtilities.RemoveQuotes(contentDisposition.Name); MediaTypeHeaderValue mediaType; - MediaTypeHeaderValue.TryParse(headers[HeaderNames.ContentType], out mediaType); + MediaTypeHeaderValue.TryParse(section.ContentType, out mediaType); var encoding = FilterEncoding(mediaType?.Encoding); using (var reader = new StreamReader(section.Body, encoding, detectEncodingFromByteOrderMarks: true, bufferSize: 1024, leaveOpen: true)) { @@ -151,20 +177,24 @@ public async Task ReadFormAsync(CancellationToken cancellationT } else { - System.Diagnostics.Debug.Assert(false, "Unrecognized content-disposition for this section: " + headers[HeaderNames.ContentDisposition]); + System.Diagnostics.Debug.Assert(false, $"Unrecognized content-disposition for this section: {section.ContentDisposition}"); } section = await multipartReader.ReadNextSectionAsync(cancellationToken); } - formFields = formAccumulator.GetResults(); + if (formAccumulator.HasValues) + { + formFields = new FormCollection(formAccumulator.GetResults(), files); + } } } // Rewind so later readers don't have to. _request.Body.Seek(0, SeekOrigin.Begin); - Form = new FormCollection(formFields, files); + Form = formFields ?? new FormCollection(null, files); + return Form; } diff --git a/src/Microsoft.AspNet.Http/Features/FormFile.cs b/src/Microsoft.AspNet.Http/Features/FormFile.cs index 557dc9d5..d803cd21 100644 --- a/src/Microsoft.AspNet.Http/Features/FormFile.cs +++ b/src/Microsoft.AspNet.Http/Features/FormFile.cs @@ -21,13 +21,13 @@ public FormFile(Stream baseStream, long baseStreamOffset, long length) public string ContentDisposition { - get { return Headers["Content-Disposition"]; } + get { return Headers["Content-Disposition"].ToString(); } set { Headers["Content-Disposition"] = value; } } public string ContentType { - get { return Headers["Content-Type"]; } + get { return Headers["Content-Type"].ToString(); } set { Headers["Content-Type"] = value; } } diff --git a/src/Microsoft.AspNet.Http/Features/HttpRequestLifetimeFeature.cs b/src/Microsoft.AspNet.Http/Features/HttpRequestLifetimeFeature.cs index 1b773b93..8a8e00f2 100644 --- a/src/Microsoft.AspNet.Http/Features/HttpRequestLifetimeFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/HttpRequestLifetimeFeature.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Threading; -using Microsoft.AspNet.Http.Features; namespace Microsoft.AspNet.Http.Features.Internal { diff --git a/src/Microsoft.AspNet.Http/Features/IQueryFeature.cs b/src/Microsoft.AspNet.Http/Features/IQueryFeature.cs index a814e385..269e31ba 100644 --- a/src/Microsoft.AspNet.Http/Features/IQueryFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/IQueryFeature.cs @@ -5,6 +5,6 @@ namespace Microsoft.AspNet.Http.Features.Internal { public interface IQueryFeature { - IReadableStringCollection Query { get; set; } + IQueryCollection Query { get; set; } } } diff --git a/src/Microsoft.AspNet.Http/Features/IRequestCookiesFeature.cs b/src/Microsoft.AspNet.Http/Features/IRequestCookiesFeature.cs index 73f23d03..d9cbc162 100644 --- a/src/Microsoft.AspNet.Http/Features/IRequestCookiesFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/IRequestCookiesFeature.cs @@ -1,10 +1,13 @@ // 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.Features.Internal { public interface IRequestCookiesFeature { - IReadableStringCollection Cookies { get; set; } + IRequestCookies Cookies { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http/Features/QueryFeature.cs b/src/Microsoft.AspNet.Http/Features/QueryFeature.cs index b2f59e00..be476842 100644 --- a/src/Microsoft.AspNet.Http/Features/QueryFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/QueryFeature.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 Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.WebUtilities; -using Microsoft.Extensions.Primitives; namespace Microsoft.AspNet.Http.Features.Internal { @@ -17,18 +15,9 @@ public class QueryFeature : IQueryFeature, IFeatureCache private IHttpRequestFeature _request; private string _original; - private IReadableStringCollection _parsedValues; + private IQueryCollection _parsedValues; - public QueryFeature(IDictionary query) - : this(new ReadableStringCollection(query)) - { - if (query == null) - { - throw new ArgumentNullException(nameof(query)); - } - } - - public QueryFeature(IReadableStringCollection query) + public QueryFeature(IQueryCollection query) { if (query == null) { @@ -62,20 +51,34 @@ private IHttpRequestFeature HttpRequestFeature get { return FeatureHelpers.GetAndCache(this, _features, ref _request); } } - public IReadableStringCollection Query + public IQueryCollection Query { get { if (_features == null) { - return _parsedValues ?? ReadableStringCollection.Empty; + if (_parsedValues == null) + { + _parsedValues = new QueryCollection(); + } + return _parsedValues; } var current = HttpRequestFeature.QueryString; if (_parsedValues == null || !string.Equals(_original, current, StringComparison.Ordinal)) { _original = current; - _parsedValues = new ReadableStringCollection(QueryHelpers.ParseQuery(current)); + + var kva = QueryHelpers.ParseQuery(current); + + if (!kva.HasValues) + { + _parsedValues = new QueryCollection(); + } + else + { + _parsedValues = new QueryCollection(kva.GetResults()); + } } return _parsedValues; } diff --git a/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs b/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs index 9eba2dc9..a3f47ad8 100644 --- a/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using Microsoft.AspNet.Http.Internal; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; @@ -18,14 +17,9 @@ public class RequestCookiesFeature : IRequestCookiesFeature, IFeatureCache private IHttpRequestFeature _request; private StringValues _original; - private IReadableStringCollection _parsedValues; - - public RequestCookiesFeature(IDictionary cookies) - : this(new ReadableStringCollection(cookies)) - { - } - - public RequestCookiesFeature(IReadableStringCollection cookies) + private IRequestCookies _parsedValues; + + public RequestCookiesFeature(IRequestCookies cookies) { if (cookies == null) { @@ -59,13 +53,17 @@ private IHttpRequestFeature HttpRequestFeature get { return FeatureHelpers.GetAndCache(this, _features, ref _request); } } - public IReadableStringCollection Cookies + public IRequestCookies Cookies { get { if (_features == null) { - return _parsedValues ?? ReadableStringCollection.Empty; + if (_parsedValues == null) + { + _parsedValues = new RequestCookies(); + } + return _parsedValues; } var headers = HttpRequestFeature.Headers; @@ -75,16 +73,16 @@ public IReadableStringCollection Cookies current = StringValues.Empty; } - if (_parsedValues == null || !Enumerable.SequenceEqual(_original, current, StringComparer.Ordinal)) + if (_parsedValues == null || !StringValues.Equals(_original, current)) { _original = current; - var collectionParser = _parsedValues as RequestCookiesCollection; + var collectionParser = _parsedValues as RequestCookies; if (collectionParser == null) { - collectionParser = new RequestCookiesCollection(); + collectionParser = new RequestCookies(); _parsedValues = collectionParser; } - collectionParser.Reparse(current); + collectionParser.Reparse(current.ToArray()); } return _parsedValues; diff --git a/src/Microsoft.AspNet.Http/Features/ResponseCookiesFeature.cs b/src/Microsoft.AspNet.Http/Features/ResponseCookiesFeature.cs index d56ba114..dd7607a7 100644 --- a/src/Microsoft.AspNet.Http/Features/ResponseCookiesFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/ResponseCookiesFeature.cs @@ -39,7 +39,7 @@ public IResponseCookies Cookies if (_cookiesCollection == null) { var headers = HttpResponseFeature.Headers; - _cookiesCollection = new ResponseCookies(new HeaderDictionary(headers)); + _cookiesCollection = new ResponseCookies(headers); } return _cookiesCollection; } diff --git a/src/Microsoft.AspNet.Http/FormCollection.cs b/src/Microsoft.AspNet.Http/FormCollection.cs index a0e74285..b561f8f7 100644 --- a/src/Microsoft.AspNet.Http/FormCollection.cs +++ b/src/Microsoft.AspNet.Http/FormCollection.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections; using System.Collections.Generic; using Microsoft.Extensions.Primitives; @@ -10,29 +11,410 @@ namespace Microsoft.AspNet.Http.Internal /// /// Contains the parsed form values. /// - public class FormCollection : ReadableStringCollection, IFormCollection + public sealed class FormCollection : IFormCollection { - public FormCollection(IDictionary store) - : this(store, new FormFileCollection()) +#if !DNXCORE50 + private static readonly string[] EmptyKeys = new string[0]; + private static readonly StringValues[] EmptyValues = new StringValues[0]; +#endif + private static readonly Enumerator EmptyEnumerator = new Enumerator(); + // Pre-box + private static readonly IEnumerator> EmptyIEnumeratorType = EmptyEnumerator; + private static readonly IEnumerator EmptyIEnumerator = EmptyEnumerator; + + private static IFormFileCollection EmptyFiles = new FormFileCollection(); + + private IFormFileCollection _files; + private FormCollection() + { + // For static Empty + } + + public FormCollection(Dictionary fields, IFormFileCollection files = null) { + // can be null + Store = fields; + _files = files; } - public FormCollection(IDictionary store, IFormFileCollection files) - : base(store) + public IFormFileCollection Files { - if (store == null) + get { - throw new ArgumentNullException(nameof(store)); + return _files ?? EmptyFiles; } + private set { _files = value; } + } + + private Dictionary Store { get; set; } - if (files == null) + /// + /// Get or sets the associated value from the collection as a single string. + /// + /// The header name. + /// the associated value from the collection as a StringValues or StringValues.Empty if the key is not present. + public StringValues this[string key] + { + get { - throw new ArgumentNullException(nameof(files)); + if (Store == null) + { + return StringValues.Empty; + } + + StringValues value; + if (TryGetValue(key, out value)) + { + return value; + } + return StringValues.Empty; } - Files = files; + set + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (StringValues.IsNullOrEmpty(value)) + { + if (Store == null) + { + return; + } + + Store.Remove(key); + } + else + { + if (Store == null) + { + Store = new Dictionary(1, StringComparer.OrdinalIgnoreCase); + } + + Store[key] = value; + } + } + } + + /// + /// Throws KeyNotFoundException if the key is not present. + /// + /// The header name. + /// + StringValues IDictionary.this[string key] + { + get { return Store[key]; } + set { this[key] = value; } } - public IFormFileCollection Files { get; } + /// + /// Gets the number of elements contained in the ;. + /// + /// The number of elements contained in the . + public int Count + { + get + { + if (Store == null) + { + return 0; + } + return Store.Count; + } + } + + /// + /// 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 ICollection Keys + { + get + { + if (Store == null) + { +#if DNXCORE50 + return Array.Empty(); +#else + return EmptyKeys; +#endif + } + return Store.Keys; + } + } + + public ICollection Values + { + get + { + if (Store == null) + { +#if DNXCORE50 + return Array.Empty(); +#else + return EmptyValues; +#endif + } + return Store.Values; + } + } + + /// + /// Adds a new list of items to the collection. + /// + /// The item to add. + public void Add(KeyValuePair item) + { + if (item.Key == null) + { + throw new ArgumentNullException(nameof(item.Key)); + } + if (Store == null) + { + Store = new Dictionary(1, StringComparer.OrdinalIgnoreCase); + } + Store.Add(item.Key, item.Value); + } + + /// + /// Adds the given header and values to the collection. + /// + /// The header name. + /// The header values. + public void Add(string key, StringValues value) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (Store == null) + { + Store = new Dictionary(1); + } + Store.Add(key, value); + } + + /// + /// Clears the entire list of objects. + /// + public void Clear() + { + if (Store == null) + { + return; + } + Store.Clear(); + } + + /// + /// Returns a value indicating whether the specified object occurs within this collection. + /// + /// The item. + /// true if the specified object occurs within this collection; otherwise, false. + public bool Contains(KeyValuePair item) + { + StringValues value; + if (Store == null || + !Store.TryGetValue(item.Key, out value) || + !StringValues.Equals(value, item.Value)) + { + return false; + } + return true; + } + + /// + /// Determines whether the contains a specific key. + /// + /// The key. + /// true if the contains a specific key; otherwise, false. + public bool ContainsKey(string key) + { + if (Store == null) + { + return false; + } + return Store.ContainsKey(key); + } + + /// + /// Copies the elements to a one-dimensional Array instance at the specified index. + /// + /// The one-dimensional Array that is the destination of the specified objects copied from the . + /// The zero-based index in at which copying begins. + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + if (Store == null) + { + return; + } + + foreach (var item in Store) + { + array[arrayIndex] = item; + arrayIndex++; + } + } + + /// + /// Removes the given item from the the collection. + /// + /// The item. + /// true if the specified object was removed from the collection; otherwise, false. + public bool Remove(KeyValuePair item) + { + if (Store == null) + { + return false; + } + + StringValues value; + + if (Store.TryGetValue(item.Key, out value) && StringValues.Equals(item.Value, value)) + { + return Store.Remove(item.Key); + } + return false; + } + + /// + /// Removes the given header from the collection. + /// + /// The header name. + /// true if the specified object was removed from the collection; otherwise, false. + public bool Remove(string key) + { + if (Store == null) + { + return false; + } + return Store.Remove(key); + } + + /// + /// Retrieves a value from the dictionary. + /// + /// The header name. + /// The value. + /// true if the contains the key; otherwise, false. + public bool TryGetValue(string key, out StringValues value) + { + if (Store == null) + { + value = default(StringValues); + return false; + } + return Store.TryGetValue(key, out value); + } + + /// + /// Returns an struct enumerator that iterates through a collection without boxing and is also used via the interface. + /// + /// An object that can be used to iterate through the collection. + public Enumerator GetEnumerator() + { + if (Store == null || Store.Count == 0) + { + // Non-boxed Enumerator + return EmptyEnumerator; + } + // Non-boxed Enumerator + return new Enumerator(Store.GetEnumerator()); + } + + /// + /// Returns an enumerator that iterates through a collection, boxes in non-empty path. + /// + /// An object that can be used to iterate through the collection. + IEnumerator> IEnumerable>.GetEnumerator() + { + if (Store == null || Store.Count == 0) + { + // Non-boxed Enumerator + return EmptyIEnumeratorType; + } + // Boxed Enumerator + return Store.GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through a collection, boxes in non-empty path. + /// + /// An object that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() + { + if (Store == null || Store.Count == 0) + { + // Non-boxed Enumerator + return EmptyIEnumerator; + } + // Boxed Enumerator + return Store.GetEnumerator(); + } + + public struct Enumerator : IEnumerator> + { + // Do NOT make this readonly, or MoveNext will not work + private Dictionary.Enumerator _dictionaryEnumerator; + private bool _notEmpty; + + internal Enumerator(Dictionary.Enumerator dictionaryEnumerator) + { + _dictionaryEnumerator = dictionaryEnumerator; + _notEmpty = true; + } + + public bool MoveNext() + { + if (_notEmpty) + { + return _dictionaryEnumerator.MoveNext(); + } + return false; + } + + public KeyValuePair Current + { + get + { + if (_notEmpty) + { + return _dictionaryEnumerator.Current; + } + return default(KeyValuePair); + } + } + + public void Dispose() + { + } + + object IEnumerator.Current + { + get + { + return Current; + } + } + + void IEnumerator.Reset() + { + if (_notEmpty) + { + ((IEnumerator)_dictionaryEnumerator).Reset(); + } + } + } } } diff --git a/src/Microsoft.AspNet.Http/HeaderDictionary.cs b/src/Microsoft.AspNet.Http/HeaderDictionary.cs index 2955cdde..184c194d 100644 --- a/src/Microsoft.AspNet.Http/HeaderDictionary.cs +++ b/src/Microsoft.AspNet.Http/HeaderDictionary.cs @@ -9,123 +9,273 @@ namespace Microsoft.AspNet.Http.Internal { /// - /// Represents a wrapper for owin.RequestHeaders and owin.ResponseHeaders. + /// Represents a wrapper for RequestHeaders and ResponseHeaders. /// - public class HeaderDictionary : IHeaderDictionary + public sealed class HeaderDictionary : IHeaderDictionary { - public HeaderDictionary() : this(new Dictionary(StringComparer.OrdinalIgnoreCase)) +#if !DNXCORE50 + private static readonly string[] EmptyKeys = new string[0]; + private static readonly StringValues[] EmptyValues = new StringValues[0]; +#endif + private static readonly Enumerator EmptyEnumerator = new Enumerator(); + // Pre-box + private static readonly IEnumerator> EmptyIEnumeratorType = EmptyEnumerator; + private static readonly IEnumerator EmptyIEnumerator = EmptyEnumerator; + + public HeaderDictionary() + { + } + + public HeaderDictionary(Dictionary store) + { + Store = store; + } + + public HeaderDictionary(int capacity) { + Store = new Dictionary(capacity, StringComparer.OrdinalIgnoreCase); } + private Dictionary Store { get; set; } + /// - /// Initializes a new instance of the class. + /// Get or sets the associated value from the collection as a single string. /// - /// The underlying data store. - public HeaderDictionary(IDictionary store) + /// The header name. + /// the associated value from the collection as a StringValues or StringValues.Empty if the key is not present. + public StringValues this[string key] { - if (store == null) + get { - throw new ArgumentNullException(nameof(store)); + if (Store == null) + { + return StringValues.Empty; + } + + StringValues value; + if (TryGetValue(key, out value)) + { + return value; + } + return StringValues.Empty; } - Store = store; - } + set + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (StringValues.IsNullOrEmpty(value)) + { + if (Store == null) + { + return; + } - private IDictionary Store { get; set; } + Store.Remove(key); + } + else + { + if (Store == null) + { + Store = new Dictionary(1, StringComparer.OrdinalIgnoreCase); + } + + Store[key] = value; + } + } + } /// - /// Gets an that contains the keys in the ;. + /// Throws KeyNotFoundException if the key is not present. /// - /// An that contains the keys in the . - public ICollection Keys + /// The header name. + /// + StringValues IDictionary.this[string key] { - get { return Store.Keys; } + get { return Store[key]; } + set { this[key] = value; } } /// - /// + /// Gets the number of elements contained in the ;. /// - public ICollection Values + /// The number of elements contained in the . + public int Count { - get { return Store.Values; } + get + { + if (Store == null) + { + return 0; + } + return Store.Count; + } } /// - /// Gets the number of elements contained in the ;. + /// Gets a value that indicates whether the is in read-only mode. /// - /// The number of elements contained in the . - public int Count + /// true if the is in read-only mode; otherwise, false. + public bool IsReadOnly + { + get + { + return false; + } + } + + public ICollection Keys + { + get + { + if (Store == null) + { +#if DNXCORE50 + return Array.Empty(); +#else + return EmptyKeys; +#endif + } + return Store.Keys; + } + } + + public ICollection Values { - get { return Store.Count; } + get + { + if (Store == null) + { +#if DNXCORE50 + return Array.Empty(); +#else + return EmptyValues; +#endif + } + return Store.Values; + } } /// - /// Gets a value that indicates whether the is in read-only mode. + /// Adds a new list of items to the collection. /// - /// true if the is in read-only mode; otherwise, false. - public bool IsReadOnly + /// The item to add. + public void Add(KeyValuePair item) { - get { return Store.IsReadOnly; } + if (item.Key == null) + { + throw new ArgumentNullException(nameof(item.Key)); + } + if (Store == null) + { + Store = new Dictionary(1, StringComparer.OrdinalIgnoreCase); + } + Store.Add(item.Key, item.Value); } /// - /// Get or sets the associated value from the collection as a single string. + /// Adds the given header and values to the collection. /// /// The header name. - /// the associated value from the collection as a StringValues or StringValues.Empty if the key is not present. - public StringValues this[string key] + /// The header values. + public void Add(string key, StringValues value) { - get { return ParsingHelpers.GetHeader(Store, key); } - set { ParsingHelpers.SetHeader(Store, key, value); } + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (Store == null) + { + Store = new Dictionary(1); + } + Store.Add(key, value); } /// - /// Throws KeyNotFoundException if the key is not present. + /// Clears the entire list of objects. /// - /// The header name. - /// - StringValues IDictionary.this[string key] + public void Clear() { - get { return Store[key]; } - set { Store[key] = value; } + if (Store == null) + { + return; + } + Store.Clear(); } /// - /// Returns an enumerator that iterates through a collection. + /// Returns a value indicating whether the specified object occurs within this collection. /// - /// An object that can be used to iterate through the collection. - IEnumerator> IEnumerable>.GetEnumerator() + /// The item. + /// true if the specified object occurs within this collection; otherwise, false. + public bool Contains(KeyValuePair item) { - return Store.GetEnumerator(); + StringValues value; + if (Store == null || + !Store.TryGetValue(item.Key, out value) || + !StringValues.Equals(value, item.Value)) + { + return false; + } + return true; } /// - /// Returns an enumerator that iterates through a collection. + /// Determines whether the contains a specific key. /// - /// An object that can be used to iterate through the collection. - IEnumerator IEnumerable.GetEnumerator() + /// The key. + /// true if the contains a specific key; otherwise, false. + public bool ContainsKey(string key) { - return Store.GetEnumerator(); + if (Store == null) + { + return false; + } + return Store.ContainsKey(key); } /// - /// Adds the given header and values to the collection. + /// Copies the elements to a one-dimensional Array instance at the specified index. /// - /// The header name. - /// The header values. - public void Add(string key, StringValues value) + /// The one-dimensional Array that is the destination of the specified objects copied from the . + /// The zero-based index in at which copying begins. + public void CopyTo(KeyValuePair[] array, int arrayIndex) { - Store.Add(key, value); + if (Store == null) + { + return; + } + + foreach (var item in Store) + { + array[arrayIndex] = item; + arrayIndex++; + } } /// - /// Determines whether the contains a specific key. + /// Removes the given item from the the collection. /// - /// The key. - /// true if the contains a specific key; otherwise, false. - public bool ContainsKey(string key) + /// The item. + /// true if the specified object was removed from the collection; otherwise, false. + public bool Remove(KeyValuePair item) { - return Store.ContainsKey(key); + if (Store == null) + { + return false; + } + + StringValues value; + + if (Store.TryGetValue(item.Key, out value) && StringValues.Equals(item.Value, value)) + { + return Store.Remove(item.Key); + } + return false; } /// @@ -135,6 +285,10 @@ public bool ContainsKey(string key) /// true if the specified object was removed from the collection; otherwise, false. public bool Remove(string key) { + if (Store == null) + { + return false; + } return Store.Remove(key); } @@ -143,57 +297,111 @@ public bool Remove(string key) /// /// The header name. /// The value. - /// true if the contains the key; otherwise, false. + /// true if the contains the key; otherwise, false. public bool TryGetValue(string key, out StringValues value) { + if (Store == null) + { + value = default(StringValues); + return false; + } return Store.TryGetValue(key, out value); } /// - /// Adds a new list of items to the collection. + /// Returns an enumerator that iterates through a collection. /// - /// The item to add. - public void Add(KeyValuePair item) + /// An object that can be used to iterate through the collection. + public Enumerator GetEnumerator() { - Store.Add(item); + if (Store == null || Store.Count == 0) + { + // Non-boxed Enumerator + return EmptyEnumerator; + } + return new Enumerator(Store.GetEnumerator()); } /// - /// Clears the entire list of objects. + /// Returns an enumerator that iterates through a collection. /// - public void Clear() + /// An object that can be used to iterate through the collection. + IEnumerator> IEnumerable>.GetEnumerator() { - Store.Clear(); + if (Store == null || Store.Count == 0) + { + // Non-boxed Enumerator + return EmptyIEnumeratorType; + } + return Store.GetEnumerator(); } /// - /// Returns a value indicating whether the specified object occurs within this collection. + /// Returns an enumerator that iterates through a collection. /// - /// The item. - /// true if the specified object occurs within this collection; otherwise, false. - public bool Contains(KeyValuePair item) + /// An object that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() { - return Store.Contains(item); + if (Store == null || Store.Count == 0) + { + // Non-boxed Enumerator + return EmptyIEnumerator; + } + return Store.GetEnumerator(); } - /// - /// Copies the elements to a one-dimensional Array instance at the specified index. - /// - /// The one-dimensional Array that is the destination of the specified objects copied from the . - /// The zero-based index in at which copying begins. - public void CopyTo(KeyValuePair[] array, int arrayIndex) + public struct Enumerator : IEnumerator> { - Store.CopyTo(array, arrayIndex); - } + // Do NOT make this readonly, or MoveNext will not work + private Dictionary.Enumerator _dictionaryEnumerator; + private bool _notEmpty; - /// - /// Removes the given item from the the collection. - /// - /// The item. - /// true if the specified object was removed from the collection; otherwise, false. - public bool Remove(KeyValuePair item) - { - return Store.Remove(item); + internal Enumerator(Dictionary.Enumerator dictionaryEnumerator) + { + _dictionaryEnumerator = dictionaryEnumerator; + _notEmpty = true; + } + + public bool MoveNext() + { + if (_notEmpty) + { + return _dictionaryEnumerator.MoveNext(); + } + return false; + } + + public KeyValuePair Current + { + get + { + if (_notEmpty) + { + return _dictionaryEnumerator.Current; + } + return default(KeyValuePair); + } + } + + public void Dispose() + { + } + + object IEnumerator.Current + { + get + { + return Current; + } + } + + void IEnumerator.Reset() + { + if (_notEmpty) + { + ((IEnumerator)_dictionaryEnumerator).Reset(); + } + } } } } diff --git a/src/Microsoft.AspNet.Http/ItemsDictionary.cs b/src/Microsoft.AspNet.Http/ItemsDictionary.cs index 90e9cd31..87a27b8b 100644 --- a/src/Microsoft.AspNet.Http/ItemsDictionary.cs +++ b/src/Microsoft.AspNet.Http/ItemsDictionary.cs @@ -97,7 +97,12 @@ bool ICollection>.IsReadOnly bool ICollection>.Remove(KeyValuePair item) { - return Items.Remove(item); + object value; + if (Items.TryGetValue(item.Key, out value) && Equals(item.Value, value)) + { + return Items.Remove(item.Key); + } + return false; } IEnumerator> IEnumerable>.GetEnumerator() diff --git a/src/Microsoft.AspNet.Http/ParsingHelpers.cs b/src/Microsoft.AspNet.Http/ParsingHelpers.cs index 9f6feb3d..8b575fd6 100644 --- a/src/Microsoft.AspNet.Http/ParsingHelpers.cs +++ b/src/Microsoft.AspNet.Http/ParsingHelpers.cs @@ -87,7 +87,7 @@ public HeaderSegmentCollection(StringValues headers) public bool Equals(HeaderSegmentCollection other) { - return Equals(_headers, other._headers); + return StringValues.Equals(_headers, other._headers); } public override bool Equals(object obj) @@ -363,13 +363,7 @@ public void Reset() internal static class ParsingHelpers { - public static StringValues GetHeader(IDictionary headers, string key) - { - StringValues value; - return headers.TryGetValue(key, out value) ? value : StringValues.Empty; - } - - public static StringValues GetHeaderSplit(IDictionary headers, string key) + public static StringValues GetHeaderSplit(IHeaderDictionary headers, string key) { var values = GetHeaderUnmodified(headers, key); return new StringValues(GetHeaderSplitImplementation(values).ToArray()); @@ -386,7 +380,7 @@ private static IEnumerable GetHeaderSplitImplementation(StringValues val } } - public static StringValues GetHeaderUnmodified(IDictionary headers, string key) + public static StringValues GetHeaderUnmodified(IHeaderDictionary headers, string key) { if (headers == null) { @@ -397,32 +391,6 @@ public static StringValues GetHeaderUnmodified(IDictionary return headers.TryGetValue(key, out values) ? values : StringValues.Empty; } - public static void SetHeader(IDictionary headers, string key, StringValues value) - { - if (headers == null) - { - throw new ArgumentNullException(nameof(headers)); - } - - if (key == null) - { - throw new ArgumentNullException(nameof(key)); - } - - if (string.IsNullOrWhiteSpace(key)) - { - throw new ArgumentNullException(nameof(key)); - } - if (StringValues.IsNullOrEmpty(value)) - { - headers.Remove(key); - } - else - { - headers[key] = value; - } - } - private static string DeQuote(string value) { if (string.IsNullOrWhiteSpace(value)) diff --git a/src/Microsoft.AspNet.Http/QueryCollection.cs b/src/Microsoft.AspNet.Http/QueryCollection.cs new file mode 100644 index 00000000..fbeb5779 --- /dev/null +++ b/src/Microsoft.AspNet.Http/QueryCollection.cs @@ -0,0 +1,412 @@ +// 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; +using System.Collections.Generic; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNet.Http.Internal +{ + /// + /// The HttpRequest query string collection + /// + public sealed class QueryCollection : IQueryCollection + { +#if !DNXCORE50 + private static readonly string[] EmptyKeys = new string[0]; + private static readonly StringValues[] EmptyValues = new StringValues[0]; +#endif + private static readonly Enumerator EmptyEnumerator = new Enumerator(); + // Pre-box + private static readonly IEnumerator> EmptyIEnumeratorType = EmptyEnumerator; + private static readonly IEnumerator EmptyIEnumerator = EmptyEnumerator; + + private Dictionary Store { get; set; } + + public QueryCollection() + { + } + + public QueryCollection(Dictionary store) + { + Store = store; + } + + public QueryCollection(QueryCollection store) + { + Store = store.Store; + } + + public QueryCollection(int capacity) + { + Store = new Dictionary(capacity, StringComparer.OrdinalIgnoreCase); + } + + /// + /// Get or sets the associated value from the collection as a single string. + /// + /// The header name. + /// the associated value from the collection as a StringValues or StringValues.Empty if the key is not present. + public StringValues this[string key] + { + get + { + if (Store == null) + { + return StringValues.Empty; + } + + StringValues value; + if (TryGetValue(key, out value)) + { + return value; + } + return StringValues.Empty; + } + + set + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (StringValues.IsNullOrEmpty(value)) + { + if (Store == null) + { + return; + } + + Store.Remove(key); + } + else + { + if (Store == null) + { + Store = new Dictionary(1, StringComparer.OrdinalIgnoreCase); + } + + Store[key] = value; + } + } + } + + /// + /// Throws KeyNotFoundException if the key is not present. + /// + /// The header name. + /// + StringValues IDictionary.this[string key] + { + get { return Store[key]; } + set { this[key] = value; } + } + + /// + /// Gets the number of elements contained in the ;. + /// + /// The number of elements contained in the . + public int Count + { + get + { + if (Store == null) + { + return 0; + } + return Store.Count; + } + } + + /// + /// 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 ICollection Keys + { + get + { + if (Store == null) + { +#if DNXCORE50 + return Array.Empty(); +#else + return EmptyKeys; +#endif + } + return Store.Keys; + } + } + + public ICollection Values + { + get + { + if (Store == null) + { +#if DNXCORE50 + return Array.Empty(); +#else + return EmptyValues; +#endif + } + return Store.Values; + } + } + + /// + /// Adds a new list of items to the collection. + /// + /// The item to add. + public void Add(KeyValuePair item) + { + if (item.Key == null) + { + throw new ArgumentNullException(nameof(item.Key)); + } + if (Store == null) + { + Store = new Dictionary(1, StringComparer.OrdinalIgnoreCase); + } + Store.Add(item.Key, item.Value); + } + + /// + /// Adds the given header and values to the collection. + /// + /// The header name. + /// The header values. + public void Add(string key, StringValues value) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (Store == null) + { + Store = new Dictionary(1); + } + Store.Add(key, value); + } + + /// + /// Clears the entire list of objects. + /// + public void Clear() + { + if (Store == null) + { + return; + } + Store.Clear(); + } + + /// + /// Returns a value indicating whether the specified object occurs within this collection. + /// + /// The item. + /// true if the specified object occurs within this collection; otherwise, false. + public bool Contains(KeyValuePair item) + { + StringValues value; + if (Store == null || + !Store.TryGetValue(item.Key, out value) || + !StringValues.Equals(value, item.Value)) + { + return false; + } + return true; + } + + /// + /// Determines whether the contains a specific key. + /// + /// The key. + /// true if the contains a specific key; otherwise, false. + public bool ContainsKey(string key) + { + if (Store == null) + { + return false; + } + return Store.ContainsKey(key); + } + + /// + /// Copies the elements to a one-dimensional Array instance at the specified index. + /// + /// The one-dimensional Array that is the destination of the specified objects copied from the . + /// The zero-based index in at which copying begins. + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + if (Store == null) + { + return; + } + + foreach (var item in Store) + { + array[arrayIndex] = item; + arrayIndex++; + } + } + + /// + /// Removes the given item from the the collection. + /// + /// The item. + /// true if the specified object was removed from the collection; otherwise, false. + public bool Remove(KeyValuePair item) + { + if (Store == null) + { + return false; + } + + StringValues value; + + if (Store.TryGetValue(item.Key, out value) && StringValues.Equals(item.Value, value)) + { + return Store.Remove(item.Key); + } + return false; + } + + /// + /// Removes the given header from the collection. + /// + /// The header name. + /// true if the specified object was removed from the collection; otherwise, false. + public bool Remove(string key) + { + if (Store == null) + { + return false; + } + return Store.Remove(key); + } + + /// + /// Retrieves a value from the dictionary. + /// + /// The header name. + /// The value. + /// true if the contains the key; otherwise, false. + public bool TryGetValue(string key, out StringValues value) + { + if (Store == null) + { + value = default(StringValues); + return false; + } + return Store.TryGetValue(key, out value); + } + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// An object that can be used to iterate through the collection. + public Enumerator GetEnumerator() + { + if (Store == null || Store.Count == 0) + { + // Non-boxed Enumerator + return EmptyEnumerator; + } + return new Enumerator(Store.GetEnumerator()); + } + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// An object that can be used to iterate through the collection. + IEnumerator> IEnumerable>.GetEnumerator() + { + if (Store == null || Store.Count == 0) + { + // Non-boxed Enumerator + return EmptyIEnumeratorType; + } + return Store.GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// An object that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() + { + if (Store == null || Store.Count == 0) + { + // Non-boxed Enumerator + return EmptyIEnumerator; + } + return Store.GetEnumerator(); + } + + public struct Enumerator : IEnumerator> + { + // Do NOT make this readonly, or MoveNext will not work + private Dictionary.Enumerator _dictionaryEnumerator; + private bool _notEmpty; + + internal Enumerator(Dictionary.Enumerator dictionaryEnumerator) + { + _dictionaryEnumerator = dictionaryEnumerator; + _notEmpty = true; + } + + public bool MoveNext() + { + if (_notEmpty) + { + return _dictionaryEnumerator.MoveNext(); + } + return false; + } + + public KeyValuePair Current + { + get + { + if (_notEmpty) + { + return _dictionaryEnumerator.Current; + } + return default(KeyValuePair); + } + } + + public void Dispose() + { + } + + object IEnumerator.Current + { + get + { + return Current; + } + } + + void IEnumerator.Reset() + { + if (_notEmpty) + { + ((IEnumerator)_dictionaryEnumerator).Reset(); + } + } + } + } +} diff --git a/src/Microsoft.AspNet.Http/ReadableStringCollection.cs b/src/Microsoft.AspNet.Http/ReadableStringCollection.cs deleted file mode 100644 index 8239699e..00000000 --- a/src/Microsoft.AspNet.Http/ReadableStringCollection.cs +++ /dev/null @@ -1,99 +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; -using System.Collections; -using System.Collections.Generic; -using Microsoft.Extensions.Primitives; - -namespace Microsoft.AspNet.Http.Internal -{ - /// - /// Accessors for query, forms, etc. - /// - public class ReadableStringCollection : IReadableStringCollection - { - public static readonly IReadableStringCollection Empty = new ReadableStringCollection(new Dictionary(0)); - - /// - /// Create a new wrapper - /// - /// - public ReadableStringCollection(IDictionary store) - { - if (store == null) - { - throw new ArgumentNullException(nameof(store)); - } - - Store = store; - } - - private IDictionary Store { get; set; } - - /// - /// Gets the number of elements contained in the collection. - /// - public int Count - { - get { return Store.Count; } - } - - /// - /// Gets a collection containing the keys. - /// - public ICollection Keys - { - get { return Store.Keys; } - } - - - /// - /// Get the associated value from the collection. Multiple values will be merged. - /// Returns StringValues.Empty if the key is not present. - /// - /// - /// - public StringValues this[string key] - { - get - { - StringValues value; - if (Store.TryGetValue(key, out value)) - { - return value; - } - return StringValues.Empty; - } - } - - /// - /// Determines whether the collection contains an element with the specified key. - /// - /// - /// - public bool ContainsKey(string key) - { - return Store.ContainsKey(key); - } - - - /// - /// - /// - /// - public IEnumerator> GetEnumerator() - { - return Store.GetEnumerator(); - } - - /// - /// - /// - /// - IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} diff --git a/src/Microsoft.AspNet.Http/ReferenceReadStream.cs b/src/Microsoft.AspNet.Http/ReferenceReadStream.cs index 1a413d42..dc1f0287 100644 --- a/src/Microsoft.AspNet.Http/ReferenceReadStream.cs +++ b/src/Microsoft.AspNet.Http/ReferenceReadStream.cs @@ -61,7 +61,7 @@ public override long Position ThrowIfDisposed(); if (value < 0 || value > Length) { - throw new ArgumentOutOfRangeException(nameof(value), value, "The Position must be within the length of the Stream: " + Length); + throw new ArgumentOutOfRangeException(nameof(value), value, $"The Position must be within the length of the Stream: {Length.ToString()}"); } VerifyPosition(); _position = value; diff --git a/src/Microsoft.AspNet.Http/RequestCookies.cs b/src/Microsoft.AspNet.Http/RequestCookies.cs new file mode 100644 index 00000000..91d606f8 --- /dev/null +++ b/src/Microsoft.AspNet.Http/RequestCookies.cs @@ -0,0 +1,467 @@ +// 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; +using System.Collections.Generic; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNet.Http.Internal +{ + public sealed class RequestCookies : IRequestCookies + { +#if !DNXCORE50 + private static readonly string[] EmptyKeys = new string[0]; + private static readonly string[] EmptyValues = new string[0]; +#endif + private static readonly Enumerator EmptyEnumerator = new Enumerator(); + // Pre-box + private static readonly IEnumerator> EmptyIEnumeratorType = EmptyEnumerator; + private static readonly IEnumerator EmptyIEnumerator = EmptyEnumerator; + + private Dictionary Store { get; set; } + + public RequestCookies() + { + } + + public RequestCookies(int capacity) + { + Store = new Dictionary(capacity, StringComparer.OrdinalIgnoreCase); + } + + public string this[string key] + { + get + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (Store == null) + { + return string.Empty; + } + + string value; + if (TryGetValue(key, out value)) + { + return value; + } + return string.Empty; + } + + set + { + if (Store == null) + { + Store = new Dictionary(1, StringComparer.OrdinalIgnoreCase); + } + Store[key] = value; + } + } + + StringValues IRequestCookies.this[string key] + { + get + { + return new StringValues(this[key]); + } + + set + { + this[key] = value.ToString(); + } + } + + StringValues IDictionary.this[string key] + { + get + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (Store == null) + { + throw new KeyNotFoundException(); + } + + return this[key]; + } + set { this[key] = value; } + } + + /// + /// Get the associated values from the collection in their original format. + /// Returns null if the key is not present. + /// + /// + /// + public IList GetValues(string key) + { + string value; + return TryGetValue(key, out value) ? new[] { value } : null; + } + + public void Reparse(IList values) + { + Clear(); + + IList cookies; + if (CookieHeaderValue.TryParseList(values, out cookies)) + { + foreach (var cookie in cookies) + { + var name = Uri.UnescapeDataString(cookie.Name.Replace('+', ' ')); + var value = Uri.UnescapeDataString(cookie.Value.Replace('+', ' ')); + this[name] = value; + } + } + } + + public int Count + { + get + { + if (Store == null) + { + return 0; + } + return Store.Count; + } + } + + public bool IsReadOnly + { + get + { + return false; + } + } + + public ICollection Keys + { + get + { + if (Store == null) + { +#if DNXCORE50 + return Array.Empty(); +#else + return EmptyKeys; +#endif + } + return Store.Keys; + } + } + + public ICollection Values + { + get + { + if (Store == null) + { +#if DNXCORE50 + return Array.Empty(); +#else + return EmptyValues; +#endif + } + return Store.Values; + } + } + + public void Add(KeyValuePair item) + { + if (item.Key == null) + { + throw new ArgumentNullException(nameof(item.Key)); + } + if (Store == null) + { + Store = new Dictionary(1, StringComparer.OrdinalIgnoreCase); + } + Store.Add(item.Key, item.Value); + } + + public void Add(string key, string value) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (StringValues.IsNullOrEmpty(value)) + { + if (Store.ContainsKey(key)) + { + throw new ArgumentException("An element with the same key already exists in the Collection"); + } + return; + } + + if (Store == null) + { + Store = new Dictionary(1); + } + Store.Add(key, value); + } + + public void Clear() + { + if (Store == null) + { + return; + } + Store.Clear(); + } + + public bool Contains(KeyValuePair item) + { + string value; + if (Store == null || + !Store.TryGetValue(item.Key, out value) || + !value.Equals(item.Value)) + { + return false; + } + return true; + } + + public bool ContainsKey(string key) + { + if (Store == null) + { + return false; + } + return Store.ContainsKey(key); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + if (Store == null) + { + return; + } + + foreach (var item in Store) + { + array[arrayIndex] = item; + arrayIndex++; + } + } + + public bool Remove(KeyValuePair item) + { + if (Store == null) + { + return false; + } + + string value; + + if (Store.TryGetValue(item.Key, out value) && item.Value == value) + { + return Store.Remove(item.Key); + } + return false; + } + + public bool Remove(string key) + { + if (Store == null) + { + return false; + } + return Store.Remove(key); + } + + public bool TryGetValue(string key, out string value) + { + if (Store == null) + { + value = default(string); + return false; + } + return Store.TryGetValue(key, out value); + } + + int ICollection>.Count => Count; + + bool ICollection>.IsReadOnly => IsReadOnly; + + ICollection IDictionary.Keys => Keys; + + ICollection IDictionary.Values => (ICollection)Values; + + void ICollection>.Add(KeyValuePair item) + { + Add(item.Key, item.Value.ToString()); + } + + void IDictionary.Add(string key, StringValues value) + { + Add(key, value.ToString()); + } + + void ICollection>.Clear() + { + Clear(); + } + + bool ICollection>.Contains(KeyValuePair item) + { + return Contains(new KeyValuePair(item.Key, item.Value.ToString())); + } + + bool IDictionary.ContainsKey(string key) => ContainsKey(key); + + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + + if (Store == null) + { + return; + } + + foreach (var item in Store) + { + array[arrayIndex] = new KeyValuePair(item.Key, item.Value); + arrayIndex++; + } + } + + bool ICollection>.Remove(KeyValuePair item) + { + if (Store == null) + { + return false; + } + + string value; + + if (Store.TryGetValue(item.Key, out value) && item.Value == value) + { + return Store.Remove(item.Key); + } + return false; + } + + bool IDictionary.Remove(string key) => Remove(key); + + bool IDictionary.TryGetValue(string key, out StringValues value) + { + string val; + if (!TryGetValue(key, out val)) + { + return false; + } + value = val; + return true; + } + + /// + /// Returns an struct enumerator that iterates through a collection without boxing. + /// + /// An object that can be used to iterate through the collection. + public Enumerator GetEnumerator() + { + if (Store == null || Store.Count == 0) + { + // Non-boxed Enumerator + return EmptyEnumerator; + } + // Non-boxed Enumerator + return new Enumerator(Store.GetEnumerator()); + } + + /// + /// Returns an enumerator that iterates through a collection, boxes in non-empty path. + /// + /// An object that can be used to iterate through the collection. + IEnumerator> IEnumerable>.GetEnumerator() + { + if (Store == null || Store.Count == 0) + { + // Non-boxed Enumerator + return EmptyIEnumeratorType; + } + // Boxed Enumerator + return GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through a collection, boxes in non-empty path. + /// + /// An object that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() + { + if (Store == null || Store.Count == 0) + { + // Non-boxed Enumerator + return EmptyIEnumerator; + } + // Boxed Enumerator + return GetEnumerator(); + } + + public struct Enumerator : IEnumerator> + { + // Do NOT make this readonly, or MoveNext will not work + private Dictionary.Enumerator _dictionaryEnumerator; + private bool _notEmpty; + + internal Enumerator(Dictionary.Enumerator dictionaryEnumerator) + { + _dictionaryEnumerator = dictionaryEnumerator; + _notEmpty = true; + } + + public bool MoveNext() + { + if (_notEmpty) + { + return _dictionaryEnumerator.MoveNext(); + } + return false; + } + + public KeyValuePair Current + { + get + { + if (_notEmpty) + { + var current = _dictionaryEnumerator.Current; + return new KeyValuePair(current.Key, new StringValues(current.Value)); + } + return default(KeyValuePair); + } + } + + object IEnumerator.Current + { + get + { + return Current; + } + } + + public void Dispose() + { + } + + public void Reset() + { + if (_notEmpty) + { + ((IEnumerator)_dictionaryEnumerator).Reset(); + } + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http/RequestCookiesCollection.cs b/src/Microsoft.AspNet.Http/RequestCookiesCollection.cs deleted file mode 100644 index d0001c1a..00000000 --- a/src/Microsoft.AspNet.Http/RequestCookiesCollection.cs +++ /dev/null @@ -1,105 +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; -using System.Collections; -using System.Collections.Generic; -using Microsoft.Extensions.Primitives; -using Microsoft.Net.Http.Headers; - -namespace Microsoft.AspNet.Http.Internal -{ - public class RequestCookiesCollection : IReadableStringCollection - { - private readonly IDictionary _dictionary; - - public RequestCookiesCollection() - { - _dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); - } - - public StringValues this[string key] - { - get { return Get(key); } - } - - /// - /// Gets the number of elements contained in the collection. - /// - public int Count - { - get { return _dictionary.Count; } - } - - /// - /// Gets a collection containing the keys. - /// - public ICollection Keys - { - get { return _dictionary.Keys; } - } - - /// - /// Determines whether the collection contains an element with the specified key. - /// - /// - /// - public bool ContainsKey(string key) - { - return _dictionary.ContainsKey(key); - } - - /// - /// Get the associated value from the collection. Multiple values will be merged. - /// Returns null if the key is not present. - /// - /// - /// - public string Get(string key) - { - string value; - return _dictionary.TryGetValue(key, out value) ? value : null; - } - - /// - /// Get the associated values from the collection in their original format. - /// Returns null if the key is not present. - /// - /// - /// - public IList GetValues(string key) - { - string value; - return _dictionary.TryGetValue(key, out value) ? new[] { value } : null; - } - - public void Reparse(IList values) - { - _dictionary.Clear(); - - IList cookies; - if (CookieHeaderValue.TryParseList(values, out cookies)) - { - foreach (var cookie in cookies) - { - var name = Uri.UnescapeDataString(cookie.Name.Replace('+', ' ')); - var value = Uri.UnescapeDataString(cookie.Value.Replace('+', ' ')); - _dictionary[name] = value; - } - } - } - - public IEnumerator> GetEnumerator() - { - foreach (var pair in _dictionary) - { - yield return new KeyValuePair(pair.Key, pair.Value); - } - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http/ResponseCookies.cs b/src/Microsoft.AspNet.Http/ResponseCookies.cs index 5075efb0..ed76dec8 100644 --- a/src/Microsoft.AspNet.Http/ResponseCookies.cs +++ b/src/Microsoft.AspNet.Http/ResponseCookies.cs @@ -2,8 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Linq; using System.Text.Encodings.Web; +using System.Collections.Generic; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; @@ -80,10 +80,10 @@ public void Append(string key, string value, CookieOptions options) /// public void Delete(string key) { - var encodedKeyPlusEquals = UrlEncoder.Default.Encode(key) + "="; - Func predicate = value => value.StartsWith(encodedKeyPlusEquals, StringComparison.OrdinalIgnoreCase); + var encodedKeyPlusEquals = $"{UrlEncoder.Default.Encode(key)}="; + Func predicate = (value, encKeyPlusEquals) => value.StartsWith(encKeyPlusEquals, StringComparison.OrdinalIgnoreCase); - StringValues deleteCookies = encodedKeyPlusEquals + "; expires=Thu, 01-Jan-1970 00:00:00 GMT"; + StringValues deleteCookies = $"{encodedKeyPlusEquals}; expires=Thu, 01-Jan-1970 00:00:00 GMT"; var existingValues = Headers[HeaderNames.SetCookie]; if (StringValues.IsNullOrEmpty(existingValues)) { @@ -91,7 +91,24 @@ public void Delete(string key) } else { - Headers[HeaderNames.SetCookie] = existingValues.Where(value => !predicate(value)).Concat(deleteCookies).ToArray(); + var values = existingValues.ToArray(); + var newValues = new List(); + + for (var i = 0; i < values.Length; i++) + { + if (!predicate(values[i], encodedKeyPlusEquals)) + { + newValues.Add(values[i]); + } + } + + values = deleteCookies.ToArray(); + for (var i = 0; i < values.Length; i++) + { + newValues.Add(values[i]); + } + + Headers[HeaderNames.SetCookie] = new StringValues(newValues.ToArray()); } } @@ -106,33 +123,44 @@ public void Delete(string key, CookieOptions options) { throw new ArgumentNullException(nameof(options)); } - - var encodedKeyPlusEquals = UrlEncoder.Default.Encode(key) + "="; + + var encodedKeyPlusEquals = $"{UrlEncoder.Default.Encode(key)}="; bool domainHasValue = !string.IsNullOrEmpty(options.Domain); bool pathHasValue = !string.IsNullOrEmpty(options.Path); - Func rejectPredicate; + Func rejectPredicate; if (domainHasValue) { - rejectPredicate = value => - value.StartsWith(encodedKeyPlusEquals, StringComparison.OrdinalIgnoreCase) && - value.IndexOf("domain=" + options.Domain, StringComparison.OrdinalIgnoreCase) != -1; + rejectPredicate = (value, encKeyPlusEquals, opts) => + value.StartsWith(encKeyPlusEquals, StringComparison.OrdinalIgnoreCase) && + value.IndexOf($"domain={opts.Domain}", StringComparison.OrdinalIgnoreCase) != -1; } else if (pathHasValue) { - rejectPredicate = value => - value.StartsWith(encodedKeyPlusEquals, StringComparison.OrdinalIgnoreCase) && - value.IndexOf("path=" + options.Path, StringComparison.OrdinalIgnoreCase) != -1; + rejectPredicate = (value, encKeyPlusEquals, opts) => + value.StartsWith(encKeyPlusEquals, StringComparison.OrdinalIgnoreCase) && + value.IndexOf($"path={opts.Path}", StringComparison.OrdinalIgnoreCase) != -1; } else { - rejectPredicate = value => value.StartsWith(encodedKeyPlusEquals, StringComparison.OrdinalIgnoreCase); + rejectPredicate = (value, encKeyPlusEquals, opts) => value.StartsWith(encKeyPlusEquals, StringComparison.OrdinalIgnoreCase); } var existingValues = Headers[HeaderNames.SetCookie]; if (!StringValues.IsNullOrEmpty(existingValues)) { - Headers[HeaderNames.SetCookie] = existingValues.Where(value => !rejectPredicate(value)).ToArray(); + var values = existingValues.ToArray(); + var newValues = new List(); + + for (var i = 0; i < values.Length; i++) + { + if (!rejectPredicate(values[i], encodedKeyPlusEquals, options)) + { + newValues.Add(values[i]); + } + } + + Headers[HeaderNames.SetCookie] = new StringValues(newValues.ToArray()); } Append(key, string.Empty, new CookieOptions diff --git a/src/Microsoft.AspNet.WebUtilities/BufferedReadStream.cs b/src/Microsoft.AspNet.WebUtilities/BufferedReadStream.cs index d78d3b34..26d8056b 100644 --- a/src/Microsoft.AspNet.WebUtilities/BufferedReadStream.cs +++ b/src/Microsoft.AspNet.WebUtilities/BufferedReadStream.cs @@ -262,7 +262,7 @@ public bool EnsureBuffered(int minCount) { if (minCount > _buffer.Length) { - throw new ArgumentOutOfRangeException(nameof(minCount), minCount, "The value must be smaller than the buffer size: " + _buffer.Length); + throw new ArgumentOutOfRangeException(nameof(minCount), minCount, $"The value must be smaller than the buffer size: {_buffer.Length.ToString()}"); } while (_bufferCount < minCount) { @@ -289,7 +289,7 @@ public async Task EnsureBufferedAsync(int minCount, CancellationToken canc { if (minCount > _buffer.Length) { - throw new ArgumentOutOfRangeException(nameof(minCount), minCount, "The value must be smaller than the buffer size: " + _buffer.Length); + throw new ArgumentOutOfRangeException(nameof(minCount), minCount, $"The value must be smaller than the buffer size: {_buffer.Length.ToString()}"); } while (_bufferCount < minCount) { @@ -315,38 +315,42 @@ public async Task EnsureBufferedAsync(int minCount, CancellationToken canc public string ReadLine(int lengthLimit) { CheckDisposed(); - var builder = new MemoryStream(200); - bool foundCR = false, foundCRLF = false; - - while (!foundCRLF && EnsureBuffered()) + using (var builder = new MemoryStream(200)) { - if (builder.Length > lengthLimit) + bool foundCR = false, foundCRLF = false; + + while (!foundCRLF && EnsureBuffered()) { - throw new InvalidOperationException("Line length limit exceeded: " + lengthLimit); + if (builder.Length > lengthLimit) + { + throw new InvalidOperationException($"Line length limit exceeded: {lengthLimit.ToString()}"); + } + ProcessLineChar(builder, ref foundCR, ref foundCRLF); } - ProcessLineChar(builder, ref foundCR, ref foundCRLF); - } - return DecodeLine(builder, foundCRLF); + return DecodeLine(builder, foundCRLF); + } } public async Task ReadLineAsync(int lengthLimit, CancellationToken cancellationToken) { CheckDisposed(); - var builder = new MemoryStream(200); - bool foundCR = false, foundCRLF = false; - - while (!foundCRLF && await EnsureBufferedAsync(cancellationToken)) + using (var builder = new MemoryStream(200)) { - if (builder.Length > lengthLimit) + bool foundCR = false, foundCRLF = false; + + while (!foundCRLF && await EnsureBufferedAsync(cancellationToken)) { - throw new InvalidOperationException("Line length limit exceeded: " + lengthLimit); + if (builder.Length > lengthLimit) + { + throw new InvalidOperationException($"Line length limit exceeded: {lengthLimit.ToString()}"); + } + + ProcessLineChar(builder, ref foundCR, ref foundCRLF); } - ProcessLineChar(builder, ref foundCR, ref foundCRLF); + return DecodeLine(builder, foundCRLF); } - - return DecodeLine(builder, foundCRLF); } private void ProcessLineChar(MemoryStream builder, ref bool foundCR, ref bool foundCRLF) diff --git a/src/Microsoft.AspNet.WebUtilities/FormReader.cs b/src/Microsoft.AspNet.WebUtilities/FormReader.cs index e361bac1..5f05ea11 100644 --- a/src/Microsoft.AspNet.WebUtilities/FormReader.cs +++ b/src/Microsoft.AspNet.WebUtilities/FormReader.cs @@ -185,7 +185,7 @@ public static IDictionary ReadForm(string text) /// /// The HTTP form body to parse. /// The collection containing the parsed HTTP form body. - public static Task> ReadFormAsync(Stream stream, CancellationToken cancellationToken = new CancellationToken()) + public static Task> ReadFormAsync(Stream stream, CancellationToken cancellationToken = new CancellationToken()) { return ReadFormAsync(stream, Encoding.UTF8, cancellationToken); } @@ -195,7 +195,24 @@ public static IDictionary ReadForm(string text) /// /// The HTTP form body to parse. /// The collection containing the parsed HTTP form body. - public static async Task> ReadFormAsync(Stream stream, Encoding encoding, CancellationToken cancellationToken = new CancellationToken()) + public static async Task> ReadFormAsync(Stream stream, Encoding encoding, CancellationToken cancellationToken = new CancellationToken()) + { + var result = await ReadNullableFormAsync(stream, encoding, cancellationToken); + + if (result == null) + { + return new Dictionary(); + } + + return result; + } + + /// + /// Parses an HTTP form body - internal use, returns null if no entires. + /// + /// The HTTP form body to parse. + /// The collection containing the parsed HTTP form body, null if there are no entries. + public static async Task> ReadNullableFormAsync(Stream stream, Encoding encoding, CancellationToken cancellationToken = new CancellationToken()) { var reader = new FormReader(stream, encoding); @@ -207,6 +224,11 @@ public static IDictionary ReadForm(string text) pair = await reader.ReadNextPairAsync(cancellationToken); } + if (!accumulator.HasValues) + { + return null; + } + return accumulator.GetResults(); } } diff --git a/src/Microsoft.AspNet.WebUtilities/KeyValueAccumulator.cs b/src/Microsoft.AspNet.WebUtilities/KeyValueAccumulator.cs index c0ecc8c4..9e8f0555 100644 --- a/src/Microsoft.AspNet.WebUtilities/KeyValueAccumulator.cs +++ b/src/Microsoft.AspNet.WebUtilities/KeyValueAccumulator.cs @@ -7,17 +7,16 @@ namespace Microsoft.AspNet.WebUtilities { - public class KeyValueAccumulator + public struct KeyValueAccumulator { private Dictionary> _accumulator; - public KeyValueAccumulator() - { - _accumulator = new Dictionary>(StringComparer.OrdinalIgnoreCase); - } - public void Append(string key, string value) { + if (_accumulator == null) + { + _accumulator = new Dictionary>(StringComparer.OrdinalIgnoreCase); + } List values; if (_accumulator.TryGetValue(key, out values)) { @@ -25,18 +24,29 @@ public void Append(string key, string value) } else { - _accumulator[key] = new List(1) { value }; + values = new List(1); + values.Add(value); + _accumulator[key] = values; } } - public IDictionary GetResults() + public bool HasValues => _accumulator != null; + + public Dictionary GetResults() { - var results = new Dictionary(StringComparer.OrdinalIgnoreCase); + if (_accumulator == null) + { + return new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + var results = new Dictionary(_accumulator.Count, StringComparer.OrdinalIgnoreCase); + foreach (var kv in _accumulator) { - results.Add(kv.Key, kv.Value.ToArray()); + results.Add(kv.Key, kv.Value.Count == 1 ? new StringValues(kv.Value[0]) : new StringValues(kv.Value.ToArray())); } + return results; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.AspNet.WebUtilities/MultipartReader.cs b/src/Microsoft.AspNet.WebUtilities/MultipartReader.cs index ef6f03a9..9bfcd916 100644 --- a/src/Microsoft.AspNet.WebUtilities/MultipartReader.cs +++ b/src/Microsoft.AspNet.WebUtilities/MultipartReader.cs @@ -74,7 +74,7 @@ public MultipartReader(string boundary, Stream stream, int bufferSize) return new MultipartSection() { Headers = headers, Body = _currentStream, BaseStreamOffset = baseStreamOffset }; } - private async Task> ReadHeadersAsync(CancellationToken cancellationToken) + private async Task> ReadHeadersAsync(CancellationToken cancellationToken) { int totalSize = 0; var accumulator = new KeyValueAccumulator(); @@ -84,10 +84,10 @@ private async Task> ReadHeadersAsync(Cancellat totalSize += line.Length; if (totalSize > TotalHeaderSizeLimit) { - throw new InvalidOperationException("Total header size limit exceeded: " + TotalHeaderSizeLimit); + throw new InvalidOperationException($"Total header size limit exceeded: {TotalHeaderSizeLimit.ToString()}"); } int splitIndex = line.IndexOf(':'); - Debug.Assert(splitIndex > 0, "Invalid header line: " + line); + Debug.Assert(splitIndex > 0, $"Invalid header line: {line.ToString()}"); if (splitIndex >= 0) { var name = line.Substring(0, splitIndex); diff --git a/src/Microsoft.AspNet.WebUtilities/MultipartSection.cs b/src/Microsoft.AspNet.WebUtilities/MultipartSection.cs index 29dfc495..f3a6e5cc 100644 --- a/src/Microsoft.AspNet.WebUtilities/MultipartSection.cs +++ b/src/Microsoft.AspNet.WebUtilities/MultipartSection.cs @@ -16,7 +16,7 @@ public string ContentType StringValues values; if (Headers.TryGetValue("Content-Type", out values)) { - return values; + return values.ToString(); } return null; } @@ -29,13 +29,13 @@ public string ContentDisposition StringValues values; if (Headers.TryGetValue("Content-Disposition", out values)) { - return values; + return values.ToString(); } return null; } } - public IDictionary Headers { get; set; } + public Dictionary Headers { get; set; } public Stream Body { get; set; } diff --git a/src/Microsoft.AspNet.WebUtilities/QueryHelpers.cs b/src/Microsoft.AspNet.WebUtilities/QueryHelpers.cs index 57ee90bc..c6b325ec 100644 --- a/src/Microsoft.AspNet.WebUtilities/QueryHelpers.cs +++ b/src/Microsoft.AspNet.WebUtilities/QueryHelpers.cs @@ -107,13 +107,21 @@ private static string AddQueryString( /// /// The raw query string value, with or without the leading '?'. /// A collection of parsed keys and values. - public static IDictionary ParseQuery(string queryString) + public static KeyValueAccumulator ParseQuery(string queryString) { + var accumulator = new KeyValueAccumulator(); + + if (string.IsNullOrEmpty(queryString) || queryString == "?") + { + return accumulator; + } + + int scanIndex = 0; if (!string.IsNullOrEmpty(queryString) && queryString[0] == '?') { - queryString = queryString.Substring(1); + scanIndex = 1; } - var accumulator = new KeyValueAccumulator(); + int textLength = queryString.Length; int equalIndex = queryString.IndexOf('='); @@ -121,7 +129,6 @@ public static IDictionary ParseQuery(string queryString) { equalIndex = textLength; } - int scanIndex = 0; while (scanIndex < textLength) { int delimiterIndex = queryString.IndexOf('&', scanIndex); @@ -149,7 +156,7 @@ public static IDictionary ParseQuery(string queryString) scanIndex = delimiterIndex + 1; } - return accumulator.GetResults(); + return accumulator; } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Http.Extensions.Tests/HeaderDictionaryTypeExtensionsTest.cs b/test/Microsoft.AspNet.Http.Extensions.Tests/HeaderDictionaryTypeExtensionsTest.cs index fb2ef452..4a8ee927 100644 --- a/test/Microsoft.AspNet.Http.Extensions.Tests/HeaderDictionaryTypeExtensionsTest.cs +++ b/test/Microsoft.AspNet.Http.Extensions.Tests/HeaderDictionaryTypeExtensionsTest.cs @@ -103,10 +103,11 @@ public void GetListT_KnownTypeWithValidValue_Success() public void GetListT_KnownTypeWithMissingValue_Null() { var context = new DefaultHttpContext(); - - var result = context.Request.GetTypedHeaders().GetList(HeaderNames.Accept); - - Assert.Null(result); + var result1 = context.Request.GetTypedHeaders().GetList(HeaderNames.Accept); + var result2 = context.Request.GetTypedHeaders().GetList(HeaderNames.Accept); + + Assert.Null(result1); + Assert.Null(result2); } [Fact] diff --git a/test/Microsoft.AspNet.Http.Tests/DefaultHttpRequestTests.cs b/test/Microsoft.AspNet.Http.Tests/DefaultHttpRequestTests.cs index e020da61..b7e705a5 100644 --- a/test/Microsoft.AspNet.Http.Tests/DefaultHttpRequestTests.cs +++ b/test/Microsoft.AspNet.Http.Tests/DefaultHttpRequestTests.cs @@ -150,7 +150,7 @@ public void Query_GetAndSet() Assert.Equal("value0", query1["name0"]); Assert.Equal("value1", query1["name1"]); - var query2 = new ReadableStringCollection(new Dictionary() + var query2 = new QueryCollection( new Dictionary() { { "name2", "value2" } }); @@ -177,10 +177,10 @@ public void Cookies_GetAndSet() Assert.Equal("value0", cookies1["name0"]); Assert.Equal("value1", cookies1["name1"]); - var cookies2 = new ReadableStringCollection(new Dictionary() + var cookies2 = new RequestCookies() { { "name2", "value2" } - }); + }; request.Cookies = cookies2; Assert.Same(cookies2, request.Cookies); Assert.Equal("value2", request.Cookies["name2"]); diff --git a/test/Microsoft.AspNet.WebUtilities.Tests/QueryHelpersTests.cs b/test/Microsoft.AspNet.WebUtilities.Tests/QueryHelpersTests.cs index 35a68f1a..e0303b35 100644 --- a/test/Microsoft.AspNet.WebUtilities.Tests/QueryHelpersTests.cs +++ b/test/Microsoft.AspNet.WebUtilities.Tests/QueryHelpersTests.cs @@ -12,7 +12,7 @@ public class QueryHelperTests [Fact] public void ParseQueryWithUniqueKeysWorks() { - var collection = QueryHelpers.ParseQuery("?key1=value1&key2=value2"); + var collection = QueryHelpers.ParseQuery("?key1=value1&key2=value2").GetResults(); Assert.Equal(2, collection.Count); Assert.Equal("value1", collection["key1"].FirstOrDefault()); Assert.Equal("value2", collection["key2"].FirstOrDefault()); @@ -21,7 +21,7 @@ public void ParseQueryWithUniqueKeysWorks() [Fact] public void ParseQueryWithoutQuestionmarkWorks() { - var collection = QueryHelpers.ParseQuery("key1=value1&key2=value2"); + var collection = QueryHelpers.ParseQuery("key1=value1&key2=value2").GetResults(); Assert.Equal(2, collection.Count); Assert.Equal("value1", collection["key1"].FirstOrDefault()); Assert.Equal("value2", collection["key2"].FirstOrDefault()); @@ -30,7 +30,7 @@ public void ParseQueryWithoutQuestionmarkWorks() [Fact] public void ParseQueryWithDuplicateKeysGroups() { - var collection = QueryHelpers.ParseQuery("?key1=valueA&key2=valueB&key1=valueC"); + var collection = QueryHelpers.ParseQuery("?key1=valueA&key2=valueB&key1=valueC").GetResults(); Assert.Equal(2, collection.Count); Assert.Equal(new[] { "valueA", "valueC" }, collection["key1"]); Assert.Equal("valueB", collection["key2"].FirstOrDefault()); @@ -39,7 +39,7 @@ public void ParseQueryWithDuplicateKeysGroups() [Fact] public void ParseQueryWithEmptyValuesWorks() { - var collection = QueryHelpers.ParseQuery("?key1=&key2="); + var collection = QueryHelpers.ParseQuery("?key1=&key2=").GetResults(); Assert.Equal(2, collection.Count); Assert.Equal(string.Empty, collection["key1"].FirstOrDefault()); Assert.Equal(string.Empty, collection["key2"].FirstOrDefault()); @@ -48,7 +48,7 @@ public void ParseQueryWithEmptyValuesWorks() [Fact] public void ParseQueryWithEmptyKeyWorks() { - var collection = QueryHelpers.ParseQuery("?=value1&="); + var collection = QueryHelpers.ParseQuery("?=value1&=").GetResults(); Assert.Equal(1, collection.Count); Assert.Equal(new[] { "value1", "" }, collection[""]); } From bd4a703980095a4128e910d80c843e646e6fd4ec Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 23 Oct 2015 08:00:04 +0100 Subject: [PATCH 2/9] Remove StringSegment - dead code --- .../Internal/HeaderSegment.cs | 2 + .../Internal/StringSegment.cs | 149 ------------------ 2 files changed, 2 insertions(+), 149 deletions(-) delete mode 100644 src/Microsoft.AspNet.Http.Extensions/Internal/StringSegment.cs diff --git a/src/Microsoft.AspNet.Http.Extensions/Internal/HeaderSegment.cs b/src/Microsoft.AspNet.Http.Extensions/Internal/HeaderSegment.cs index 74b49e67..acd3a9ad 100644 --- a/src/Microsoft.AspNet.Http.Extensions/Internal/HeaderSegment.cs +++ b/src/Microsoft.AspNet.Http.Extensions/Internal/HeaderSegment.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.Extensions.Primitives; + namespace Microsoft.AspNet.Http.Internal { diff --git a/src/Microsoft.AspNet.Http.Extensions/Internal/StringSegment.cs b/src/Microsoft.AspNet.Http.Extensions/Internal/StringSegment.cs deleted file mode 100644 index 3223168a..00000000 --- a/src/Microsoft.AspNet.Http.Extensions/Internal/StringSegment.cs +++ /dev/null @@ -1,149 +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; - -namespace Microsoft.AspNet.Http.Internal -{ - internal struct StringSegment : IEquatable - { - private readonly string _buffer; - private readonly int _offset; - private readonly int _count; - - // - // Initializes a new instance of the class. - // - public StringSegment(string buffer, int offset, int count) - { - _buffer = buffer; - _offset = offset; - _count = count; - } - - public string Buffer - { - get { return _buffer; } - } - - public int Offset - { - get { return _offset; } - } - - public int Count - { - get { return _count; } - } - - public string Value - { - get { return _offset == -1 ? null : _buffer.Substring(_offset, _count); } - } - - public bool HasValue - { - get { return _offset != -1 && _count != 0 && _buffer != null; } - } - - public bool Equals(StringSegment other) - { - return string.Equals(_buffer, other._buffer) && _offset == other._offset && _count == other._count; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - return obj is StringSegment && Equals((StringSegment)obj); - } - - public override int GetHashCode() - { - unchecked - { - int hashCode = (_buffer != null ? _buffer.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ _offset; - hashCode = (hashCode * 397) ^ _count; - return hashCode; - } - } - - public static bool operator ==(StringSegment left, StringSegment right) - { - return left.Equals(right); - } - - public static bool operator !=(StringSegment left, StringSegment right) - { - return !left.Equals(right); - } - - public bool StartsWith(string text, StringComparison comparisonType) - { - if (text == null) - { - throw new ArgumentNullException(nameof(text)); - } - - int textLength = text.Length; - if (!HasValue || _count < textLength) - { - return false; - } - - return string.Compare(_buffer, _offset, text, 0, textLength, comparisonType) == 0; - } - - public bool EndsWith(string text, StringComparison comparisonType) - { - if (text == null) - { - throw new ArgumentNullException(nameof(text)); - } - - int textLength = text.Length; - if (!HasValue || _count < textLength) - { - return false; - } - - return string.Compare(_buffer, _offset + _count - textLength, text, 0, textLength, comparisonType) == 0; - } - - public bool Equals(string text, StringComparison comparisonType) - { - if (text == null) - { - throw new ArgumentNullException(nameof(text)); - } - - int textLength = text.Length; - if (!HasValue || _count != textLength) - { - return false; - } - - return string.Compare(_buffer, _offset, text, 0, textLength, comparisonType) == 0; - } - - public string Substring(int offset, int length) - { - return _buffer.Substring(_offset + offset, length); - } - - public StringSegment Subsegment(int offset, int length) - { - return new StringSegment(_buffer, _offset + offset, length); - } - - public override string ToString() - { - return Value ?? string.Empty; - } - } - -} From 23cac46b94905c08c24928379095053da825d5b6 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Thu, 29 Oct 2015 19:45:21 +0000 Subject: [PATCH 3/9] Initial cr feedback --- .../HttpRequest.cs | 2 +- .../IFormCollection.cs | 137 +++++++++++++ .../IFormFile.cs | 0 .../IFormFileCollection.cs | 0 .../IQueryCollection.cs | 88 +++++++++ .../IRequestCookieCollection.cs | 130 ++++++++++++ .../QueryString.cs | 2 +- .../IFormCollection.cs | 28 --- .../IQueryCollection.cs | 21 -- .../IRequestCookies.cs | 21 -- .../DefaultHttpRequest.cs | 2 +- .../Features/FormFeature.cs | 21 +- .../Features/IRequestCookiesFeature.cs | 5 +- .../Features/QueryFeature.cs | 10 +- .../Features/RequestCookiesFeature.cs | 16 +- src/Microsoft.AspNet.Http/FormCollection.cs | 136 +------------ src/Microsoft.AspNet.Http/HeaderDictionary.cs | 2 +- src/Microsoft.AspNet.Http/QueryCollection.cs | 186 +----------------- .../ReferenceReadStream.cs | 2 +- ...tCookies.cs => RequestCookieCollection.cs} | 182 ++++------------- .../BufferedReadStream.cs | 8 +- .../FormReader.cs | 22 --- .../MultipartReader.cs | 2 +- .../QueryHelpers.cs | 29 ++- .../DefaultHttpRequestTests.cs | 14 +- .../QueryHelpersTests.cs | 10 +- 26 files changed, 475 insertions(+), 601 deletions(-) create mode 100644 src/Microsoft.AspNet.Http.Abstractions/IFormCollection.cs rename src/{Microsoft.AspNet.Http.Features => Microsoft.AspNet.Http.Abstractions}/IFormFile.cs (100%) rename src/{Microsoft.AspNet.Http.Features => Microsoft.AspNet.Http.Abstractions}/IFormFileCollection.cs (100%) create mode 100644 src/Microsoft.AspNet.Http.Abstractions/IQueryCollection.cs create mode 100644 src/Microsoft.AspNet.Http.Abstractions/IRequestCookieCollection.cs delete mode 100644 src/Microsoft.AspNet.Http.Features/IFormCollection.cs delete mode 100644 src/Microsoft.AspNet.Http.Features/IQueryCollection.cs delete mode 100644 src/Microsoft.AspNet.Http.Features/IRequestCookies.cs rename src/Microsoft.AspNet.Http/{RequestCookies.cs => RequestCookieCollection.cs} (67%) diff --git a/src/Microsoft.AspNet.Http.Abstractions/HttpRequest.cs b/src/Microsoft.AspNet.Http.Abstractions/HttpRequest.cs index 9dbe0843..c0f52fd4 100644 --- a/src/Microsoft.AspNet.Http.Abstractions/HttpRequest.cs +++ b/src/Microsoft.AspNet.Http.Abstractions/HttpRequest.cs @@ -81,7 +81,7 @@ public abstract class HttpRequest /// Gets the collection of Cookies for this request. /// /// The collection of Cookies for this request. - public abstract IRequestCookies Cookies { get; set; } + public abstract IRequestCookieCollection Cookies { get; set; } /// /// Gets or sets the Content-Length header diff --git a/src/Microsoft.AspNet.Http.Abstractions/IFormCollection.cs b/src/Microsoft.AspNet.Http.Abstractions/IFormCollection.cs new file mode 100644 index 00000000..cfad94a0 --- /dev/null +++ b/src/Microsoft.AspNet.Http.Abstractions/IFormCollection.cs @@ -0,0 +1,137 @@ +// 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 the parsed form values sent with the HttpRequest. + /// + public interface IFormCollection : IEnumerable> + { + /// + /// Gets the number of elements contained in the . + /// + /// + /// The number of elements contained in the . + /// + int Count { get; } + + /// + /// Determines whether the contains a specific + /// value. + /// + /// + /// The object to locate in the . + /// + /// + /// true if item is found in the ; otherwise, + /// false. + /// + bool Contains(KeyValuePair item); + + /// + /// Copies the elements of the to an , + /// starting at a particular index. + /// + /// + /// The one-dimensional that is the destination of the elements copied + /// from . The must have zero-based + /// indexing. + /// + /// The zero-based index in array at which copying begins. + /// array is null. + /// arrayIndex is less than 0. + /// + /// The number of elements in the source + /// is greater than the available space from arrayIndex to the end of the destination + /// array. + /// + void CopyTo(KeyValuePair[] array, int arrayIndex); + + /// + /// Gets an containing the keys of the + /// . + /// + /// + /// An containing the keys of the object + /// that implements . + /// + ICollection Keys { get; } + + /// + /// Gets an containing the values in the + /// . + /// + /// + /// An containing the values in the object + /// that implements . + /// + ICollection Values { get; } + + /// + /// Determines whether the contains an element + /// with the specified key. + /// + /// + /// The key to locate in the . + /// + /// + /// true if the contains an element with + /// the key; otherwise, false. + /// + /// + /// key is null. + /// + bool ContainsKey(string key); + + /// + /// Gets the value associated with the specified key. + /// + /// + /// The key of the value to get. + /// + /// + /// The key of the value to get. + /// When this method returns, the value associated with the specified key, if the + /// key is found; otherwise, the default value for the type of the value parameter. + /// This parameter is passed uninitialized. + /// + /// + /// true if the object that implements contains + /// an element with the specified key; otherwise, false. + /// + /// + /// key is null. + /// + bool TryGetValue(string key, out StringValues value); + + /// + /// Gets the value with the specified key. + /// + /// + /// The key of the value to get. + /// + /// + /// The element with the specified key, or .Empty if the key is not present. + /// + /// + /// key is null. + /// + /// + /// has a different indexer contract than + /// , as it will return StringValues.Empty for missing entries + /// rather than throwing an Exception. + /// + StringValues this[string key] { get; } + + /// + /// The file collection sent with the request. + /// + /// + /// The files included with the request. + IFormFileCollection Files { get; } + } +} diff --git a/src/Microsoft.AspNet.Http.Features/IFormFile.cs b/src/Microsoft.AspNet.Http.Abstractions/IFormFile.cs similarity index 100% rename from src/Microsoft.AspNet.Http.Features/IFormFile.cs rename to src/Microsoft.AspNet.Http.Abstractions/IFormFile.cs diff --git a/src/Microsoft.AspNet.Http.Features/IFormFileCollection.cs b/src/Microsoft.AspNet.Http.Abstractions/IFormFileCollection.cs similarity index 100% rename from src/Microsoft.AspNet.Http.Features/IFormFileCollection.cs rename to src/Microsoft.AspNet.Http.Abstractions/IFormFileCollection.cs diff --git a/src/Microsoft.AspNet.Http.Abstractions/IQueryCollection.cs b/src/Microsoft.AspNet.Http.Abstractions/IQueryCollection.cs new file mode 100644 index 00000000..4bed287e --- /dev/null +++ b/src/Microsoft.AspNet.Http.Abstractions/IQueryCollection.cs @@ -0,0 +1,88 @@ +// 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 the HttpRequest query string collection + /// + public interface IQueryCollection : IEnumerable> + { + /// + /// Gets the number of elements contained in the . + /// + /// + /// The number of elements contained in the . + /// + int Count { get; } + + /// + /// Gets an containing the keys of the + /// . + /// + /// + /// An containing the keys of the object + /// that implements . + /// + ICollection Keys { get; } + + /// + /// Determines whether the contains an element + /// with the specified key. + /// + /// + /// The key to locate in the . + /// + /// + /// true if the contains an element with + /// the key; otherwise, false. + /// + /// + /// key is null. + /// + bool ContainsKey(string key); + + /// + /// Gets the value associated with the specified key. + /// + /// + /// The key of the value to get. + /// + /// + /// The key of the value to get. + /// When this method returns, the value associated with the specified key, if the + /// key is found; otherwise, the default value for the type of the value parameter. + /// This parameter is passed uninitialized. + /// + /// + /// true if the object that implements contains + /// an element with the specified key; otherwise, false. + /// + /// + /// key is null. + /// + bool TryGetValue(string key, out StringValues value); + + /// + /// Gets the value with the specified key. + /// + /// + /// The key of the value to get. + /// + /// + /// The element with the specified key, or .Empty if the key is not present. + /// + /// + /// key is null. + /// + /// + /// has a different indexer contract than + /// , as it will return StringValues.Empty for missing entries + /// rather than throwing an Exception. + /// + StringValues this[string key] { get; } + } +} diff --git a/src/Microsoft.AspNet.Http.Abstractions/IRequestCookieCollection.cs b/src/Microsoft.AspNet.Http.Abstractions/IRequestCookieCollection.cs new file mode 100644 index 00000000..ec58fb97 --- /dev/null +++ b/src/Microsoft.AspNet.Http.Abstractions/IRequestCookieCollection.cs @@ -0,0 +1,130 @@ +// 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 the HttpRequest cookie collection + /// + public interface IRequestCookieCollection : IEnumerable> + { + /// + /// Gets the number of elements contained in the . + /// + /// + /// The number of elements contained in the . + /// + int Count { get; } + + /// + /// Determines whether the contains a specific + /// value. + /// + /// + /// The object to locate in the . + /// + /// + /// true if item is found in the ; otherwise, + /// false. + /// + bool Contains(KeyValuePair item); + + /// + /// Copies the elements of the to an , + /// starting at a particular index. + /// + /// + /// The one-dimensional that is the destination of the elements copied + /// from . The must have zero-based + /// indexing. + /// + /// The zero-based index in array at which copying begins. + /// array is null. + /// arrayIndex is less than 0. + /// + /// The number of elements in the source + /// is greater than the available space from arrayIndex to the end of the destination + /// array. + /// + void CopyTo(KeyValuePair[] array, int arrayIndex); + + /// + /// Gets an containing the keys of the + /// . + /// + /// + /// An containing the keys of the object + /// that implements . + /// + ICollection Keys { get; } + + /// + /// Gets an containing the values in the + /// . + /// + /// + /// An containing the values in the object + /// that implements . + /// + ICollection Values { get; } + + /// + /// Determines whether the contains an element + /// with the specified key. + /// + /// + /// The key to locate in the . + /// + /// + /// true if the contains an element with + /// the key; otherwise, false. + /// + /// + /// key is null. + /// + bool ContainsKey(string key); + + /// + /// Gets the value associated with the specified key. + /// + /// + /// The key of the value to get. + /// + /// + /// The key of the value to get. + /// When this method returns, the value associated with the specified key, if the + /// key is found; otherwise, the default value for the type of the value parameter. + /// This parameter is passed uninitialized. + /// + /// + /// true if the object that implements contains + /// an element with the specified key; otherwise, false. + /// + /// + /// key is null. + /// + bool TryGetValue(string key, out StringValues value); + + /// + /// Gets the value with the specified key. + /// + /// + /// The key of the value to get. + /// + /// + /// The element with the specified key, or .Empty if the key is not present. + /// + /// + /// key is null. + /// + /// + /// has a different indexer contract than + /// , as it will return StringValues.Empty for missing entries + /// rather than throwing an Exception. + /// + StringValues this[string key] { get; } + } +} diff --git a/src/Microsoft.AspNet.Http.Abstractions/QueryString.cs b/src/Microsoft.AspNet.Http.Abstractions/QueryString.cs index f3ec5df0..088402b2 100644 --- a/src/Microsoft.AspNet.Http.Abstractions/QueryString.cs +++ b/src/Microsoft.AspNet.Http.Abstractions/QueryString.cs @@ -157,7 +157,7 @@ public static QueryString Create(IEnumerable> param /// /// /// The resulting QueryString - public static QueryString Create(IQueryCollection parameters) + public static QueryString Create(IEnumerable> parameters) { var builder = new StringBuilder(); bool first = true; diff --git a/src/Microsoft.AspNet.Http.Features/IFormCollection.cs b/src/Microsoft.AspNet.Http.Features/IFormCollection.cs deleted file mode 100644 index f0e98fc9..00000000 --- a/src/Microsoft.AspNet.Http.Features/IFormCollection.cs +++ /dev/null @@ -1,28 +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 the parsed form values sent with the HttpRequest. - /// - public interface IFormCollection : IDictionary - { - /// - /// IFormCollection 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; } - - /// - /// The file collection sent with the request. - /// - /// - /// The files included with the request. - IFormFileCollection Files { get; } - } -} diff --git a/src/Microsoft.AspNet.Http.Features/IQueryCollection.cs b/src/Microsoft.AspNet.Http.Features/IQueryCollection.cs deleted file mode 100644 index b4614578..00000000 --- a/src/Microsoft.AspNet.Http.Features/IQueryCollection.cs +++ /dev/null @@ -1,21 +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 the HttpRequest query string collection - /// - public interface IQueryCollection : IDictionary - { - /// - /// IQueryCollection 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/IRequestCookies.cs b/src/Microsoft.AspNet.Http.Features/IRequestCookies.cs deleted file mode 100644 index bb876ebd..00000000 --- a/src/Microsoft.AspNet.Http.Features/IRequestCookies.cs +++ /dev/null @@ -1,21 +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 the HttpRequest cookie collection - /// - public interface IRequestCookies : IDictionary - { - /// - /// IRequestCookies 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/DefaultHttpRequest.cs b/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs index 1aa0fec1..c39f01cb 100644 --- a/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs +++ b/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs @@ -161,7 +161,7 @@ public override IHeaderDictionary Headers get { return HttpRequestFeature.Headers; } } - public override IRequestCookies Cookies + public override IRequestCookieCollection Cookies { get { return RequestCookiesFeature.Cookies; } set { RequestCookiesFeature.Cookies = value; } diff --git a/src/Microsoft.AspNet.Http/Features/FormFeature.cs b/src/Microsoft.AspNet.Http/Features/FormFeature.cs index db78289d..0a5808b2 100644 --- a/src/Microsoft.AspNet.Http/Features/FormFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/FormFeature.cs @@ -82,7 +82,7 @@ public IFormCollection ReadForm() if (!HasFormContentType) { - throw new InvalidOperationException($"Incorrect Content-Type: {_request.ContentType}"); + throw new InvalidOperationException("Incorrect Content-Type: " + _request.ContentType); } // TODO: Avoid Sync-over-Async http://blogs.msdn.com/b/pfxteam/archive/2012/04/13/10293638.aspx @@ -113,7 +113,7 @@ private async Task InnerReadFormAsync(CancellationToken cancell { if (!HasFormContentType) { - throw new InvalidOperationException($"Incorrect Content-Type: {_request.ContentType}"); + throw new InvalidOperationException("Incorrect Content-Type: " + _request.ContentType); } cancellationToken.ThrowIfCancellationRequested(); @@ -131,7 +131,7 @@ private async Task InnerReadFormAsync(CancellationToken cancell if (HasApplicationFormContentType(contentType)) { var encoding = FilterEncoding(contentType.Encoding); - formFields = new FormCollection(await FormReader.ReadNullableFormAsync(_request.Body, encoding, cancellationToken)); + formFields = new FormCollection(await FormReader.ReadFormAsync(_request.Body, encoding, cancellationToken)); } else if (HasMultipartFormContentType(contentType)) { @@ -177,7 +177,7 @@ private async Task InnerReadFormAsync(CancellationToken cancell } else { - System.Diagnostics.Debug.Assert(false, $"Unrecognized content-disposition for this section: {section.ContentDisposition}"); + System.Diagnostics.Debug.Assert(false, "Unrecognized content-disposition for this section: " + section.ContentDisposition); } section = await multipartReader.ReadNextSectionAsync(cancellationToken); @@ -193,7 +193,18 @@ private async Task InnerReadFormAsync(CancellationToken cancell // Rewind so later readers don't have to. _request.Body.Seek(0, SeekOrigin.Begin); - Form = formFields ?? new FormCollection(null, files); + if (formFields != null) + { + Form = formFields; + } + else if (files != null) + { + Form = new FormCollection(null, files); + } + else + { + Form = FormCollection.Empty; + } return Form; } diff --git a/src/Microsoft.AspNet.Http/Features/IRequestCookiesFeature.cs b/src/Microsoft.AspNet.Http/Features/IRequestCookiesFeature.cs index d9cbc162..b015b2d8 100644 --- a/src/Microsoft.AspNet.Http/Features/IRequestCookiesFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/IRequestCookiesFeature.cs @@ -1,13 +1,10 @@ // 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.Features.Internal { public interface IRequestCookiesFeature { - IRequestCookies Cookies { get; set; } + IRequestCookieCollection Cookies { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http/Features/QueryFeature.cs b/src/Microsoft.AspNet.Http/Features/QueryFeature.cs index be476842..e17e6125 100644 --- a/src/Microsoft.AspNet.Http/Features/QueryFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/QueryFeature.cs @@ -59,7 +59,7 @@ public IQueryCollection Query { if (_parsedValues == null) { - _parsedValues = new QueryCollection(); + _parsedValues = QueryCollection.Empty; } return _parsedValues; } @@ -69,15 +69,15 @@ public IQueryCollection Query { _original = current; - var kva = QueryHelpers.ParseQuery(current); + var result = QueryHelpers.ParseNullableQuery(current); - if (!kva.HasValues) + if (result == null) { - _parsedValues = new QueryCollection(); + _parsedValues = QueryCollection.Empty; } else { - _parsedValues = new QueryCollection(kva.GetResults()); + _parsedValues = new QueryCollection(result); } } return _parsedValues; diff --git a/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs b/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs index a3f47ad8..d153c1a5 100644 --- a/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs @@ -17,9 +17,9 @@ public class RequestCookiesFeature : IRequestCookiesFeature, IFeatureCache private IHttpRequestFeature _request; private StringValues _original; - private IRequestCookies _parsedValues; + private IRequestCookieCollection _parsedValues; - public RequestCookiesFeature(IRequestCookies cookies) + public RequestCookiesFeature(IRequestCookieCollection cookies) { if (cookies == null) { @@ -53,7 +53,7 @@ private IHttpRequestFeature HttpRequestFeature get { return FeatureHelpers.GetAndCache(this, _features, ref _request); } } - public IRequestCookies Cookies + public IRequestCookieCollection Cookies { get { @@ -61,7 +61,7 @@ public IRequestCookies Cookies { if (_parsedValues == null) { - _parsedValues = new RequestCookies(); + _parsedValues = RequestCookieCollection.Empty; } return _parsedValues; } @@ -76,13 +76,7 @@ public IRequestCookies Cookies if (_parsedValues == null || !StringValues.Equals(_original, current)) { _original = current; - var collectionParser = _parsedValues as RequestCookies; - if (collectionParser == null) - { - collectionParser = new RequestCookies(); - _parsedValues = collectionParser; - } - collectionParser.Reparse(current.ToArray()); + _parsedValues = RequestCookieCollection.Reparse(current.ToArray()); } return _parsedValues; diff --git a/src/Microsoft.AspNet.Http/FormCollection.cs b/src/Microsoft.AspNet.Http/FormCollection.cs index b561f8f7..b7faf074 100644 --- a/src/Microsoft.AspNet.Http/FormCollection.cs +++ b/src/Microsoft.AspNet.Http/FormCollection.cs @@ -13,6 +13,7 @@ namespace Microsoft.AspNet.Http.Internal /// public sealed class FormCollection : IFormCollection { + public static readonly FormCollection Empty = new FormCollection(); #if !DNXCORE50 private static readonly string[] EmptyKeys = new string[0]; private static readonly StringValues[] EmptyValues = new StringValues[0]; @@ -25,6 +26,7 @@ public sealed class FormCollection : IFormCollection private static IFormFileCollection EmptyFiles = new FormFileCollection(); private IFormFileCollection _files; + private FormCollection() { // For static Empty @@ -69,44 +71,11 @@ public StringValues this[string key] } return StringValues.Empty; } - - set - { - if (key == null) - { - throw new ArgumentNullException(nameof(key)); - } - - if (StringValues.IsNullOrEmpty(value)) - { - if (Store == null) - { - return; - } - - Store.Remove(key); - } - else - { - if (Store == null) - { - Store = new Dictionary(1, StringComparer.OrdinalIgnoreCase); - } - - Store[key] = value; - } - } } - - /// - /// Throws KeyNotFoundException if the key is not present. - /// - /// The header name. - /// - StringValues IDictionary.this[string key] + + StringValues IFormCollection.this[string key] { - get { return Store[key]; } - set { this[key] = value; } + get { return this[key]; } } /// @@ -125,18 +94,6 @@ public int Count } } - /// - /// 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 ICollection Keys { get @@ -169,54 +126,6 @@ public ICollection Values } } - /// - /// Adds a new list of items to the collection. - /// - /// The item to add. - public void Add(KeyValuePair item) - { - if (item.Key == null) - { - throw new ArgumentNullException(nameof(item.Key)); - } - if (Store == null) - { - Store = new Dictionary(1, StringComparer.OrdinalIgnoreCase); - } - Store.Add(item.Key, item.Value); - } - - /// - /// Adds the given header and values to the collection. - /// - /// The header name. - /// The header values. - public void Add(string key, StringValues value) - { - if (key == null) - { - throw new ArgumentNullException(nameof(key)); - } - - if (Store == null) - { - Store = new Dictionary(1); - } - Store.Add(key, value); - } - - /// - /// Clears the entire list of objects. - /// - public void Clear() - { - if (Store == null) - { - return; - } - Store.Clear(); - } - /// /// Returns a value indicating whether the specified object occurs within this collection. /// @@ -267,41 +176,6 @@ public void CopyTo(KeyValuePair[] array, int arrayIndex) } } - /// - /// Removes the given item from the the collection. - /// - /// The item. - /// true if the specified object was removed from the collection; otherwise, false. - public bool Remove(KeyValuePair item) - { - if (Store == null) - { - return false; - } - - StringValues value; - - if (Store.TryGetValue(item.Key, out value) && StringValues.Equals(item.Value, value)) - { - return Store.Remove(item.Key); - } - return false; - } - - /// - /// Removes the given header from the collection. - /// - /// The header name. - /// true if the specified object was removed from the collection; otherwise, false. - public bool Remove(string key) - { - if (Store == null) - { - return false; - } - return Store.Remove(key); - } - /// /// Retrieves a value from the dictionary. /// diff --git a/src/Microsoft.AspNet.Http/HeaderDictionary.cs b/src/Microsoft.AspNet.Http/HeaderDictionary.cs index 184c194d..68e5c4df 100644 --- a/src/Microsoft.AspNet.Http/HeaderDictionary.cs +++ b/src/Microsoft.AspNet.Http/HeaderDictionary.cs @@ -167,7 +167,7 @@ public void Add(KeyValuePair item) { if (item.Key == null) { - throw new ArgumentNullException(nameof(item.Key)); + throw new ArgumentNullException("The key is null"); } if (Store == null) { diff --git a/src/Microsoft.AspNet.Http/QueryCollection.cs b/src/Microsoft.AspNet.Http/QueryCollection.cs index fbeb5779..09ca36ea 100644 --- a/src/Microsoft.AspNet.Http/QueryCollection.cs +++ b/src/Microsoft.AspNet.Http/QueryCollection.cs @@ -13,6 +13,7 @@ namespace Microsoft.AspNet.Http.Internal /// public sealed class QueryCollection : IQueryCollection { + public static readonly QueryCollection Empty = new QueryCollection(); #if !DNXCORE50 private static readonly string[] EmptyKeys = new string[0]; private static readonly StringValues[] EmptyValues = new StringValues[0]; @@ -64,44 +65,6 @@ public StringValues this[string key] } return StringValues.Empty; } - - set - { - if (key == null) - { - throw new ArgumentNullException(nameof(key)); - } - - if (StringValues.IsNullOrEmpty(value)) - { - if (Store == null) - { - return; - } - - Store.Remove(key); - } - else - { - if (Store == null) - { - Store = new Dictionary(1, StringComparer.OrdinalIgnoreCase); - } - - Store[key] = value; - } - } - } - - /// - /// Throws KeyNotFoundException if the key is not present. - /// - /// The header name. - /// - StringValues IDictionary.this[string key] - { - get { return Store[key]; } - set { this[key] = value; } } /// @@ -120,18 +83,6 @@ public int Count } } - /// - /// 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 ICollection Keys { get @@ -148,87 +99,6 @@ public ICollection Keys } } - public ICollection Values - { - get - { - if (Store == null) - { -#if DNXCORE50 - return Array.Empty(); -#else - return EmptyValues; -#endif - } - return Store.Values; - } - } - - /// - /// Adds a new list of items to the collection. - /// - /// The item to add. - public void Add(KeyValuePair item) - { - if (item.Key == null) - { - throw new ArgumentNullException(nameof(item.Key)); - } - if (Store == null) - { - Store = new Dictionary(1, StringComparer.OrdinalIgnoreCase); - } - Store.Add(item.Key, item.Value); - } - - /// - /// Adds the given header and values to the collection. - /// - /// The header name. - /// The header values. - public void Add(string key, StringValues value) - { - if (key == null) - { - throw new ArgumentNullException(nameof(key)); - } - - if (Store == null) - { - Store = new Dictionary(1); - } - Store.Add(key, value); - } - - /// - /// Clears the entire list of objects. - /// - public void Clear() - { - if (Store == null) - { - return; - } - Store.Clear(); - } - - /// - /// Returns a value indicating whether the specified object occurs within this collection. - /// - /// The item. - /// true if the specified object occurs within this collection; otherwise, false. - public bool Contains(KeyValuePair item) - { - StringValues value; - if (Store == null || - !Store.TryGetValue(item.Key, out value) || - !StringValues.Equals(value, item.Value)) - { - return false; - } - return true; - } - /// /// Determines whether the contains a specific key. /// @@ -243,60 +113,6 @@ public bool ContainsKey(string key) return Store.ContainsKey(key); } - /// - /// Copies the elements to a one-dimensional Array instance at the specified index. - /// - /// The one-dimensional Array that is the destination of the specified objects copied from the . - /// The zero-based index in at which copying begins. - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - if (Store == null) - { - return; - } - - foreach (var item in Store) - { - array[arrayIndex] = item; - arrayIndex++; - } - } - - /// - /// Removes the given item from the the collection. - /// - /// The item. - /// true if the specified object was removed from the collection; otherwise, false. - public bool Remove(KeyValuePair item) - { - if (Store == null) - { - return false; - } - - StringValues value; - - if (Store.TryGetValue(item.Key, out value) && StringValues.Equals(item.Value, value)) - { - return Store.Remove(item.Key); - } - return false; - } - - /// - /// Removes the given header from the collection. - /// - /// The header name. - /// true if the specified object was removed from the collection; otherwise, false. - public bool Remove(string key) - { - if (Store == null) - { - return false; - } - return Store.Remove(key); - } - /// /// Retrieves a value from the dictionary. /// diff --git a/src/Microsoft.AspNet.Http/ReferenceReadStream.cs b/src/Microsoft.AspNet.Http/ReferenceReadStream.cs index dc1f0287..2d6cfdac 100644 --- a/src/Microsoft.AspNet.Http/ReferenceReadStream.cs +++ b/src/Microsoft.AspNet.Http/ReferenceReadStream.cs @@ -61,7 +61,7 @@ public override long Position ThrowIfDisposed(); if (value < 0 || value > Length) { - throw new ArgumentOutOfRangeException(nameof(value), value, $"The Position must be within the length of the Stream: {Length.ToString()}"); + throw new ArgumentOutOfRangeException(nameof(value), value, "The Position must be within the length of the Stream: " + Length.ToString()); } VerifyPosition(); _position = value; diff --git a/src/Microsoft.AspNet.Http/RequestCookies.cs b/src/Microsoft.AspNet.Http/RequestCookieCollection.cs similarity index 67% rename from src/Microsoft.AspNet.Http/RequestCookies.cs rename to src/Microsoft.AspNet.Http/RequestCookieCollection.cs index 91d606f8..8958031a 100644 --- a/src/Microsoft.AspNet.Http/RequestCookies.cs +++ b/src/Microsoft.AspNet.Http/RequestCookieCollection.cs @@ -9,8 +9,10 @@ namespace Microsoft.AspNet.Http.Internal { - public sealed class RequestCookies : IRequestCookies + public sealed class RequestCookieCollection : IRequestCookieCollection { + public static readonly RequestCookieCollection Empty = new RequestCookieCollection(); + #if !DNXCORE50 private static readonly string[] EmptyKeys = new string[0]; private static readonly string[] EmptyValues = new string[0]; @@ -22,11 +24,16 @@ public sealed class RequestCookies : IRequestCookies private Dictionary Store { get; set; } - public RequestCookies() + public RequestCookieCollection() + { + } + + public RequestCookieCollection(Dictionary store) { + Store = store; } - public RequestCookies(int capacity) + public RequestCookieCollection(int capacity) { Store = new Dictionary(capacity, StringComparer.OrdinalIgnoreCase); } @@ -53,7 +60,7 @@ public string this[string key] return string.Empty; } - set + private set { if (Store == null) { @@ -63,36 +70,12 @@ public string this[string key] } } - StringValues IRequestCookies.this[string key] + StringValues IRequestCookieCollection.this[string key] { get { return new StringValues(this[key]); } - - set - { - this[key] = value.ToString(); - } - } - - StringValues IDictionary.this[string key] - { - get - { - if (key == null) - { - throw new ArgumentNullException(nameof(key)); - } - - if (Store == null) - { - throw new KeyNotFoundException(); - } - - return this[key]; - } - set { this[key] = value; } } /// @@ -107,20 +90,33 @@ public IList GetValues(string key) return TryGetValue(key, out value) ? new[] { value } : null; } - public void Reparse(IList values) + public static RequestCookieCollection Reparse(IList values) { - Clear(); + if (values.Count == 0) + { + return Empty; + } IList cookies; if (CookieHeaderValue.TryParseList(values, out cookies)) { - foreach (var cookie in cookies) + if (cookies.Count == 0) + { + return Empty; + } + + var store = new Dictionary(cookies.Count); + for (var i = 0; i < cookies.Count; i++) { + var cookie = cookies[i]; var name = Uri.UnescapeDataString(cookie.Name.Replace('+', ' ')); var value = Uri.UnescapeDataString(cookie.Value.Replace('+', ' ')); - this[name] = value; + store[name] = value; } + + return new RequestCookieCollection(store); } + return Empty; } public int Count @@ -135,14 +131,6 @@ public int Count } } - public bool IsReadOnly - { - get - { - return false; - } - } - public ICollection Keys { get @@ -175,43 +163,7 @@ public ICollection Values } } - public void Add(KeyValuePair item) - { - if (item.Key == null) - { - throw new ArgumentNullException(nameof(item.Key)); - } - if (Store == null) - { - Store = new Dictionary(1, StringComparer.OrdinalIgnoreCase); - } - Store.Add(item.Key, item.Value); - } - - public void Add(string key, string value) - { - if (key == null) - { - throw new ArgumentNullException(nameof(key)); - } - - if (StringValues.IsNullOrEmpty(value)) - { - if (Store.ContainsKey(key)) - { - throw new ArgumentException("An element with the same key already exists in the Collection"); - } - return; - } - - if (Store == null) - { - Store = new Dictionary(1); - } - Store.Add(key, value); - } - - public void Clear() + private void Clear() { if (Store == null) { @@ -255,31 +207,6 @@ public void CopyTo(KeyValuePair[] array, int arrayIndex) } } - public bool Remove(KeyValuePair item) - { - if (Store == null) - { - return false; - } - - string value; - - if (Store.TryGetValue(item.Key, out value) && item.Value == value) - { - return Store.Remove(item.Key); - } - return false; - } - - public bool Remove(string key) - { - if (Store == null) - { - return false; - } - return Store.Remove(key); - } - public bool TryGetValue(string key, out string value) { if (Store == null) @@ -290,37 +217,20 @@ public bool TryGetValue(string key, out string value) return Store.TryGetValue(key, out value); } - int ICollection>.Count => Count; + int IRequestCookieCollection.Count => Count; - bool ICollection>.IsReadOnly => IsReadOnly; + ICollection IRequestCookieCollection.Keys => Keys; - ICollection IDictionary.Keys => Keys; + ICollection IRequestCookieCollection.Values => (ICollection)Values; - ICollection IDictionary.Values => (ICollection)Values; - - void ICollection>.Add(KeyValuePair item) - { - Add(item.Key, item.Value.ToString()); - } - - void IDictionary.Add(string key, StringValues value) - { - Add(key, value.ToString()); - } - - void ICollection>.Clear() - { - Clear(); - } - - bool ICollection>.Contains(KeyValuePair item) + bool IRequestCookieCollection.Contains(KeyValuePair item) { return Contains(new KeyValuePair(item.Key, item.Value.ToString())); } - bool IDictionary.ContainsKey(string key) => ContainsKey(key); + bool IRequestCookieCollection.ContainsKey(string key) => ContainsKey(key); - void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + void IRequestCookieCollection.CopyTo(KeyValuePair[] array, int arrayIndex) { if (Store == null) @@ -335,25 +245,7 @@ void ICollection>.CopyTo(KeyValuePair>.Remove(KeyValuePair item) - { - if (Store == null) - { - return false; - } - - string value; - - if (Store.TryGetValue(item.Key, out value) && item.Value == value) - { - return Store.Remove(item.Key); - } - return false; - } - - bool IDictionary.Remove(string key) => Remove(key); - - bool IDictionary.TryGetValue(string key, out StringValues value) + bool IRequestCookieCollection.TryGetValue(string key, out StringValues value) { string val; if (!TryGetValue(key, out val)) diff --git a/src/Microsoft.AspNet.WebUtilities/BufferedReadStream.cs b/src/Microsoft.AspNet.WebUtilities/BufferedReadStream.cs index 26d8056b..ccb58d66 100644 --- a/src/Microsoft.AspNet.WebUtilities/BufferedReadStream.cs +++ b/src/Microsoft.AspNet.WebUtilities/BufferedReadStream.cs @@ -262,7 +262,7 @@ public bool EnsureBuffered(int minCount) { if (minCount > _buffer.Length) { - throw new ArgumentOutOfRangeException(nameof(minCount), minCount, $"The value must be smaller than the buffer size: {_buffer.Length.ToString()}"); + throw new ArgumentOutOfRangeException(nameof(minCount), minCount, "The value must be smaller than the buffer size: " + _buffer.Length.ToString()); } while (_bufferCount < minCount) { @@ -289,7 +289,7 @@ public async Task EnsureBufferedAsync(int minCount, CancellationToken canc { if (minCount > _buffer.Length) { - throw new ArgumentOutOfRangeException(nameof(minCount), minCount, $"The value must be smaller than the buffer size: {_buffer.Length.ToString()}"); + throw new ArgumentOutOfRangeException(nameof(minCount), minCount, "The value must be smaller than the buffer size: " + _buffer.Length.ToString()); } while (_bufferCount < minCount) { @@ -323,7 +323,7 @@ public string ReadLine(int lengthLimit) { if (builder.Length > lengthLimit) { - throw new InvalidOperationException($"Line length limit exceeded: {lengthLimit.ToString()}"); + throw new InvalidOperationException("Line length limit exceeded: " + lengthLimit.ToString()); } ProcessLineChar(builder, ref foundCR, ref foundCRLF); } @@ -343,7 +343,7 @@ public async Task ReadLineAsync(int lengthLimit, CancellationToken cance { if (builder.Length > lengthLimit) { - throw new InvalidOperationException($"Line length limit exceeded: {lengthLimit.ToString()}"); + throw new InvalidOperationException("Line length limit exceeded: " + lengthLimit.ToString()); } ProcessLineChar(builder, ref foundCR, ref foundCRLF); diff --git a/src/Microsoft.AspNet.WebUtilities/FormReader.cs b/src/Microsoft.AspNet.WebUtilities/FormReader.cs index 5f05ea11..554b86e7 100644 --- a/src/Microsoft.AspNet.WebUtilities/FormReader.cs +++ b/src/Microsoft.AspNet.WebUtilities/FormReader.cs @@ -196,23 +196,6 @@ public static IDictionary ReadForm(string text) /// The HTTP form body to parse. /// The collection containing the parsed HTTP form body. public static async Task> ReadFormAsync(Stream stream, Encoding encoding, CancellationToken cancellationToken = new CancellationToken()) - { - var result = await ReadNullableFormAsync(stream, encoding, cancellationToken); - - if (result == null) - { - return new Dictionary(); - } - - return result; - } - - /// - /// Parses an HTTP form body - internal use, returns null if no entires. - /// - /// The HTTP form body to parse. - /// The collection containing the parsed HTTP form body, null if there are no entries. - public static async Task> ReadNullableFormAsync(Stream stream, Encoding encoding, CancellationToken cancellationToken = new CancellationToken()) { var reader = new FormReader(stream, encoding); @@ -224,11 +207,6 @@ public static IDictionary ReadForm(string text) pair = await reader.ReadNextPairAsync(cancellationToken); } - if (!accumulator.HasValues) - { - return null; - } - return accumulator.GetResults(); } } diff --git a/src/Microsoft.AspNet.WebUtilities/MultipartReader.cs b/src/Microsoft.AspNet.WebUtilities/MultipartReader.cs index 9bfcd916..e9e0aba9 100644 --- a/src/Microsoft.AspNet.WebUtilities/MultipartReader.cs +++ b/src/Microsoft.AspNet.WebUtilities/MultipartReader.cs @@ -84,7 +84,7 @@ private async Task> ReadHeadersAsync(Cancellati totalSize += line.Length; if (totalSize > TotalHeaderSizeLimit) { - throw new InvalidOperationException($"Total header size limit exceeded: {TotalHeaderSizeLimit.ToString()}"); + throw new InvalidOperationException("Total header size limit exceeded: " + TotalHeaderSizeLimit.ToString()); } int splitIndex = line.IndexOf(':'); Debug.Assert(splitIndex > 0, $"Invalid header line: {line.ToString()}"); diff --git a/src/Microsoft.AspNet.WebUtilities/QueryHelpers.cs b/src/Microsoft.AspNet.WebUtilities/QueryHelpers.cs index c6b325ec..036ed557 100644 --- a/src/Microsoft.AspNet.WebUtilities/QueryHelpers.cs +++ b/src/Microsoft.AspNet.WebUtilities/QueryHelpers.cs @@ -107,13 +107,31 @@ private static string AddQueryString( /// /// The raw query string value, with or without the leading '?'. /// A collection of parsed keys and values. - public static KeyValueAccumulator ParseQuery(string queryString) + public static Dictionary ParseQuery(string queryString) + { + var result = ParseNullableQuery(queryString); + + if (result == null) + { + return new Dictionary(); + } + + return result; + } + + + /// + /// Parse a query string into its component key and value parts. + /// + /// The raw query string value, with or without the leading '?'. + /// A collection of parsed keys and values, null if there are no entries. + public static Dictionary ParseNullableQuery(string queryString) { var accumulator = new KeyValueAccumulator(); if (string.IsNullOrEmpty(queryString) || queryString == "?") { - return accumulator; + return null; } int scanIndex = 0; @@ -156,7 +174,12 @@ public static KeyValueAccumulator ParseQuery(string queryString) scanIndex = delimiterIndex + 1; } - return accumulator; + if (!accumulator.HasValues) + { + return null; + } + + return accumulator.GetResults(); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Http.Tests/DefaultHttpRequestTests.cs b/test/Microsoft.AspNet.Http.Tests/DefaultHttpRequestTests.cs index b7e705a5..c3e32bc9 100644 --- a/test/Microsoft.AspNet.Http.Tests/DefaultHttpRequestTests.cs +++ b/test/Microsoft.AspNet.Http.Tests/DefaultHttpRequestTests.cs @@ -170,19 +170,23 @@ public void Cookies_GetAndSet() var cookies0 = request.Cookies; Assert.Equal(0, cookies0.Count); - request.Headers["Cookie"] = new[] { "name0=value0", "name1=value1" }; + var newCookies = new[] { "name0=value0", "name1=value1" }; + request.Headers["Cookie"] = newCookies; + + cookies0 = RequestCookieCollection.Reparse(newCookies); var cookies1 = request.Cookies; - Assert.Same(cookies0, cookies1); + Assert.Equal(cookies0, cookies1); Assert.Equal(2, cookies1.Count); Assert.Equal("value0", cookies1["name0"]); Assert.Equal("value1", cookies1["name1"]); + Assert.Equal(newCookies, request.Headers["Cookie"]); - var cookies2 = new RequestCookies() + var cookies2 = new RequestCookieCollection(new Dictionary() { { "name2", "value2" } - }; + }); request.Cookies = cookies2; - Assert.Same(cookies2, request.Cookies); + Assert.Equal(cookies2, request.Cookies); Assert.Equal("value2", request.Cookies["name2"]); cookieHeaders = request.Headers["Cookie"]; Assert.Equal(new[] { "name2=value2" }, cookieHeaders); diff --git a/test/Microsoft.AspNet.WebUtilities.Tests/QueryHelpersTests.cs b/test/Microsoft.AspNet.WebUtilities.Tests/QueryHelpersTests.cs index e0303b35..35a68f1a 100644 --- a/test/Microsoft.AspNet.WebUtilities.Tests/QueryHelpersTests.cs +++ b/test/Microsoft.AspNet.WebUtilities.Tests/QueryHelpersTests.cs @@ -12,7 +12,7 @@ public class QueryHelperTests [Fact] public void ParseQueryWithUniqueKeysWorks() { - var collection = QueryHelpers.ParseQuery("?key1=value1&key2=value2").GetResults(); + var collection = QueryHelpers.ParseQuery("?key1=value1&key2=value2"); Assert.Equal(2, collection.Count); Assert.Equal("value1", collection["key1"].FirstOrDefault()); Assert.Equal("value2", collection["key2"].FirstOrDefault()); @@ -21,7 +21,7 @@ public void ParseQueryWithUniqueKeysWorks() [Fact] public void ParseQueryWithoutQuestionmarkWorks() { - var collection = QueryHelpers.ParseQuery("key1=value1&key2=value2").GetResults(); + var collection = QueryHelpers.ParseQuery("key1=value1&key2=value2"); Assert.Equal(2, collection.Count); Assert.Equal("value1", collection["key1"].FirstOrDefault()); Assert.Equal("value2", collection["key2"].FirstOrDefault()); @@ -30,7 +30,7 @@ public void ParseQueryWithoutQuestionmarkWorks() [Fact] public void ParseQueryWithDuplicateKeysGroups() { - var collection = QueryHelpers.ParseQuery("?key1=valueA&key2=valueB&key1=valueC").GetResults(); + var collection = QueryHelpers.ParseQuery("?key1=valueA&key2=valueB&key1=valueC"); Assert.Equal(2, collection.Count); Assert.Equal(new[] { "valueA", "valueC" }, collection["key1"]); Assert.Equal("valueB", collection["key2"].FirstOrDefault()); @@ -39,7 +39,7 @@ public void ParseQueryWithDuplicateKeysGroups() [Fact] public void ParseQueryWithEmptyValuesWorks() { - var collection = QueryHelpers.ParseQuery("?key1=&key2=").GetResults(); + var collection = QueryHelpers.ParseQuery("?key1=&key2="); Assert.Equal(2, collection.Count); Assert.Equal(string.Empty, collection["key1"].FirstOrDefault()); Assert.Equal(string.Empty, collection["key2"].FirstOrDefault()); @@ -48,7 +48,7 @@ public void ParseQueryWithEmptyValuesWorks() [Fact] public void ParseQueryWithEmptyKeyWorks() { - var collection = QueryHelpers.ParseQuery("?=value1&=").GetResults(); + var collection = QueryHelpers.ParseQuery("?=value1&="); Assert.Equal(1, collection.Count); Assert.Equal(new[] { "value1", "" }, collection[""]); } From c2514466e8316aac6ba34025b16f01dabb076d9d Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 30 Oct 2015 10:03:30 +0000 Subject: [PATCH 4/9] feedback changes --- .../Extensions/MapWhenExtensions.cs | 1 + .../HeadersExtensions.cs | 43 +++++++++++++++++++ .../RequestHeaders.cs | 30 +------------ .../ResponseHeaders.cs | 29 +------------ .../Features/FormFeature.cs | 2 +- src/Microsoft.AspNet.Http/FormCollection.cs | 16 +++---- src/Microsoft.AspNet.Http/HeaderDictionary.cs | 17 +++----- src/Microsoft.AspNet.Http/QueryCollection.cs | 11 +++-- .../RequestCookieCollection.cs | 16 +++---- .../HeaderDictionaryTypeExtensionsTest.cs | 9 ++-- 10 files changed, 73 insertions(+), 101 deletions(-) create mode 100644 src/Microsoft.AspNet.Http.Extensions/HeadersExtensions.cs diff --git a/src/Microsoft.AspNet.Http.Abstractions/Extensions/MapWhenExtensions.cs b/src/Microsoft.AspNet.Http.Abstractions/Extensions/MapWhenExtensions.cs index 8e615d89..7bd65846 100644 --- a/src/Microsoft.AspNet.Http.Abstractions/Extensions/MapWhenExtensions.cs +++ b/src/Microsoft.AspNet.Http.Abstractions/Extensions/MapWhenExtensions.cs @@ -5,6 +5,7 @@ using Microsoft.AspNet.Http; using Microsoft.AspNet.Builder.Extensions; + namespace Microsoft.AspNet.Builder { using Predicate = Func; diff --git a/src/Microsoft.AspNet.Http.Extensions/HeadersExtensions.cs b/src/Microsoft.AspNet.Http.Extensions/HeadersExtensions.cs new file mode 100644 index 00000000..baf5602f --- /dev/null +++ b/src/Microsoft.AspNet.Http.Extensions/HeadersExtensions.cs @@ -0,0 +1,43 @@ +// 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 Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNet.Http.Extensions +{ + public static class HeadersExtensions + { + public static void AppendList(this IHeaderDictionary Headers, string name, IList values) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + switch (values.Count) + { + case 0: + Headers.Append(name, StringValues.Empty); + break; + case 1: + Headers.Append(name, new StringValues(values[0].ToString())); + break; + default: + var newValues = new string[values.Count]; + for (var i = 0; i < values.Count; i++) + { + newValues[i] = values[i].ToString(); + } + Headers.Append(name, new StringValues(newValues)); + break; + } + } + } +} diff --git a/src/Microsoft.AspNet.Http.Extensions/RequestHeaders.cs b/src/Microsoft.AspNet.Http.Extensions/RequestHeaders.cs index 74873c92..800c0def 100644 --- a/src/Microsoft.AspNet.Http.Extensions/RequestHeaders.cs +++ b/src/Microsoft.AspNet.Http.Extensions/RequestHeaders.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; -using Microsoft.Extensions.Primitives; +using Microsoft.AspNet.Http.Extensions; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Http.Headers @@ -309,33 +309,7 @@ public void Append(string name, object value) public void AppendList(string name, IList values) { - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } - - if (values == null) - { - throw new ArgumentNullException(nameof(values)); - } - - switch (values.Count) - { - case 0: - Headers.Append(name, StringValues.Empty); - break; - case 1: - Headers.Append(name, new StringValues(values[0].ToString())); - break; - default: - var newValues = new string[values.Count]; - for (var i = 0; i < values.Count; i++) - { - newValues[i] = values[i].ToString(); - } - Headers.Append(name, new StringValues(newValues)); - break; - } + Headers.AppendList(name, values); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http.Extensions/ResponseHeaders.cs b/src/Microsoft.AspNet.Http.Extensions/ResponseHeaders.cs index d37bdcd3..baad250f 100644 --- a/src/Microsoft.AspNet.Http.Extensions/ResponseHeaders.cs +++ b/src/Microsoft.AspNet.Http.Extensions/ResponseHeaders.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using Microsoft.AspNet.Http.Extensions; -using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Http.Headers @@ -206,33 +205,7 @@ public void Append(string name, object value) public void AppendList(string name, IList values) { - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } - - if (values == null) - { - throw new ArgumentNullException(nameof(values)); - } - - switch (values.Count) - { - case 0: - Headers.Append(name, StringValues.Empty); - break; - case 1: - Headers.Append(name, new StringValues(values[0].ToString())); - break; - default: - var newValues = new string[values.Count]; - for (var i = 0; i < values.Count; i++) - { - newValues[i] = values[i].ToString(); - } - Headers.Append(name, new StringValues(newValues)); - break; - } + Headers.AppendList(name, values); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http/Features/FormFeature.cs b/src/Microsoft.AspNet.Http/Features/FormFeature.cs index 0a5808b2..f3ed68e3 100644 --- a/src/Microsoft.AspNet.Http/Features/FormFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/FormFeature.cs @@ -85,7 +85,7 @@ public IFormCollection ReadForm() throw new InvalidOperationException("Incorrect Content-Type: " + _request.ContentType); } - // TODO: Avoid Sync-over-Async http://blogs.msdn.com/b/pfxteam/archive/2012/04/13/10293638.aspx + // TODO: Issue #456 Avoid Sync-over-Async http://blogs.msdn.com/b/pfxteam/archive/2012/04/13/10293638.aspx // TODO: How do we prevent thread exhaustion? return ReadFormAsync().GetAwaiter().GetResult(); } diff --git a/src/Microsoft.AspNet.Http/FormCollection.cs b/src/Microsoft.AspNet.Http/FormCollection.cs index b7faf074..ddb4cf95 100644 --- a/src/Microsoft.AspNet.Http/FormCollection.cs +++ b/src/Microsoft.AspNet.Http/FormCollection.cs @@ -1,7 +1,6 @@ // 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; using System.Collections.Generic; using Microsoft.Extensions.Primitives; @@ -11,10 +10,13 @@ namespace Microsoft.AspNet.Http.Internal /// /// Contains the parsed form values. /// - public sealed class FormCollection : IFormCollection + public class FormCollection : IFormCollection { public static readonly FormCollection Empty = new FormCollection(); -#if !DNXCORE50 +#if DNXCORE50 + private static readonly string[] EmptyKeys = Array.Empty(); + private static readonly StringValues[] EmptyValues = Array.Empty(); +#else private static readonly string[] EmptyKeys = new string[0]; private static readonly StringValues[] EmptyValues = new StringValues[0]; #endif @@ -100,11 +102,7 @@ public ICollection Keys { if (Store == null) { -#if DNXCORE50 - return Array.Empty(); -#else return EmptyKeys; -#endif } return Store.Keys; } @@ -116,11 +114,7 @@ public ICollection Values { if (Store == null) { -#if DNXCORE50 - return Array.Empty(); -#else return EmptyValues; -#endif } return Store.Values; } diff --git a/src/Microsoft.AspNet.Http/HeaderDictionary.cs b/src/Microsoft.AspNet.Http/HeaderDictionary.cs index 68e5c4df..f1fde19a 100644 --- a/src/Microsoft.AspNet.Http/HeaderDictionary.cs +++ b/src/Microsoft.AspNet.Http/HeaderDictionary.cs @@ -11,9 +11,12 @@ namespace Microsoft.AspNet.Http.Internal /// /// Represents a wrapper for RequestHeaders and ResponseHeaders. /// - public sealed class HeaderDictionary : IHeaderDictionary + public class HeaderDictionary : IHeaderDictionary { -#if !DNXCORE50 +#if DNXCORE50 + private static readonly string[] EmptyKeys = Array.Empty(); + private static readonly StringValues[] EmptyValues = Array.Empty(); +#else private static readonly string[] EmptyKeys = new string[0]; private static readonly StringValues[] EmptyValues = new StringValues[0]; #endif @@ -133,11 +136,7 @@ public ICollection Keys { if (Store == null) { -#if DNXCORE50 - return Array.Empty(); -#else return EmptyKeys; -#endif } return Store.Keys; } @@ -149,11 +148,7 @@ public ICollection Values { if (Store == null) { -#if DNXCORE50 - return Array.Empty(); -#else return EmptyValues; -#endif } return Store.Values; } @@ -167,7 +162,7 @@ public void Add(KeyValuePair item) { if (item.Key == null) { - throw new ArgumentNullException("The key is null"); + throw new ArgumentNullException("The key is null"); } if (Store == null) { diff --git a/src/Microsoft.AspNet.Http/QueryCollection.cs b/src/Microsoft.AspNet.Http/QueryCollection.cs index 09ca36ea..2b9168d5 100644 --- a/src/Microsoft.AspNet.Http/QueryCollection.cs +++ b/src/Microsoft.AspNet.Http/QueryCollection.cs @@ -11,10 +11,13 @@ namespace Microsoft.AspNet.Http.Internal /// /// The HttpRequest query string collection /// - public sealed class QueryCollection : IQueryCollection + public class QueryCollection : IQueryCollection { public static readonly QueryCollection Empty = new QueryCollection(); -#if !DNXCORE50 +#if DNXCORE50 + private static readonly string[] EmptyKeys = Array.Empty(); + private static readonly StringValues[] EmptyValues = Array.Empty(); +#else private static readonly string[] EmptyKeys = new string[0]; private static readonly StringValues[] EmptyValues = new StringValues[0]; #endif @@ -89,11 +92,7 @@ public ICollection Keys { if (Store == null) { -#if DNXCORE50 - return Array.Empty(); -#else return EmptyKeys; -#endif } return Store.Keys; } diff --git a/src/Microsoft.AspNet.Http/RequestCookieCollection.cs b/src/Microsoft.AspNet.Http/RequestCookieCollection.cs index 8958031a..b643acd6 100644 --- a/src/Microsoft.AspNet.Http/RequestCookieCollection.cs +++ b/src/Microsoft.AspNet.Http/RequestCookieCollection.cs @@ -9,11 +9,13 @@ namespace Microsoft.AspNet.Http.Internal { - public sealed class RequestCookieCollection : IRequestCookieCollection + public class RequestCookieCollection : IRequestCookieCollection { public static readonly RequestCookieCollection Empty = new RequestCookieCollection(); - -#if !DNXCORE50 +#if DNXCORE50 + private static readonly string[] EmptyKeys = Array.Empty(); + private static readonly StringValues[] EmptyValues = Array.Empty(); +#else private static readonly string[] EmptyKeys = new string[0]; private static readonly string[] EmptyValues = new string[0]; #endif @@ -137,11 +139,7 @@ public ICollection Keys { if (Store == null) { -#if DNXCORE50 - return Array.Empty(); -#else return EmptyKeys; -#endif } return Store.Keys; } @@ -153,11 +151,7 @@ public ICollection Values { if (Store == null) { -#if DNXCORE50 - return Array.Empty(); -#else return EmptyValues; -#endif } return Store.Values; } diff --git a/test/Microsoft.AspNet.Http.Extensions.Tests/HeaderDictionaryTypeExtensionsTest.cs b/test/Microsoft.AspNet.Http.Extensions.Tests/HeaderDictionaryTypeExtensionsTest.cs index 4a8ee927..fb2ef452 100644 --- a/test/Microsoft.AspNet.Http.Extensions.Tests/HeaderDictionaryTypeExtensionsTest.cs +++ b/test/Microsoft.AspNet.Http.Extensions.Tests/HeaderDictionaryTypeExtensionsTest.cs @@ -103,11 +103,10 @@ public void GetListT_KnownTypeWithValidValue_Success() public void GetListT_KnownTypeWithMissingValue_Null() { var context = new DefaultHttpContext(); - var result1 = context.Request.GetTypedHeaders().GetList(HeaderNames.Accept); - var result2 = context.Request.GetTypedHeaders().GetList(HeaderNames.Accept); - - Assert.Null(result1); - Assert.Null(result2); + + var result = context.Request.GetTypedHeaders().GetList(HeaderNames.Accept); + + Assert.Null(result); } [Fact] From 046715bcc6a751ac610e777fc214e1c61c4bf03c Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 30 Oct 2015 13:39:22 +0000 Subject: [PATCH 5/9] cr feedback - slim interfaces --- .../IFormCollection.cs | 44 +--------- .../IRequestCookieCollection.cs | 46 +--------- src/Microsoft.AspNet.Http/FormCollection.cs | 53 ------------ .../RequestCookieCollection.cs | 84 +------------------ 4 files changed, 4 insertions(+), 223 deletions(-) diff --git a/src/Microsoft.AspNet.Http.Abstractions/IFormCollection.cs b/src/Microsoft.AspNet.Http.Abstractions/IFormCollection.cs index cfad94a0..03e81168 100644 --- a/src/Microsoft.AspNet.Http.Abstractions/IFormCollection.cs +++ b/src/Microsoft.AspNet.Http.Abstractions/IFormCollection.cs @@ -18,39 +18,7 @@ public interface IFormCollection : IEnumerable. /// int Count { get; } - - /// - /// Determines whether the contains a specific - /// value. - /// - /// - /// The object to locate in the . - /// - /// - /// true if item is found in the ; otherwise, - /// false. - /// - bool Contains(KeyValuePair item); - - /// - /// Copies the elements of the to an , - /// starting at a particular index. - /// - /// - /// The one-dimensional that is the destination of the elements copied - /// from . The must have zero-based - /// indexing. - /// - /// The zero-based index in array at which copying begins. - /// array is null. - /// arrayIndex is less than 0. - /// - /// The number of elements in the source - /// is greater than the available space from arrayIndex to the end of the destination - /// array. - /// - void CopyTo(KeyValuePair[] array, int arrayIndex); - + /// /// Gets an containing the keys of the /// . @@ -61,16 +29,6 @@ public interface IFormCollection : IEnumerable ICollection Keys { get; } - /// - /// Gets an containing the values in the - /// . - /// - /// - /// An containing the values in the object - /// that implements . - /// - ICollection Values { get; } - /// /// Determines whether the contains an element /// with the specified key. diff --git a/src/Microsoft.AspNet.Http.Abstractions/IRequestCookieCollection.cs b/src/Microsoft.AspNet.Http.Abstractions/IRequestCookieCollection.cs index ec58fb97..eb9f5ef0 100644 --- a/src/Microsoft.AspNet.Http.Abstractions/IRequestCookieCollection.cs +++ b/src/Microsoft.AspNet.Http.Abstractions/IRequestCookieCollection.cs @@ -18,39 +18,7 @@ public interface IRequestCookieCollection : IEnumerable. /// int Count { get; } - - /// - /// Determines whether the contains a specific - /// value. - /// - /// - /// The object to locate in the . - /// - /// - /// true if item is found in the ; otherwise, - /// false. - /// - bool Contains(KeyValuePair item); - - /// - /// Copies the elements of the to an , - /// starting at a particular index. - /// - /// - /// The one-dimensional that is the destination of the elements copied - /// from . The must have zero-based - /// indexing. - /// - /// The zero-based index in array at which copying begins. - /// array is null. - /// arrayIndex is less than 0. - /// - /// The number of elements in the source - /// is greater than the available space from arrayIndex to the end of the destination - /// array. - /// - void CopyTo(KeyValuePair[] array, int arrayIndex); - + /// /// Gets an containing the keys of the /// . @@ -60,17 +28,7 @@ public interface IRequestCookieCollection : IEnumerable. /// ICollection Keys { get; } - - /// - /// Gets an containing the values in the - /// . - /// - /// - /// An containing the values in the object - /// that implements . - /// - ICollection Values { get; } - + /// /// Determines whether the contains an element /// with the specified key. diff --git a/src/Microsoft.AspNet.Http/FormCollection.cs b/src/Microsoft.AspNet.Http/FormCollection.cs index ddb4cf95..7a7d67c2 100644 --- a/src/Microsoft.AspNet.Http/FormCollection.cs +++ b/src/Microsoft.AspNet.Http/FormCollection.cs @@ -74,11 +74,6 @@ public StringValues this[string key] return StringValues.Empty; } } - - StringValues IFormCollection.this[string key] - { - get { return this[key]; } - } /// /// Gets the number of elements contained in the ;. @@ -108,35 +103,6 @@ public ICollection Keys } } - public ICollection Values - { - get - { - if (Store == null) - { - return EmptyValues; - } - return Store.Values; - } - } - - /// - /// Returns a value indicating whether the specified object occurs within this collection. - /// - /// The item. - /// true if the specified object occurs within this collection; otherwise, false. - public bool Contains(KeyValuePair item) - { - StringValues value; - if (Store == null || - !Store.TryGetValue(item.Key, out value) || - !StringValues.Equals(value, item.Value)) - { - return false; - } - return true; - } - /// /// Determines whether the contains a specific key. /// @@ -151,25 +117,6 @@ public bool ContainsKey(string key) return Store.ContainsKey(key); } - /// - /// Copies the elements to a one-dimensional Array instance at the specified index. - /// - /// The one-dimensional Array that is the destination of the specified objects copied from the . - /// The zero-based index in at which copying begins. - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - if (Store == null) - { - return; - } - - foreach (var item in Store) - { - array[arrayIndex] = item; - arrayIndex++; - } - } - /// /// Retrieves a value from the dictionary. /// diff --git a/src/Microsoft.AspNet.Http/RequestCookieCollection.cs b/src/Microsoft.AspNet.Http/RequestCookieCollection.cs index b643acd6..0ec3053d 100644 --- a/src/Microsoft.AspNet.Http/RequestCookieCollection.cs +++ b/src/Microsoft.AspNet.Http/RequestCookieCollection.cs @@ -61,15 +61,6 @@ public string this[string key] } return string.Empty; } - - private set - { - if (Store == null) - { - Store = new Dictionary(1, StringComparer.OrdinalIgnoreCase); - } - Store[key] = value; - } } StringValues IRequestCookieCollection.this[string key] @@ -145,39 +136,6 @@ public ICollection Keys } } - public ICollection Values - { - get - { - if (Store == null) - { - return EmptyValues; - } - return Store.Values; - } - } - - private void Clear() - { - if (Store == null) - { - return; - } - Store.Clear(); - } - - public bool Contains(KeyValuePair item) - { - string value; - if (Store == null || - !Store.TryGetValue(item.Key, out value) || - !value.Equals(item.Value)) - { - return false; - } - return true; - } - public bool ContainsKey(string key) { if (Store == null) @@ -187,20 +145,6 @@ public bool ContainsKey(string key) return Store.ContainsKey(key); } - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - if (Store == null) - { - return; - } - - foreach (var item in Store) - { - array[arrayIndex] = item; - arrayIndex++; - } - } - public bool TryGetValue(string key, out string value) { if (Store == null) @@ -210,35 +154,9 @@ public bool TryGetValue(string key, out string value) } return Store.TryGetValue(key, out value); } - - int IRequestCookieCollection.Count => Count; - - ICollection IRequestCookieCollection.Keys => Keys; - - ICollection IRequestCookieCollection.Values => (ICollection)Values; - - bool IRequestCookieCollection.Contains(KeyValuePair item) - { - return Contains(new KeyValuePair(item.Key, item.Value.ToString())); - } bool IRequestCookieCollection.ContainsKey(string key) => ContainsKey(key); - void IRequestCookieCollection.CopyTo(KeyValuePair[] array, int arrayIndex) - { - - if (Store == null) - { - return; - } - - foreach (var item in Store) - { - array[arrayIndex] = new KeyValuePair(item.Key, item.Value); - arrayIndex++; - } - } - bool IRequestCookieCollection.TryGetValue(string key, out StringValues value) { string val; @@ -246,7 +164,7 @@ bool IRequestCookieCollection.TryGetValue(string key, out StringValues value) { return false; } - value = val; + value = new StringValues(val); return true; } From cd739bc93a07d0f0015ab0e4910070cefbf4584c Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 30 Oct 2015 13:45:50 +0000 Subject: [PATCH 6/9] remove irrelevant interface implement --- src/Microsoft.AspNet.Http/RequestCookieCollection.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Microsoft.AspNet.Http/RequestCookieCollection.cs b/src/Microsoft.AspNet.Http/RequestCookieCollection.cs index 0ec3053d..66594840 100644 --- a/src/Microsoft.AspNet.Http/RequestCookieCollection.cs +++ b/src/Microsoft.AspNet.Http/RequestCookieCollection.cs @@ -155,8 +155,6 @@ public bool TryGetValue(string key, out string value) return Store.TryGetValue(key, out value); } - bool IRequestCookieCollection.ContainsKey(string key) => ContainsKey(key); - bool IRequestCookieCollection.TryGetValue(string key, out StringValues value) { string val; From a0caefd4bece68008cec5886d47df1dc8347c334 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 31 Oct 2015 01:26:09 +0000 Subject: [PATCH 7/9] Move extension to existing Extenstions class --- .../HeaderDictionaryTypeExtensions.cs | 31 +++++++++++++ .../HeadersExtensions.cs | 43 ------------------- 2 files changed, 31 insertions(+), 43 deletions(-) delete mode 100644 src/Microsoft.AspNet.Http.Extensions/HeadersExtensions.cs diff --git a/src/Microsoft.AspNet.Http.Extensions/HeaderDictionaryTypeExtensions.cs b/src/Microsoft.AspNet.Http.Extensions/HeaderDictionaryTypeExtensions.cs index 19029b32..9486a522 100644 --- a/src/Microsoft.AspNet.Http.Extensions/HeaderDictionaryTypeExtensions.cs +++ b/src/Microsoft.AspNet.Http.Extensions/HeaderDictionaryTypeExtensions.cs @@ -101,6 +101,37 @@ internal static void SetList(this IHeaderDictionary headers, string name, ILi } } + public static void AppendList(this IHeaderDictionary Headers, string name, IList values) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + switch (values.Count) + { + case 0: + Headers.Append(name, StringValues.Empty); + break; + case 1: + Headers.Append(name, new StringValues(values[0].ToString())); + break; + default: + var newValues = new string[values.Count]; + for (var i = 0; i < values.Count; i++) + { + newValues[i] = values[i].ToString(); + } + Headers.Append(name, new StringValues(newValues)); + break; + } + } + internal static void SetDate(this IHeaderDictionary headers, string name, DateTimeOffset? value) { if (headers == null) diff --git a/src/Microsoft.AspNet.Http.Extensions/HeadersExtensions.cs b/src/Microsoft.AspNet.Http.Extensions/HeadersExtensions.cs deleted file mode 100644 index baf5602f..00000000 --- a/src/Microsoft.AspNet.Http.Extensions/HeadersExtensions.cs +++ /dev/null @@ -1,43 +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; -using System.Collections.Generic; -using Microsoft.Extensions.Primitives; - -namespace Microsoft.AspNet.Http.Extensions -{ - public static class HeadersExtensions - { - public static void AppendList(this IHeaderDictionary Headers, string name, IList values) - { - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } - - if (values == null) - { - throw new ArgumentNullException(nameof(values)); - } - - switch (values.Count) - { - case 0: - Headers.Append(name, StringValues.Empty); - break; - case 1: - Headers.Append(name, new StringValues(values[0].ToString())); - break; - default: - var newValues = new string[values.Count]; - for (var i = 0; i < values.Count; i++) - { - newValues[i] = values[i].ToString(); - } - Headers.Append(name, new StringValues(newValues)); - break; - } - } - } -} From 90b8f4f69f4df63aeca60133f73dfcb6b673ac49 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Mon, 2 Nov 2015 17:55:39 +0000 Subject: [PATCH 8/9] cr feedback --- .../HeaderDictionaryTypeExtensions.cs | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.AspNet.Http.Extensions/HeaderDictionaryTypeExtensions.cs b/src/Microsoft.AspNet.Http.Extensions/HeaderDictionaryTypeExtensions.cs index 9486a522..c9ac784d 100644 --- a/src/Microsoft.AspNet.Http.Extensions/HeaderDictionaryTypeExtensions.cs +++ b/src/Microsoft.AspNet.Http.Extensions/HeaderDictionaryTypeExtensions.cs @@ -78,26 +78,18 @@ internal static void SetList(this IHeaderDictionary headers, string name, ILi { headers.Remove(name); } + else if (values.Count == 1) + { + headers[name] = new StringValues(values[0].ToString()); + } else { - - switch (values.Count) + var newValues = new string[values.Count]; + for (var i = 0; i < values.Count; i++) { - case 0: - headers.Remove(name); - break; - case 1: - headers[name] = new StringValues(values[0].ToString()); - break; - default: - var newValues = new string[values.Count]; - for (var i = 0; i < values.Count; i++) - { - newValues[i] = values[i].ToString(); - } - headers[name] = new StringValues(newValues); - break; + newValues[i] = values[i].ToString(); } + headers[name] = new StringValues(newValues); } } From 003152dbd84c3b11ec7c7eefd3758db2fd1c37a5 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Mon, 2 Nov 2015 21:02:31 +0000 Subject: [PATCH 9/9] Simplify Cookies Interface --- .../IRequestCookieCollection.cs | 11 ++-- .../Features/RequestCookiesFeature.cs | 11 ++-- .../RequestCookieCollection.cs | 50 +++---------------- .../DefaultHttpRequestTests.cs | 2 +- 4 files changed, 18 insertions(+), 56 deletions(-) diff --git a/src/Microsoft.AspNet.Http.Abstractions/IRequestCookieCollection.cs b/src/Microsoft.AspNet.Http.Abstractions/IRequestCookieCollection.cs index eb9f5ef0..5cc028b1 100644 --- a/src/Microsoft.AspNet.Http.Abstractions/IRequestCookieCollection.cs +++ b/src/Microsoft.AspNet.Http.Abstractions/IRequestCookieCollection.cs @@ -2,14 +2,13 @@ // 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 the HttpRequest cookie collection /// - public interface IRequestCookieCollection : IEnumerable> + public interface IRequestCookieCollection : IEnumerable> { /// /// Gets the number of elements contained in the . @@ -64,7 +63,7 @@ public interface IRequestCookieCollection : IEnumerable /// key is null. /// - bool TryGetValue(string key, out StringValues value); + bool TryGetValue(string key, out string value); /// /// Gets the value with the specified key. @@ -73,16 +72,16 @@ public interface IRequestCookieCollection : IEnumerable /// - /// The element with the specified key, or .Empty if the key is not present. + /// The element with the specified key, or .Empty if the key is not present. /// /// /// key is null. /// /// /// has a different indexer contract than - /// , as it will return StringValues.Empty for missing entries + /// , as it will return String.Empty for missing entries /// rather than throwing an Exception. /// - StringValues this[string key] { get; } + string this[string key] { get; } } } diff --git a/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs b/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs index d153c1a5..e759f1d2 100644 --- a/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs @@ -70,13 +70,13 @@ public IRequestCookieCollection Cookies StringValues current; if (!headers.TryGetValue(HeaderNames.Cookie, out current)) { - current = StringValues.Empty; + current = string.Empty; } - if (_parsedValues == null || !StringValues.Equals(_original, current)) + if (_parsedValues == null || _original != current) { _original = current; - _parsedValues = RequestCookieCollection.Reparse(current.ToArray()); + _parsedValues = RequestCookieCollection.Parse(current.ToArray()); } return _parsedValues; @@ -96,10 +96,7 @@ public IRequestCookieCollection Cookies var headers = new List(); foreach (var pair in _parsedValues) { - foreach (var cookieValue in pair.Value) - { - headers.Add(new CookieHeaderValue(pair.Key, cookieValue).ToString()); - } + headers.Add(new CookieHeaderValue(pair.Key, pair.Value).ToString()); } _original = headers.ToArray(); HttpRequestFeature.Headers[HeaderNames.Cookie] = _original; diff --git a/src/Microsoft.AspNet.Http/RequestCookieCollection.cs b/src/Microsoft.AspNet.Http/RequestCookieCollection.cs index 66594840..363b69df 100644 --- a/src/Microsoft.AspNet.Http/RequestCookieCollection.cs +++ b/src/Microsoft.AspNet.Http/RequestCookieCollection.cs @@ -4,7 +4,6 @@ using System; using System.Collections; using System.Collections.Generic; -using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Http.Internal @@ -14,14 +13,12 @@ public class RequestCookieCollection : IRequestCookieCollection public static readonly RequestCookieCollection Empty = new RequestCookieCollection(); #if DNXCORE50 private static readonly string[] EmptyKeys = Array.Empty(); - private static readonly StringValues[] EmptyValues = Array.Empty(); #else private static readonly string[] EmptyKeys = new string[0]; - private static readonly string[] EmptyValues = new string[0]; #endif private static readonly Enumerator EmptyEnumerator = new Enumerator(); // Pre-box - private static readonly IEnumerator> EmptyIEnumeratorType = EmptyEnumerator; + private static readonly IEnumerator> EmptyIEnumeratorType = EmptyEnumerator; private static readonly IEnumerator EmptyIEnumerator = EmptyEnumerator; private Dictionary Store { get; set; } @@ -62,28 +59,8 @@ public string this[string key] return string.Empty; } } - - StringValues IRequestCookieCollection.this[string key] - { - get - { - return new StringValues(this[key]); - } - } - - /// - /// Get the associated values from the collection in their original format. - /// Returns null if the key is not present. - /// - /// - /// - public IList GetValues(string key) - { - string value; - return TryGetValue(key, out value) ? new[] { value } : null; - } - public static RequestCookieCollection Reparse(IList values) + public static RequestCookieCollection Parse(IList values) { if (values.Count == 0) { @@ -149,23 +126,12 @@ public bool TryGetValue(string key, out string value) { if (Store == null) { - value = default(string); + value = string.Empty; return false; } return Store.TryGetValue(key, out value); } - bool IRequestCookieCollection.TryGetValue(string key, out StringValues value) - { - string val; - if (!TryGetValue(key, out val)) - { - return false; - } - value = new StringValues(val); - return true; - } - /// /// Returns an struct enumerator that iterates through a collection without boxing. /// @@ -185,7 +151,7 @@ public Enumerator GetEnumerator() /// Returns an enumerator that iterates through a collection, boxes in non-empty path. /// /// An object that can be used to iterate through the collection. - IEnumerator> IEnumerable>.GetEnumerator() + IEnumerator> IEnumerable>.GetEnumerator() { if (Store == null || Store.Count == 0) { @@ -211,7 +177,7 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } - public struct Enumerator : IEnumerator> + public struct Enumerator : IEnumerator> { // Do NOT make this readonly, or MoveNext will not work private Dictionary.Enumerator _dictionaryEnumerator; @@ -232,16 +198,16 @@ public bool MoveNext() return false; } - public KeyValuePair Current + public KeyValuePair Current { get { if (_notEmpty) { var current = _dictionaryEnumerator.Current; - return new KeyValuePair(current.Key, new StringValues(current.Value)); + return new KeyValuePair(current.Key, current.Value); } - return default(KeyValuePair); + return default(KeyValuePair); } } diff --git a/test/Microsoft.AspNet.Http.Tests/DefaultHttpRequestTests.cs b/test/Microsoft.AspNet.Http.Tests/DefaultHttpRequestTests.cs index c3e32bc9..586b8ca1 100644 --- a/test/Microsoft.AspNet.Http.Tests/DefaultHttpRequestTests.cs +++ b/test/Microsoft.AspNet.Http.Tests/DefaultHttpRequestTests.cs @@ -173,7 +173,7 @@ public void Cookies_GetAndSet() var newCookies = new[] { "name0=value0", "name1=value1" }; request.Headers["Cookie"] = newCookies; - cookies0 = RequestCookieCollection.Reparse(newCookies); + cookies0 = RequestCookieCollection.Parse(newCookies); var cookies1 = request.Cookies; Assert.Equal(cookies0, cookies1); Assert.Equal(2, cookies1.Count);