Skip to content

Commit b470fb2

Browse files
committed
Showing what Removing RequestCookieCollection would look like
1 parent 687a330 commit b470fb2

7 files changed

+553
-282
lines changed

src/Http/Http/perf/Microbenchmarks/RequestCookieCollectionBenchmarks.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using BenchmarkDotNet.Attributes;
5+
using Microsoft.AspNetCore.Http.Features;
56
using Microsoft.Extensions.Primitives;
67

78
namespace Microsoft.AspNetCore.Http
@@ -19,7 +20,7 @@ public void Setup()
1920
[Benchmark]
2021
public void Parse_TypicalCookie()
2122
{
22-
RequestCookieCollection.Parse(_cookie);
23+
var feature = RequestCookiesFeature.Parse(_cookie);
2324
}
2425
}
2526
}

src/Http/Http/src/Features/RequestCookiesFeature.cs

+253-11
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Collections;
56
using System.Collections.Generic;
7+
using System.Diagnostics.CodeAnalysis;
68
using Microsoft.Extensions.Primitives;
79
using Microsoft.Net.Http.Headers;
810

@@ -11,14 +13,21 @@ namespace Microsoft.AspNetCore.Http.Features
1113
/// <summary>
1214
/// Default implementation for <see cref="IRequestCookiesFeature"/>.
1315
/// </summary>
14-
public class RequestCookiesFeature : IRequestCookiesFeature
16+
public class RequestCookiesFeature : IRequestCookiesFeature, IRequestCookieCollection
1517
{
1618
// Lambda hoisted to static readonly field to improve inlining https://github.com/dotnet/roslyn/issues/13624
1719
private readonly static Func<IFeatureCollection, IHttpRequestFeature?> _nullRequestFeature = f => null;
1820

1921
private FeatureReferences<IHttpRequestFeature> _features;
2022
private StringValues _original;
21-
private IRequestCookieCollection? _parsedValues;
23+
private Dictionary<string, string>? _parsedValues;
24+
private IRequestCookieCollection? _userPassed;
25+
26+
private static readonly string[] EmptyKeys = Array.Empty<string>();
27+
private static readonly Enumerator EmptyEnumerator = new Enumerator();
28+
// Pre-box
29+
private static readonly IEnumerator<KeyValuePair<string, string>> EmptyIEnumeratorType = EmptyEnumerator;
30+
private static readonly IEnumerator EmptyIEnumerator = EmptyEnumerator;
2231

2332
/// <summary>
2433
/// Initializes a new instance of <see cref="RequestCookiesFeature"/>.
@@ -31,7 +40,7 @@ public RequestCookiesFeature(IRequestCookieCollection cookies)
3140
throw new ArgumentNullException(nameof(cookies));
3241
}
3342

34-
_parsedValues = cookies;
43+
_userPassed = cookies;
3544
}
3645

3746
/// <summary>
@@ -48,6 +57,8 @@ public RequestCookiesFeature(IFeatureCollection features)
4857
_features.Initalize(features);
4958
}
5059

60+
private Dictionary<string, string>? Store { get; set; }
61+
5162
private IHttpRequestFeature HttpRequestFeature =>
5263
_features.Fetch(ref _features.Cache, _nullRequestFeature)!;
5364

@@ -60,9 +71,10 @@ public IRequestCookieCollection Cookies
6071
{
6172
if (_parsedValues == null)
6273
{
63-
_parsedValues = RequestCookieCollection.Empty;
74+
_parsedValues = new Dictionary<string, string>();
6475
}
65-
return _parsedValues;
76+
77+
return this;
6678
}
6779

6880
var headers = HttpRequestFeature.Headers;
@@ -75,25 +87,25 @@ public IRequestCookieCollection Cookies
7587
if (_parsedValues == null || _original != current)
7688
{
7789
_original = current;
78-
_parsedValues = RequestCookieCollection.Parse(current);
90+
_parsedValues = Parse(current);
7991
}
8092

