Skip to content

Commit 238f7dd

Browse files
QueryStringEnumerable now works in terms of ReadOnlyMemory<char> (#34001)
1 parent 2e8d295 commit 238f7dd

File tree

4 files changed

+39
-31
lines changed

4 files changed

+39
-31
lines changed

src/Http/Http/src/Features/QueryFeature.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,10 @@ public IQueryCollection Query
110110
}
111111

112112
var accumulator = new KvpAccumulator();
113-
var enumerable = new QueryStringEnumerable(queryString.AsSpan());
113+
var enumerable = new QueryStringEnumerable(queryString);
114114
foreach (var pair in enumerable)
115115
{
116-
accumulator.Append(pair.DecodeName(), pair.DecodeValue());
116+
accumulator.Append(pair.DecodeName().Span, pair.DecodeValue().Span);
117117
}
118118

119119
return accumulator.HasValues

src/Http/WebUtilities/src/PublicAPI.Unshipped.txt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@ Microsoft.AspNetCore.WebUtilities.FileBufferingReadStream.MemoryThreshold.get ->
44
Microsoft.AspNetCore.WebUtilities.FileBufferingWriteStream.MemoryThreshold.get -> int
55
Microsoft.AspNetCore.WebUtilities.QueryStringEnumerable
66
Microsoft.AspNetCore.WebUtilities.QueryStringEnumerable.EncodedNameValuePair
7-
Microsoft.AspNetCore.WebUtilities.QueryStringEnumerable.EncodedNameValuePair.DecodeName() -> System.ReadOnlySpan<char>
8-
Microsoft.AspNetCore.WebUtilities.QueryStringEnumerable.EncodedNameValuePair.DecodeValue() -> System.ReadOnlySpan<char>
9-
Microsoft.AspNetCore.WebUtilities.QueryStringEnumerable.EncodedNameValuePair.EncodedName.get -> System.ReadOnlySpan<char>
10-
Microsoft.AspNetCore.WebUtilities.QueryStringEnumerable.EncodedNameValuePair.EncodedValue.get -> System.ReadOnlySpan<char>
7+
Microsoft.AspNetCore.WebUtilities.QueryStringEnumerable.EncodedNameValuePair.DecodeName() -> System.ReadOnlyMemory<char>
8+
Microsoft.AspNetCore.WebUtilities.QueryStringEnumerable.EncodedNameValuePair.DecodeValue() -> System.ReadOnlyMemory<char>
9+
Microsoft.AspNetCore.WebUtilities.QueryStringEnumerable.EncodedNameValuePair.EncodedName.get -> System.ReadOnlyMemory<char>
10+
Microsoft.AspNetCore.WebUtilities.QueryStringEnumerable.EncodedNameValuePair.EncodedValue.get -> System.ReadOnlyMemory<char>
1111
Microsoft.AspNetCore.WebUtilities.QueryStringEnumerable.Enumerator
1212
Microsoft.AspNetCore.WebUtilities.QueryStringEnumerable.Enumerator.Current.get -> Microsoft.AspNetCore.WebUtilities.QueryStringEnumerable.EncodedNameValuePair
1313
Microsoft.AspNetCore.WebUtilities.QueryStringEnumerable.Enumerator.MoveNext() -> bool
1414
Microsoft.AspNetCore.WebUtilities.QueryStringEnumerable.GetEnumerator() -> Microsoft.AspNetCore.WebUtilities.QueryStringEnumerable.Enumerator
15-
Microsoft.AspNetCore.WebUtilities.QueryStringEnumerable.QueryStringEnumerable(System.ReadOnlySpan<char> queryString) -> void
15+
Microsoft.AspNetCore.WebUtilities.QueryStringEnumerable.QueryStringEnumerable(System.ReadOnlyMemory<char> queryString) -> void
16+
Microsoft.AspNetCore.WebUtilities.QueryStringEnumerable.QueryStringEnumerable(string? queryString) -> void
1617
override Microsoft.AspNetCore.WebUtilities.BufferedReadStream.ReadAsync(System.Memory<byte> buffer, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask<int>
1718
override Microsoft.AspNetCore.WebUtilities.FileBufferingWriteStream.WriteAsync(System.ReadOnlyMemory<byte> buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
1819
static Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseNullableQuery(string? queryString) -> System.Collections.Generic.Dictionary<string!, Microsoft.Extensions.Primitives.StringValues>?

src/Shared/QueryStringEnumerable.cs

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,24 @@ namespace Microsoft.AspNetCore.Internal
2222
#else
2323
internal
2424
#endif
25-
readonly ref struct QueryStringEnumerable
25+
readonly struct QueryStringEnumerable
2626
{
27-
private readonly ReadOnlySpan<char> _queryString;
27+
private readonly ReadOnlyMemory<char> _queryString;
2828

2929
/// <summary>
3030
/// Constructs an instance of <see cref="QueryStringEnumerable"/>.
3131
/// </summary>
3232
/// <param name="queryString">The query string.</param>
33-
public QueryStringEnumerable(ReadOnlySpan<char> queryString)
33+
public QueryStringEnumerable(string? queryString)
34+
: this(queryString.AsMemory())
35+
{
36+
}
37+
38+
/// <summary>
39+
/// Constructs an instance of <see cref="QueryStringEnumerable"/>.
40+
/// </summary>
41+
/// <param name="queryString">The query string.</param>
42+
public QueryStringEnumerable(ReadOnlyMemory<char> queryString)
3443
{
3544
_queryString = queryString;
3645
}
@@ -45,21 +54,21 @@ public Enumerator GetEnumerator()
4554
/// <summary>
4655
/// Represents a single name/value pair extracted from a query string during enumeration.
4756
/// </summary>
48-
public readonly ref struct EncodedNameValuePair
57+
public readonly struct EncodedNameValuePair
4958
{
5059
/// <summary>
5160
/// Gets the name from this name/value pair in its original encoded form.
5261
/// To get the decoded string, call <see cref="DecodeName"/>.
5362
/// </summary>
54-
public readonly ReadOnlySpan<char> EncodedName { get; }
63+
public readonly ReadOnlyMemory<char> EncodedName { get; }
5564

5665
/// <summary>
5766
/// Gets the value from this name/value pair in its original encoded form.
5867
/// To get the decoded string, call <see cref="DecodeValue"/>.
5968
/// </summary>
60-
public readonly ReadOnlySpan<char> EncodedValue { get; }
69+
public readonly ReadOnlyMemory<char> EncodedValue { get; }
6170

62-
internal EncodedNameValuePair(ReadOnlySpan<char> encodedName, ReadOnlySpan<char> encodedValue)
71+
internal EncodedNameValuePair(ReadOnlyMemory<char> encodedName, ReadOnlyMemory<char> encodedValue)
6372
{
6473
EncodedName = encodedName;
6574
EncodedValue = encodedValue;
@@ -69,37 +78,37 @@ internal EncodedNameValuePair(ReadOnlySpan<char> encodedName, ReadOnlySpan<char>
6978
/// Decodes the name from this name/value pair.
7079
/// </summary>
7180
/// <returns>Characters representing the decoded name.</returns>
72-
public ReadOnlySpan<char> DecodeName()
81+
public ReadOnlyMemory<char> DecodeName()
7382
=> Decode(EncodedName);
7483

7584
/// <summary>
7685
/// Decodes the value from this name/value pair.
7786
/// </summary>
7887
/// <returns>Characters representing the decoded value.</returns>
79-
public ReadOnlySpan<char> DecodeValue()
88+
public ReadOnlyMemory<char> DecodeValue()
8089
=> Decode(EncodedValue);
8190

82-
private static ReadOnlySpan<char> Decode(ReadOnlySpan<char> chars)
91+
private static ReadOnlyMemory<char> Decode(ReadOnlyMemory<char> chars)
8392
{
8493
// If the value is short, it's cheap to check up front if it really needs decoding. If it doesn't,
8594
// then we can save some allocations.
86-
return chars.Length < 16 && chars.IndexOfAny('%', '+') < 0
95+
return chars.Length < 16 && chars.Span.IndexOfAny('%', '+') < 0
8796
? chars
88-
: Uri.UnescapeDataString(SpanHelper.ReplacePlusWithSpace(chars));
97+
: Uri.UnescapeDataString(SpanHelper.ReplacePlusWithSpace(chars.Span)).AsMemory();
8998
}
9099
}
91100

92101
/// <summary>
93102
/// An enumerator that supplies the name/value pairs from a URI query string.
94103
/// </summary>
95-
public ref struct Enumerator
104+
public struct Enumerator
96105
{
97-
private ReadOnlySpan<char> _query;
106+
private ReadOnlyMemory<char> _query;
98107

99-
internal Enumerator(ReadOnlySpan<char> query)
108+
internal Enumerator(ReadOnlyMemory<char> query)
100109
{
101110
Current = default;
102-
_query = query.IsEmpty || query[0] != '?'
111+
_query = query.IsEmpty || query.Span[0] != '?'
103112
? query
104113
: query.Slice(1);
105114
}
@@ -118,8 +127,8 @@ public bool MoveNext()
118127
while (!_query.IsEmpty)
119128
{
120129
// Chomp off the next segment
121-
ReadOnlySpan<char> segment;
122-
var delimiterIndex = _query.IndexOf('&');
130+
ReadOnlyMemory<char> segment;
131+
var delimiterIndex = _query.Span.IndexOf('&');
123132
if (delimiterIndex >= 0)
124133
{
125134
segment = _query.Slice(0, delimiterIndex);
@@ -132,7 +141,7 @@ public bool MoveNext()
132141
}
133142

134143
// If it's nonempty, emit it
135-
var equalIndex = segment.IndexOf('=');
144+
var equalIndex = segment.Span.IndexOf('=');
136145
if (equalIndex >= 0)
137146
{
138147
Current = new EncodedNameValuePair(

src/Shared/test/Shared.Tests/QueryStringEnumerableTest.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,12 @@ public void DecodingWorks(string queryString, string expectedDecodedName, string
9797
}
9898

9999
[Fact]
100-
public void DecodingRetainsSpansIfDecodingNotNeeded()
100+
public void DecodingReusesMemoryIfDecodingNotNeeded()
101101
{
102102
foreach (var kvp in new QueryStringEnumerable("?key=value"))
103103
{
104-
Assert.True(MemoryExtensions.Overlaps(kvp.EncodedName, kvp.DecodeName(), out var nameOffset));
105-
Assert.True(MemoryExtensions.Overlaps(kvp.EncodedValue, kvp.DecodeValue(), out var valueOffset));
106-
Assert.Equal(0, nameOffset);
107-
Assert.Equal(0, valueOffset);
104+
Assert.True(kvp.EncodedName.Equals(kvp.DecodeName()));
105+
Assert.True(kvp.EncodedValue.Equals(kvp.DecodeValue()));
108106
}
109107
}
110108

0 commit comments

Comments
 (0)