Skip to content

Commit c84f4ba

Browse files
Replace generic dictionary with specialized collection type.
1 parent 7c2b496 commit c84f4ba

22 files changed

+221
-99
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Collections.ObjectModel;
7+
8+
namespace SixLabors.ImageSharp.Web.Commands
9+
{
10+
/// <summary>
11+
/// Represents an ordered collection of processing commands.
12+
/// </summary>
13+
public sealed class CommandCollection : KeyedCollection<string, KeyValuePair<string, string>>
14+
{
15+
private readonly List<string> keys = new();
16+
17+
/// <summary>
18+
/// Initializes a new instance of the <see cref="CommandCollection"/> class.
19+
/// </summary>
20+
public CommandCollection()
21+
: this(StringComparer.OrdinalIgnoreCase)
22+
{
23+
}
24+
25+
private CommandCollection(IEqualityComparer<string> comparer)
26+
: base(comparer)
27+
{
28+
}
29+
30+
/// <summary>
31+
/// Gets an <see cref="ICollection{T}"/> representing the keys of the collection.
32+
/// </summary>
33+
public ICollection<string> Keys => this.keys;
34+
35+
/// <summary>
36+
/// Gets the command value with the specified key.
37+
/// </summary>
38+
/// <param name="key">The key of the element to get.</param>
39+
/// <returns>
40+
/// The command value with the specified key. If a value with the specified key is not
41+
/// found, an exception is thrown.
42+
/// </returns>
43+
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null.</exception>
44+
/// <exception cref="KeyNotFoundException">An element with the specified key does not exist in the collection.</exception>
45+
public new string this[string key]
46+
{
47+
get
48+
{
49+
if (this.TryGetValue(key, out KeyValuePair<string, string> item))
50+
{
51+
return item.Key;
52+
}
53+
54+
throw new KeyNotFoundException();
55+
}
56+
}
57+
58+
/// <inheritdoc/>
59+
protected override void InsertItem(int index, KeyValuePair<string, string> item)
60+
{
61+
base.InsertItem(index, item);
62+
this.keys.Insert(index, item.Key);
63+
}
64+
65+
/// <inheritdoc/>
66+
protected override void RemoveItem(int index)
67+
{
68+
base.RemoveItem(index);
69+
this.keys.RemoveAt(index);
70+
}
71+
72+
/// <inheritdoc/>
73+
protected override void SetItem(int index, KeyValuePair<string, string> item)
74+
{
75+
base.SetItem(index, item);
76+
this.keys[index] = item.Key;
77+
}
78+
79+
/// <inheritdoc/>
80+
protected override void ClearItems()
81+
{
82+
base.ClearItems();
83+
this.keys.Clear();
84+
}
85+
86+
/// <inheritdoc/>
87+
protected override string GetKeyForItem(KeyValuePair<string, string> item) => item.Key;
88+
}
89+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System.Collections.Generic;
5+
6+
namespace SixLabors.ImageSharp.Web.Commands
7+
{
8+
/// <summary>
9+
/// Extension methods for <see cref="CommandCollectionExtensions"/>.
10+
/// </summary>
11+
public static class CommandCollectionExtensions
12+
{
13+
/// <summary>
14+
/// Gets the value associated with the specified key or the default value.
15+
/// </summary>
16+
/// <param name="collection">The collection instance.</param>
17+
/// <param name="key">The key of the value to get.</param>
18+
/// <returns>The value associated with the specified key or the default value.</returns>
19+
public static string GetValueOrDefault(this CommandCollection collection, string key)
20+
{
21+
collection.TryGetValue(key, out KeyValuePair<string, string> result);
22+
return result.Value;
23+
}
24+
}
25+
}

src/ImageSharp.Web/Commands/IRequestParser.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ public interface IRequestParser
1616
/// </summary>
1717
/// <param name="context">Encapsulates all HTTP-specific information about an individual HTTP request.</param>
1818
/// <returns>The <see cref="IDictionary{TKey,TValue}"/>.</returns>
19-
IDictionary<string, string> ParseRequestCommands(HttpContext context);
19+
CommandCollection ParseRequestCommands(HttpContext context);
2020
}
2121
}