81-
return _parsedValues;
93+
return this;
8294
}
8395
set
8496
{
85-
_parsedValues = value;
97+
_userPassed = value;
8698
_original = StringValues.Empty;
8799
if (_features.Collection != null)
88100
{
89-
if (_parsedValues == null || _parsedValues.Count == 0)
101+
if (_userPassed == null || _userPassed.Count == 0)
90102
{
91103
HttpRequestFeature.Headers.Remove(HeaderNames.Cookie);
92104
}
93105
else
94106
{
95-
var headers = new List<string>(_parsedValues.Count);
96-
foreach (var pair in _parsedValues)
107+
var headers = new List<string>(_userPassed.Count);
108+
foreach (var pair in _userPassed)
97109
{
98110
headers.Add(new CookieHeaderValue(pair.Key, pair.Value).ToString());
99111
}
@@ -103,5 +115,235 @@ public IRequestCookieCollection Cookies
103115
}
104116
}
105117
}
118+
119+
/// <inheritdoc />
120+
public string? this[string key]
121+
{
122+
get
123+
{
124+
if (key == null)
125+
{
126+
throw new ArgumentNullException(nameof(key));
127+
}
128+
129+
if (_userPassed != null)
130+
{
131+
return _userPassed[key];
132+
}
133+
134+
if (Store == null)
135+
{
136+
return null;
137+
}
138+
139+
if (TryGetValue(key, out var value))
140+
{
141+
return value;
142+
}
143+
return null;
144+
}
145+
}
146+
147+
internal static Dictionary<string, string>? Parse(StringValues values)
148+
=> ParseInternal(values, AppContext.TryGetSwitch(ResponseCookies.EnableCookieNameEncoding, out var enabled) && enabled);
149+
150+
internal static Dictionary<string, string>? ParseInternal(StringValues values, bool enableCookieNameEncoding)
151+
{
152+
if (values.Count == 0)
153+
{
154+
return null;
155+
}
156+
157+
var store = new Dictionary<string, string>(values.Count);
158+
159+
if (CookieHeaderValue.TryParseIntoDictionary(values, store, enableCookieNameEncoding))
160+
{
161+
if (store.Count == 0)
162+
{
163+
return null;
164+
}
165+
166+
return store;
167+
}
168+
169+
return null;
170+
}
171+
172+
/// <inheritdoc />
173+
public int Count
174+
{
175+
get
176+
{
177+
if (_userPassed != null)
178+
{
179+
return _userPassed.Count;
180+
}
181+
182+
if (Store == null)
183+
{
184+
return 0;
185+
}
186+
return Store.Count;
187+
}
188+
}
189+
190+
/// <inheritdoc />
191+
public ICollection<string> Keys
192+
{
193+
get
194+
{
195+
if (_userPassed != null)
196+
{
197+
return _userPassed.Keys;
198+
}
199+
200+
if (Store == null)
201+
{
202+
return EmptyKeys;
203+
}
204+
return Store.Keys;
205+
}
206+
}
207+
208+
/// <inheritdoc />
209+
public bool ContainsKey(string key)
210+
{
211+
if (_userPassed != null)
212+
{
213+
return _userPassed.ContainsKey(key);
214+
}
215+
216+
if (Store == null)
217+
{
218+
return false;
219+
}
220+
return Store.ContainsKey(key);
221+
}
222+
223+
/// <inheritdoc />
224+
public bool TryGetValue(string key, [MaybeNullWhen(false)] out string? value)
225+
{
226+
if (_userPassed != null)
227+
{
228+
return _userPassed.TryGetValue(key, out value);
229+
}
230+
231+
if (Store == null)
232+
{
233+
value = null;
234+
return false;
235+
}
236+
return Store.TryGetValue(key, out value);
237+
}
238+
239+
/// <summary>
240+
/// Returns an struct enumerator that iterates through a collection without boxing.
241+
/// </summary>
242+
/// <returns>An <see cref="Enumerator" /> object that can be used to iterate through the collection.</returns>
243+
internal Enumerator GetEnumerator()
244+
{
245+
if (Store == null || Store.Count == 0)
246+
{
247+
// Non-boxed Enumerator
248+
return EmptyEnumerator;
249+
}
250+
// Non-boxed Enumerator
251+
return new Enumerator(Store.GetEnumerator());
252+
}
253+
254+
/// <summary>
255+
/// Returns an enumerator that iterates through a collection, boxes in non-empty path.
256+
/// </summary>
257+
/// <returns>An <see cref="IEnumerator{T}" /> object that can be used to iterate through the collection.</returns>
258+
IEnumerator<KeyValuePair<string, string>> IEnumerable<KeyValuePair<string, string>>.GetEnumerator()
259+
{
260+
if (_userPassed != null)
261+
{
262+
return _userPassed.GetEnumerator();
263+
}
264+
265+
if (Store == null || Store.Count == 0)
266+
{
267+
// Non-boxed Enumerator
268+
return EmptyIEnumeratorType;
269+
}
270+
// Boxed Enumerator
271+
return GetEnumerator();
272+
}
273+
274+
/// <summary>
275+
/// Returns an enumerator that iterates through a collection, boxes in non-empty path.
276+
/// </summary>
277+
/// <returns>An <see cref="IEnumerator" /> object that can be used to iterate through the collection.</returns>
278+
IEnumerator IEnumerable.GetEnumerator()
279+
{
280+
if (_userPassed != null)
281+
{
282+
return _userPassed.GetEnumerator();
283+
}
284+
285+
if (Store == null || Store.Count == 0)
286+
{
287+
// Non-boxed Enumerator
288+
return EmptyIEnumerator;
289+
}
290+
// Boxed Enumerator
291+
return GetEnumerator();
292+
}
293+
294+
internal struct Enumerator : IEnumerator<KeyValuePair<string, string>>
295+
{
296+
// Do NOT make this readonly, or MoveNext will not work
297+
private Dictionary<string, string>.Enumerator _dictionaryEnumerator;
298+
private bool _notEmpty;
299+
300+
internal Enumerator(Dictionary<string, string>.Enumerator dictionaryEnumerator)
301+
{
302+
_dictionaryEnumerator = dictionaryEnumerator;
303+
_notEmpty = true;
304+
}
305+
306+
public bool MoveNext()
307+
{
308+
if (_notEmpty)
309+
{
310+
return _dictionaryEnumerator.MoveNext();
311+
}
312+
return false;
313+
}
314+
315+
public KeyValuePair<string, string> Current
316+
{
317+
get
318+
{
319+
if (_notEmpty)
320+
{
321+
var current = _dictionaryEnumerator.Current;
322+
return new KeyValuePair<string, string>(current.Key, current.Value);
323+
}
324+
return default(KeyValuePair<string, string>);
325+
}
326+
}
327+
328+
object IEnumerator.Current
329+
{
330+
get
331+
{
332+
return Current;
333+
}
334+
}
335+
336+
public void Dispose()
337+
{
338+
}
339+
340+
public void Reset()
341+
{
342+
if (_notEmpty)
343+
{
344+
((IEnumerator)_dictionaryEnumerator).Reset();
345+
}
346+
}
347+
}
106348
}
107349
}

0 commit comments

Comments
 (0)