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..c0f52fd4 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 IRequestCookieCollection 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
index 68505962..03e81168 100644
--- a/src/Microsoft.AspNet.Http.Abstractions/IFormCollection.cs
+++ b/src/Microsoft.AspNet.Http.Abstractions/IFormCollection.cs
@@ -1,13 +1,95 @@
// 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
{
///
- /// Contains the parsed form values.
+ /// Represents the parsed form values sent with the HttpRequest.
///
- public interface IFormCollection : IReadableStringCollection
+ public interface IFormCollection : 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; }
+
+ ///
+ /// 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.Abstractions/IFormFile.cs
index e85ee75f..6f8fdaa2 100644
--- a/src/Microsoft.AspNet.Http.Abstractions/IFormFile.cs
+++ b/src/Microsoft.AspNet.Http.Abstractions/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.Abstractions/IFormFileCollection.cs
index 4950758b..229b7bbb 100644
--- a/src/Microsoft.AspNet.Http.Abstractions/IFormFileCollection.cs
+++ b/src/Microsoft.AspNet.Http.Abstractions/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.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/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/IRequestCookieCollection.cs b/src/Microsoft.AspNet.Http.Abstractions/IRequestCookieCollection.cs
new file mode 100644
index 00000000..5cc028b1
--- /dev/null
+++ b/src/Microsoft.AspNet.Http.Abstractions/IRequestCookieCollection.cs
@@ -0,0 +1,87 @@
+// 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;
+
+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; }
+
+ ///
+ /// 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 string 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 String.Empty for missing entries
+ /// rather than throwing an Exception.
+ ///
+ string this[string key] { get; }
+ }
+}
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..088402b2 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)}");
}
///
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..c9ac784d 100644
--- a/src/Microsoft.AspNet.Http.Extensions/HeaderDictionaryTypeExtensions.cs
+++ b/src/Microsoft.AspNet.Http.Extensions/HeaderDictionaryTypeExtensions.cs
@@ -78,9 +78,49 @@ 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
{
- headers[name] = values.Select(value => value.ToString()).ToArray();
+ var newValues = new string[values.Count];
+ for (var i = 0; i < values.Count; i++)
+ {
+ newValues[i] = values[i].ToString();
+ }
+ headers[name] = new StringValues(newValues);
+ }
+ }
+
+ 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;
}
}
@@ -139,7 +179,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 +188,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 +202,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 +219,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 +231,7 @@ private static T GetViaReflection(string value)
&& methodParams[1].ParameterType.Equals(type.MakeByRefType());
}
return false;
- }).FirstOrDefault();
+ });
if (method == null)
{
@@ -213,7 +253,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 +265,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..acd3a9ad 100644
--- a/src/Microsoft.AspNet.Http.Extensions/Internal/HeaderSegment.cs
+++ b/src/Microsoft.AspNet.Http.Extensions/Internal/HeaderSegment.cs
@@ -1,4 +1,9 @@
-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;
+using Microsoft.Extensions.Primitives;
+
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
deleted file mode 100644
index 83c99842..00000000
--- a/src/Microsoft.AspNet.Http.Extensions/Internal/StringSegment.cs
+++ /dev/null
@@ -1,146 +0,0 @@
-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;
- }
- }
-
-}
diff --git a/src/Microsoft.AspNet.Http.Extensions/RequestHeaders.cs b/src/Microsoft.AspNet.Http.Extensions/RequestHeaders.cs
index 4b31e306..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 System.Linq;
+using Microsoft.AspNet.Http.Extensions;
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
{
@@ -309,17 +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));
- }
-
- Headers.Append(name, values.Select(value => value.ToString()).ToArray());
+ 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 e7cb97ad..baad250f 100644
--- a/src/Microsoft.AspNet.Http.Extensions/ResponseHeaders.cs
+++ b/src/Microsoft.AspNet.Http.Extensions/ResponseHeaders.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using Microsoft.AspNet.Http.Extensions;
using Microsoft.Net.Http.Headers;
@@ -135,7 +134,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;
}
@@ -206,17 +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));
- }
-
- Headers.Append(name, values.Select(value => value.ToString()).ToArray());
+ Headers.AppendList(name, values);
}
}
}
\ 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/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/DefaultHttpRequest.cs b/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs
index 99f39264..c39f01cb 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 IRequestCookieCollection 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..f3ed68e3 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()
{
@@ -77,17 +85,32 @@ public IFormCollection ReadForm()
throw new InvalidOperationException("Incorrect Content-Type: " + _request.ContentType);
}
+ // 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(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);
@@ -97,18 +120,18 @@ public async Task ReadFormAsync(CancellationToken cancellationT
_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.ReadFormAsync(_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,35 @@ 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);
+ 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/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..b015b2d8 100644
--- a/src/Microsoft.AspNet.Http/Features/IRequestCookiesFeature.cs
+++ b/src/Microsoft.AspNet.Http/Features/IRequestCookiesFeature.cs
@@ -5,6 +5,6 @@ namespace Microsoft.AspNet.Http.Features.Internal
{
public interface IRequestCookiesFeature
{
- IReadableStringCollection 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 b2f59e00..e17e6125 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 = QueryCollection.Empty;
+ }
+ return _parsedValues;
}
var current = HttpRequestFeature.QueryString;
if (_parsedValues == null || !string.Equals(_original, current, StringComparison.Ordinal))
{
_original = current;
- _parsedValues = new ReadableStringCollection(QueryHelpers.ParseQuery(current));
+
+ var result = QueryHelpers.ParseNullableQuery(current);
+
+ if (result == null)
+ {
+ _parsedValues = QueryCollection.Empty;
+ }
+ else
+ {
+ _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 9eba2dc9..e759f1d2 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 IRequestCookieCollection _parsedValues;
+
+ public RequestCookiesFeature(IRequestCookieCollection cookies)
{
if (cookies == null)
{
@@ -59,32 +53,30 @@ private IHttpRequestFeature HttpRequestFeature
get { return FeatureHelpers.GetAndCache(this, _features, ref _request); }
}
- public IReadableStringCollection Cookies
+ public IRequestCookieCollection Cookies
{
get
{
if (_features == null)
{
- return _parsedValues ?? ReadableStringCollection.Empty;
+ if (_parsedValues == null)
+ {
+ _parsedValues = RequestCookieCollection.Empty;
+ }
+ return _parsedValues;
}
var headers = HttpRequestFeature.Headers;
StringValues current;
if (!headers.TryGetValue(HeaderNames.Cookie, out current))
{
- current = StringValues.Empty;
+ current = string.Empty;
}
- if (_parsedValues == null || !Enumerable.SequenceEqual(_original, current, StringComparer.Ordinal))
+ if (_parsedValues == null || _original != current)
{
_original = current;
- var collectionParser = _parsedValues as RequestCookiesCollection;
- if (collectionParser == null)
- {
- collectionParser = new RequestCookiesCollection();
- _parsedValues = collectionParser;
- }
- collectionParser.Reparse(current);
+ _parsedValues = RequestCookieCollection.Parse(current.ToArray());
}
return _parsedValues;
@@ -104,10 +96,7 @@ public IReadableStringCollection 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/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..7a7d67c2 100644
--- a/src/Microsoft.AspNet.Http/FormCollection.cs
+++ b/src/Microsoft.AspNet.Http/FormCollection.cs
@@ -1,7 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-using System;
+using System.Collections;
using System.Collections.Generic;
using Microsoft.Extensions.Primitives;
@@ -10,29 +10,226 @@ namespace Microsoft.AspNet.Http.Internal
///
/// Contains the parsed form values.
///
- public class FormCollection : ReadableStringCollection, IFormCollection
+ public class FormCollection : IFormCollection
{
- public FormCollection(IDictionary store)
- : this(store, new FormFileCollection())
+ public static readonly FormCollection Empty = new FormCollection();
+#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
+ 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 IFormFileCollection Files
+ {
+ get
+ {
+ return _files ?? EmptyFiles;
+ }
+ private set { _files = value; }
+ }
+
+ private Dictionary Store { get; set; }
+
+ ///
+ /// 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;
+ }
+ }
+
+ ///
+ /// 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;
+ }
+ }
+
+ public ICollection Keys
+ {
+ get
+ {
+ if (Store == null)
+ {
+ return EmptyKeys;
+ }
+ return Store.Keys;
+ }
+ }
+
+ ///
+ /// 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);
+ }
+
+ ///
+ /// 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);
}
- public FormCollection(IDictionary store, IFormFileCollection files)
- : base(store)
+ ///
+ /// 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)
+ if (Store == null || Store.Count == 0)
{
- throw new ArgumentNullException(nameof(store));
+ // Non-boxed Enumerator
+ return EmptyEnumerator;
}
+ // Non-boxed Enumerator
+ return new Enumerator(Store.GetEnumerator());
+ }
- if (files == null)
+ ///
+ /// 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)
{
- throw new ArgumentNullException(nameof(files));
+ // Non-boxed Enumerator
+ return EmptyIEnumeratorType;
}
+ // Boxed Enumerator
+ return Store.GetEnumerator();
+ }
- Files = files;
+ ///
+ /// 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 IFormFileCollection Files { get; }
+ 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..f1fde19a 100644
--- a/src/Microsoft.AspNet.Http/HeaderDictionary.cs
+++ b/src/Microsoft.AspNet.Http/HeaderDictionary.cs
@@ -9,123 +9,268 @@
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 HeaderDictionary() : this(new Dictionary(StringComparer.OrdinalIgnoreCase))
+#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
+ 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)
+ {
+ return EmptyKeys;
+ }
+ return Store.Keys;
+ }
+ }
+
+ public ICollection Values
{
- get { return Store.Count; }
+ get
+ {
+ if (Store == null)
+ {
+ return EmptyValues;
+ }
+ 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("The key is null");
+ }
+ 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 +280,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 +292,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
/// 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,7 @@ 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 reader = new FormReader(stream, encoding);
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..e9e0aba9 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..036ed557 100644
--- a/src/Microsoft.AspNet.WebUtilities/QueryHelpers.cs
+++ b/src/Microsoft.AspNet.WebUtilities/QueryHelpers.cs
@@ -107,21 +107,46 @@ 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 Dictionary ParseQuery(string queryString)
{
- if (!string.IsNullOrEmpty(queryString) && queryString[0] == '?')
+ var result = ParseNullableQuery(queryString);
+
+ if (result == null)
{
- queryString = queryString.Substring(1);
+ 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 null;
+ }
+
+ int scanIndex = 0;
+ if (!string.IsNullOrEmpty(queryString) && queryString[0] == '?')
+ {
+ scanIndex = 1;
+ }
+
+
int textLength = queryString.Length;
int equalIndex = queryString.IndexOf('=');
if (equalIndex == -1)
{
equalIndex = textLength;
}
- int scanIndex = 0;
while (scanIndex < textLength)
{
int delimiterIndex = queryString.IndexOf('&', scanIndex);
@@ -149,6 +174,11 @@ public static IDictionary ParseQuery(string queryString)
scanIndex = delimiterIndex + 1;
}
+ if (!accumulator.HasValues)
+ {
+ return null;
+ }
+
return accumulator.GetResults();
}
}
diff --git a/test/Microsoft.AspNet.Http.Tests/DefaultHttpRequestTests.cs b/test/Microsoft.AspNet.Http.Tests/DefaultHttpRequestTests.cs
index e020da61..586b8ca1 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" }
});
@@ -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.Parse(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 ReadableStringCollection(new Dictionary()
+ 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);