src/ImageSharp.Web/Commands/PresetOnlyQueryCollectionRequestParser.cs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Six Labors.
1+
// Copyright (c) Six Labors.
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
@@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Web.Commands
1616
/// </summary>
1717
public class PresetOnlyQueryCollectionRequestParser : IRequestParser
1818
{
19-
private readonly IDictionary<string, IDictionary<string, string>> presets;
19+
private readonly IDictionary<string, CommandCollection> presets;
2020

2121
/// <summary>
2222
/// The command constant for the preset query parameter.
@@ -31,31 +31,33 @@ public PresetOnlyQueryCollectionRequestParser(IOptions<PresetOnlyQueryCollection
3131
this.presets = ParsePresets(presetOptions.Value.Presets);
3232

3333
/// <inheritdoc/>
34-
public IDictionary<string, string> ParseRequestCommands(HttpContext context)
34+
public CommandCollection ParseRequestCommands(HttpContext context)
3535
{
3636
if (context.Request.Query.Count == 0 || !context.Request.Query.ContainsKey(QueryKey))
3737
{
38-
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
38+
return new();
3939
}
4040

41-
var requestedPreset = context.Request.Query["preset"][0];
42-
return this.presets.GetValueOrDefault(requestedPreset) ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
41+
string requestedPreset = context.Request.Query["preset"][0];
42+
return this.presets.GetValueOrDefault(requestedPreset) ?? new();
4343
}
4444

45-
private static IDictionary<string, IDictionary<string, string>> ParsePresets(
45+
private static IDictionary<string, CommandCollection> ParsePresets(
4646
IDictionary<string, string> unparsedPresets) =>
4747
unparsedPresets
4848
.Select(keyValue =>
49-
new KeyValuePair<string, IDictionary<string, string>>(keyValue.Key, ParsePreset(keyValue.Value)))
49+
new KeyValuePair<string, CommandCollection>(keyValue.Key, ParsePreset(keyValue.Value)))
5050
.ToDictionary(keyValue => keyValue.Key, keyValue => keyValue.Value, StringComparer.OrdinalIgnoreCase);
5151

52-
private static IDictionary<string, string> ParsePreset(string unparsedPresetValue)
52+
private static CommandCollection ParsePreset(string unparsedPresetValue)
5353
{
54+
// TODO: Investigate skipping the double allocation here.
55+
// In .NET 6 we can directly use the QueryStringEnumerable type and enumerate stright to our command collection
5456
Dictionary<string, StringValues> parsed = QueryHelpers.ParseQuery(unparsedPresetValue);
55-
var transformed = new Dictionary<string, string>(parsed.Count, StringComparer.OrdinalIgnoreCase);
56-
foreach (KeyValuePair<string, StringValues> keyValue in parsed)
57+
CommandCollection transformed = new();
58+
foreach (KeyValuePair<string, StringValues> pair in parsed)
5759
{
58-
transformed[keyValue.Key] = keyValue.Value.ToString();
60+
transformed.Add(new(pair.Key, pair.Value.ToString()));
5961
}
6062

6163
return transformed;

src/ImageSharp.Web/Commands/QueryCollectionRequestParser.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Apache License, Version 2.0.
33

4-
using System;
54
using System.Collections.Generic;
65
using Microsoft.AspNetCore.Http;
76
using Microsoft.AspNetCore.WebUtilities;
@@ -15,18 +14,20 @@ namespace SixLabors.ImageSharp.Web.Commands
1514
public sealed class QueryCollectionRequestParser : IRequestParser
1615
{
1716
/// <inheritdoc/>
18-
public IDictionary<string, string> ParseRequestCommands(HttpContext context)
17+
public CommandCollection ParseRequestCommands(HttpContext context)
1918
{
2019
if (context.Request.Query.Count == 0)
2120
{
22-
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
21+
return new();
2322
}
2423

24+
// TODO: Investigate skipping the double allocation here.
25+
// In .NET 6 we can directly use the QueryStringEnumerable type and enumerate stright to our command collection
2526
Dictionary<string, StringValues> parsed = QueryHelpers.ParseQuery(context.Request.QueryString.ToUriComponent());
26-
var transformed = new Dictionary<string, string>(parsed.Count, StringComparer.OrdinalIgnoreCase);
27+
CommandCollection transformed = new();
2728
foreach (KeyValuePair<string, StringValues> pair in parsed)
2829
{
29-
transformed[pair.Key] = pair.Value.ToString();
30+
transformed.Add(new(pair.Key, pair.Value.ToString()));
3031
}
3132

3233
return transformed;

src/ImageSharp.Web/Middleware/ImageCommandContext.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Apache License, Version 2.0.
33

4-
using System.Collections.Generic;
54
using System.Globalization;
65
using Microsoft.AspNetCore.Http;
76
using SixLabors.ImageSharp.Web.Commands;
@@ -22,7 +21,7 @@ public class ImageCommandContext
2221
/// <param name="culture">The culture used to parse commands.</param>
2322
public ImageCommandContext(
2423
HttpContext context,
25-
IDictionary<string, string> commands,
24+
CommandCollection commands,
2625
CommandParser parser,
2726
CultureInfo culture)
2827
{
@@ -38,9 +37,9 @@ public ImageCommandContext(
3837
public HttpContext Context { get; }
3938

4039
/// <summary>
41-
/// Gets the dictionary containing the collection of URI derived processing commands.
40+
/// Gets the collection of URI derived processing commands.
4241
/// </summary>
43-
public IDictionary<string, string> Commands { get; }
42+
public CommandCollection Commands { get; }
4443

4544
/// <summary>
4645
/// Gets the command parser for parsing URI derived processing commands.

src/ImageSharp.Web/Middleware/ImageProcessingContext.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Collections.Generic;
55
using System.IO;
66
using Microsoft.AspNetCore.Http;
7+
using SixLabors.ImageSharp.Web.Commands;
78

89
namespace SixLabors.ImageSharp.Web.Middleware
910
{
@@ -23,7 +24,7 @@ public class ImageProcessingContext
2324
public ImageProcessingContext(
2425
HttpContext context,
2526
Stream stream,
26-
IDictionary<string, string> commands,
27+
CommandCollection commands,
2728
string contentType,
2829
string extension)
2930
{
@@ -47,10 +48,10 @@ public ImageProcessingContext(
4748
/// <summary>
4849
/// Gets the parsed collection of processing commands.
4950
/// </summary>
50-
public IDictionary<string, string> Commands { get; }
51+
public CommandCollection Commands { get; }
5152

5253
/// <summary>
53-
/// Gets the content type for for the processed image.
54+
/// Gets the content type for the processed image.
5455
/// </summary>
5556
public string ContentType { get; }
5657

src/ImageSharp.Web/Middleware/ImageSharpMiddleware.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -172,18 +172,15 @@ public ImageSharpMiddleware(
172172
this.asyncKeyLock = asyncKeyLock;
173173
}
174174

175-
#pragma warning disable IDE1006 // Naming Styles
176175
/// <summary>
177176
/// Performs operations upon the current request.
178177
/// </summary>
179178
/// <param name="context">The current HTTP request context.</param>
180179
/// <returns>The <see cref="Task"/>.</returns>
181180
public async Task Invoke(HttpContext context)
182-
#pragma warning restore IDE1006 // Naming Styles
183181
{
184182
// We expect to get concrete collection type which removes virtual dispatch concerns and enumerator allocations
185-
IDictionary<string, string> parsedCommands = this.requestParser.ParseRequestCommands(context);
186-
Dictionary<string, string> commands = parsedCommands as Dictionary<string, string> ?? new Dictionary<string, string>(parsedCommands, StringComparer.OrdinalIgnoreCase);
183+
CommandCollection commands = this.requestParser.ParseRequestCommands(context);
187184

188185
if (commands.Count > 0)
189186
{
@@ -243,7 +240,7 @@ await this.ProcessRequestAsync(
243240
commands);
244241
}
245242

246-
private void StripUnknownCommands(Dictionary<string, string> commands, int startAtIndex)
243+
private void StripUnknownCommands(CommandCollection commands, int startAtIndex)
247244
{
248245
var keys = new List<string>(commands.Keys);
249246
for (int index = startAtIndex; index < keys.Count; index++)
@@ -260,7 +257,7 @@ private async Task ProcessRequestAsync(
260257
HttpContext context,
261258
IImageResolver sourceImageResolver,
262259
ImageContext imageContext,
263-
IDictionary<string, string> commands)
260+
CommandCollection commands)
264261
{
265262
// Create a cache key based on all the components of the requested url
266263
string uri = GetUri(context, commands);
@@ -511,7 +508,7 @@ private async Task SendResponseAsync(
511508
}
512509
}
513510

514-
private static string GetUri(HttpContext context, IDictionary<string, string> commands)
511+
private static string GetUri(HttpContext context, CommandCollection commands)
515512
{
516513
var sb = new StringBuilder(context.Request.Host.ToString());
517514

src/ImageSharp.Web/Processors/BackgroundColorWebProcessor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ private static readonly IEnumerable<string> ColorCommands
3232
public FormattedImage Process(
3333
FormattedImage image,
3434
ILogger logger,
35-
IDictionary<string, string> commands,
35+
CommandCollection commands,
3636
CommandParser parser,
3737
CultureInfo culture)
3838
{

src/ImageSharp.Web/Processors/FormatWebProcessor.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,15 @@ public FormatWebProcessor(IOptions<ImageSharpMiddlewareOptions> options)
4646
public FormattedImage Process(
4747
FormattedImage image,
4848
ILogger logger,
49-
IDictionary<string, string> commands,
49+
CommandCollection commands,
5050
CommandParser parser,
5151
CultureInfo culture)
5252
{
5353
string extension = commands.GetValueOrDefault(Format);
5454

5555
if (!string.IsNullOrWhiteSpace(extension))
5656
{
57-
IImageFormat format = this.options.Configuration
58-
.ImageFormatsManager.FindFormatByFileExtension(extension);
57+
IImageFormat format = this.options.Configuration.ImageFormatsManager.FindFormatByFileExtension(extension);
5958

6059
if (format != null)
6160
{

0 commit comments

Comments
 (0)