Skip to content

Commit 1192e51

Browse files
Chris Martinezcommonsensesoftware
authored andcommitted
Final refactoring for V1 Web API implementation
1 parent bb326be commit 1192e51

File tree

7 files changed

+112
-66
lines changed

7 files changed

+112
-66
lines changed

src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/Description/ApiDescriptionComparer.cs

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,24 @@
55
using System.Web.Http.Description;
66
using static System.StringComparison;
77

8-
sealed class ApiDescriptionComparer : IEqualityComparer<ApiDescription>, IEqualityComparer<VersionedApiDescription>, IComparer<VersionedApiDescription>
8+
/// <summary>
9+
/// Represents an object that compares <see cref="ApiDescription">API Descriptions</see><seealso cref="VersionedApiDescription"/>.
10+
/// </summary>
11+
public class ApiDescriptionComparer :
12+
IEqualityComparer<ApiDescription>,
13+
IEqualityComparer<VersionedApiDescription>,
14+
IComparer<ApiDescription>,
15+
IComparer<VersionedApiDescription>
916
{
1017
readonly StringComparer comparer = StringComparer.OrdinalIgnoreCase;
1118

12-
internal static ApiDescriptionComparer Instance { get; } = new ApiDescriptionComparer();
13-
14-
public bool Equals( VersionedApiDescription x, VersionedApiDescription y )
19+
/// <summary>
20+
/// Determines whether the two <see cref="VersionedApiDescription">API descriptions</see> are equal.
21+
/// </summary>
22+
/// <param name="x">The <see cref="VersionedApiDescription">API descriptions</see> to compare.</param>
23+
/// <param name="y">The <see cref="VersionedApiDescription">API descriptions</see> to compare against.</param>
24+
/// <returns>True if the two API descriptions are equal; otherwise, false.</returns>
25+
public virtual bool Equals( VersionedApiDescription x, VersionedApiDescription y )
1526
{
1627
if ( x == null )
1728
{
@@ -22,15 +33,20 @@ public bool Equals( VersionedApiDescription x, VersionedApiDescription y )
2233
return false;
2334
}
2435

25-
if ( string.Equals( x.UniqueID, y.UniqueID, OrdinalIgnoreCase ) )
36+
if ( string.Equals( x.ID, y.ID, OrdinalIgnoreCase ) )
2637
{
2738
return x.ApiVersion == y.ApiVersion;
2839
}
2940

3041
return false;
3142
}
3243

33-
public int GetHashCode( VersionedApiDescription obj )
44+
/// <summary>
45+
/// Returns a hash code for the especified <see cref="VersionedApiDescription">API description</see>.
46+
/// </summary>
47+
/// <param name="obj">The object to get a hash code for.</param>
48+
/// <returns>The hash code of the specified object.</returns>
49+
public virtual int GetHashCode( VersionedApiDescription obj )
3450
{
3551
if ( obj == null )
3652
{
@@ -50,7 +66,13 @@ public int GetHashCode( VersionedApiDescription obj )
5066
return ( hash * 397 ) ^ apiVersion?.GetHashCode() ?? 0;
5167
}
5268

53-
public bool Equals( ApiDescription x, ApiDescription y )
69+
/// <summary>
70+
/// Determines whether the two <see cref="ApiDescription">API descriptions</see> are equal.
71+
/// </summary>
72+
/// <param name="x">The <see cref="ApiDescription">API descriptions</see> to compare.</param>
73+
/// <param name="y">The <see cref="ApiDescription">API descriptions</see> to compare against.</param>
74+
/// <returns>True if the two API descriptions are equal; otherwise, false.</returns>
75+
public virtual bool Equals( ApiDescription x, ApiDescription y )
5476
{
5577
var id1 = default( string );
5678
var id2 = default( string );
@@ -70,13 +92,13 @@ public bool Equals( ApiDescription x, ApiDescription y )
7092
return Equals( x1, y1 );
7193
}
7294

73-
id1 = x1.UniqueID;
95+
id1 = x1.GetUniqueID();
7496
id2 = y.ID;
7597
}
7698
else if ( y is VersionedApiDescription y1 )
7799
{
78100
id1 = x.ID;
79-
id2 = y1.UniqueID;
101+
id2 = y1.GetUniqueID();
80102
}
81103
else
82104
{
@@ -87,7 +109,12 @@ public bool Equals( ApiDescription x, ApiDescription y )
87109
return string.Equals( id1, id2, OrdinalIgnoreCase );
88110
}
89111

90-
public int GetHashCode( ApiDescription obj )
112+
/// <summary>
113+
/// Returns a hash code for the especified <see cref="ApiDescription">API description</see>.
114+
/// </summary>
115+
/// <param name="obj">The object to get a hash code for.</param>
116+
/// <returns>The hash code of the specified object.</returns>
117+
public virtual int GetHashCode( ApiDescription obj )
91118
{
92119
if ( obj is VersionedApiDescription other )
93120
{
@@ -99,7 +126,14 @@ public int GetHashCode( ApiDescription obj )
99126
return id == null ? 0 : comparer.GetHashCode( id );
100127
}
101128

102-
public int Compare( VersionedApiDescription x, VersionedApiDescription y )
129+
/// <summary>
130+
/// Compares two <see cref="VersionedApiDescription">API descriptions</see>.
131+
/// </summary>
132+
/// <param name="x">The <see cref="VersionedApiDescription">API descriptions</see> to compare.</param>
133+
/// <param name="y">The <see cref="VersionedApiDescription">API descriptions</see> to compare against.</param>
134+
/// <returns>0 if the objects are equal, 1 if <paramref name="x"/> is greater than <paramref name="y"/>,
135+
/// or -1 if <paramref name="x"/> is less than <paramref name="y"/>.</returns>
136+
public virtual int Compare( VersionedApiDescription x, VersionedApiDescription y )
103137
{
104138
if ( x == null )
105139
{
@@ -125,6 +159,34 @@ public int Compare( VersionedApiDescription x, VersionedApiDescription y )
125159
return result;
126160
}
127161

162+
/// <summary>
163+
/// Compares two <see cref="ApiDescription">API descriptions</see>.
164+
/// </summary>
165+
/// <param name="x">The <see cref="ApiDescription">API descriptions</see> to compare.</param>
166+
/// <param name="y">The <see cref="ApiDescription">API descriptions</see> to compare against.</param>
167+
/// <returns>0 if the objects are equal, 1 if <paramref name="x"/> is greater than <paramref name="y"/>,
168+
/// or -1 if <paramref name="x"/> is less than <paramref name="y"/>.</returns>
169+
public virtual int Compare( ApiDescription x, ApiDescription y )
170+
{
171+
if ( x == null )
172+
{
173+
return y == null ? 0 : -1;
174+
}
175+
else if ( y == null )
176+
{
177+
return 1;
178+
}
179+
180+
var result = CompareStrings( x.HttpMethod?.Method, y.HttpMethod?.Method );
181+
182+
if ( result == 0 )
183+
{
184+
result = CompareStrings( x.RelativePath, y.RelativePath );
185+
}
186+
187+
return result;
188+
}
189+
128190
int CompareStrings( string string1, string string2 )
129191
{
130192
if ( string1 == null )

src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/Description/VersionedApiDescription.cs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
namespace Microsoft.Web.Http.Description
22
{
33
using System;
4-
using System.ComponentModel;
54
using System.Diagnostics;
65
using System.Linq.Expressions;
76
using System.Web.Http.Description;
8-
using static System.ComponentModel.EditorBrowsableState;
97

108
/// <summary>
119
/// Represents a versioned API description.
@@ -16,20 +14,6 @@ public class VersionedApiDescription : ApiDescription
1614
static readonly Lazy<Action<ApiDescription, ResponseDescription>> setResponseDescription =
1715
new Lazy<Action<ApiDescription, ResponseDescription>>( CreateSetResponseDescriptionMutator );
1816

19-
/// <summary>
20-
/// Gets the unique API description identifier.
21-
/// </summary>
22-
/// <value>The unique identifier of the API description.</value>
23-
/// <remarks>This property should be preferred over <see cref="ID"/> because it will be unique by version.</remarks>
24-
public virtual string UniqueID => $"{base.ID}-{ApiVersion}";
25-
26-
/// <summary>
27-
/// Gets the unique API description identifier.
28-
/// </summary>
29-
/// <value>The unique identifier of the API description.</value>
30-
[EditorBrowsable( Never )]
31-
new public string ID => UniqueID;
32-
3317
/// <summary>
3418
/// Gets or sets the name of the group for the API description.
3519
/// </summary>

src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/Description/VersionedApiExplorer.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,8 @@ public VersionedApiExplorer( HttpConfiguration configuration )
6161
/// <summary>
6262
/// Gets the comparer used to compare API descriptions.
6363
/// </summary>
64-
/// <value>An <see cref="IEqualityComparer{T}">equality comparer</see> for <see cref="ApiDescription">API descriptions</see>.</value>
65-
/// <remarks>The defined <see cref="IEqualityComparer{T}">equality comparer</see> should expect to handle instances of
66-
/// type <see cref="VersionedApiDescription"/>.</remarks>
67-
protected virtual IEqualityComparer<ApiDescription> Comparer { get; } = ApiDescriptionComparer.Instance;
64+
/// <value>A <see cref="ApiDescriptionComparer">comparer</see> for <see cref="ApiDescription">API descriptions</see>.</value>
65+
protected virtual ApiDescriptionComparer Comparer { get; } = new ApiDescriptionComparer();
6866

6967
/// <summary>
7068
/// Gets the object used to parse routes.
@@ -310,7 +308,7 @@ protected virtual void SortApiDescriptionGroup( ApiDescriptionGroup apiDescripti
310308

311309
var items = apiDescriptionGroup.ApiDescriptions.ToArray();
312310

313-
Array.Sort( items, ApiDescriptionComparer.Instance );
311+
Array.Sort( items, Comparer );
314312

315313
apiDescriptionGroup.ApiDescriptions.Clear();
316314
apiDescriptionGroup.ApiDescriptions.AddRange( items );
@@ -943,7 +941,7 @@ static Collection<VersionedApiDescription> RemoveInvalidApiDescriptions( Collect
943941

944942
foreach ( var description in apiDescriptions )
945943
{
946-
var apiDescriptionId = description.UniqueID;
944+
var apiDescriptionId = description.GetUniqueID();
947945

948946
if ( visitedApiDescriptionIds.Contains( apiDescriptionId ) )
949947
{
@@ -959,7 +957,7 @@ static Collection<VersionedApiDescription> RemoveInvalidApiDescriptions( Collect
959957

960958
foreach ( var apiDescription in apiDescriptions )
961959
{
962-
var apiDescriptionId = apiDescription.UniqueID;
960+
var apiDescriptionId = apiDescription.GetUniqueID();
963961

964962
if ( !duplicateApiDescriptionIds.Contains( apiDescriptionId ) )
965963
{

src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/Routing/IPathContentSegment.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace Microsoft.Web.Http.Routing
22
{
33
using System.Collections.Generic;
4+
45
/// <summary>
56
/// Defines the behavior of a path content segment.
67
/// </summary>

src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/System.Web.Http/Description/ApiDescriptionExtensions.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
{
33
using Microsoft;
44
using Microsoft.Web.Http.Description;
5+
using System.Diagnostics.Contracts;
56

67
/// <summary>
78
/// Provides extension methods for the <see cref="ApiDescription"/> class.
@@ -26,5 +27,25 @@ public static string GetGroupName( this ApiDescription apiDescription )
2627

2728
return null;
2829
}
30+
31+
/// <summary>
32+
/// Gets the unique API description identifier.
33+
/// </summary>
34+
/// <value>The unique identifier of the API description.</value>
35+
/// <remarks>If the <paramref name="apiDescription">API description</paramref> is of type <see cref="VersionedApiDescription"/>
36+
/// the return value will be in the format of "{<see cref="ApiDescription.ID"/>}-{<see cref="VersionedApiDescription.ApiVersion"/>}";
37+
/// otherwise, the return value will be "{<see cref="ApiDescription.ID"/>}".</remarks>
38+
public static string GetUniqueID( this ApiDescription apiDescription )
39+
{
40+
Arg.NotNull( apiDescription, nameof( apiDescription ) );
41+
Contract.Ensures( !string.IsNullOrEmpty( Contract.Result<string>() ) );
42+
43+
if ( apiDescription is VersionedApiDescription versionedApiDescription )
44+
{
45+
return $"{versionedApiDescription.ID}-{versionedApiDescription.ApiVersion}";
46+
}
47+
48+
return apiDescription.ID;
49+
}
2950
}
3051
}

test/Microsoft.AspNet.WebApi.Versioning.ApiExplorer.Tests/Description/VersionedApiDescriptionTest.cs

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,11 @@
11
namespace Microsoft.Web.Http.Description
22
{
33
using FluentAssertions;
4-
using System;
54
using System.Web.Http.Description;
65
using Xunit;
7-
using static System.Net.Http.HttpMethod;
86

97
public class VersionedApiDescriptionTest
108
{
11-
[Fact]
12-
public void shadowed_ID_property_should_return_expected_value()
13-
{
14-
// arrange
15-
var apiDescription = new VersionedApiDescription()
16-
{
17-
HttpMethod = Get,
18-
RelativePath = "Values",
19-
ApiVersion = new ApiVersion( 1, 0 )
20-
};
21-
22-
// act
23-
var id = apiDescription.ID;
24-
25-
// assert
26-
id.Should().Be( "GETValues-1.0" );
27-
}
28-
299
[Fact]
3010
public void shadowed_ResponseDescription_property_should_set_internal_value()
3111
{

test/Microsoft.AspNet.WebApi.Versioning.ApiExplorer.Tests/Description/VersionedApiExplorerTest.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ public void api_description_group_should_explore_v1_actions( HttpConfiguration c
293293
description.ShouldBeEquivalentTo(
294294
new
295295
{
296-
ID = $"GET{relativePath}-1.0",
296+
ID = $"GET{relativePath}",
297297
HttpMethod = Get,
298298
RelativePath = relativePath,
299299
Version = apiVersion
@@ -320,14 +320,14 @@ public void api_description_group_should_explore_v2_actions( HttpConfiguration c
320320
{
321321
new
322322
{
323-
ID = $"GET{relativePaths[0]}-2.0",
323+
ID = $"GET{relativePaths[0]}",
324324
HttpMethod = Get,
325325
RelativePath = relativePaths[0],
326326
Version = apiVersion
327327
},
328328
new
329329
{
330-
ID = $"GET{relativePaths[1]}-2.0",
330+
ID = $"GET{relativePaths[1]}",
331331
HttpMethod = Get,
332332
RelativePath = relativePaths[1],
333333
Version = apiVersion
@@ -355,21 +355,21 @@ public void api_description_group_should_explore_v3_actions( HttpConfiguration c
355355
{
356356
new
357357
{
358-
ID = $"GET{relativePaths[0]}-3.0",
358+
ID = $"GET{relativePaths[0]}",
359359
HttpMethod = Get,
360360
RelativePath = relativePaths[0],
361361
Version = apiVersion
362362
},
363363
new
364364
{
365-
ID = $"GET{relativePaths[1]}-3.0",
365+
ID = $"GET{relativePaths[1]}",
366366
HttpMethod = Get,
367367
RelativePath = relativePaths[1],
368368
Version = apiVersion
369369
},
370370
new
371371
{
372-
ID = $"POST{relativePaths[2]}-3.0",
372+
ID = $"POST{relativePaths[2]}",
373373
HttpMethod = Post,
374374
RelativePath = relativePaths[2],
375375
Version = apiVersion
@@ -398,14 +398,14 @@ public void api_description_group_should_explore_v3_beta_actions( HttpConfigurat
398398
{
399399
new
400400
{
401-
ID = $"GET{relativePaths[0]}-3.0-beta",
401+
ID = $"GET{relativePaths[0]}",
402402
HttpMethod = Get,
403403
RelativePath = relativePaths[0],
404404
Version = apiVersion
405405
},
406406
new
407407
{
408-
ID = $"GET{relativePaths[1]}-3.0-beta",
408+
ID = $"GET{relativePaths[1]}",
409409
HttpMethod = Get,
410410
RelativePath = relativePaths[1],
411411
Version = apiVersion
@@ -433,28 +433,28 @@ public void api_description_group_should_explore_v4_actions( HttpConfiguration c
433433
{
434434
new
435435
{
436-
ID = $"GET{relativePaths[0]}-4.0",
436+
ID = $"GET{relativePaths[0]}",
437437
HttpMethod = Get,
438438
RelativePath = relativePaths[0],
439439
Version = apiVersion
440440
},
441441
new
442442
{
443-
ID = $"GET{relativePaths[1]}-4.0",
443+
ID = $"GET{relativePaths[1]}",
444444
HttpMethod = Get,
445445
RelativePath = relativePaths[1],
446446
Version = apiVersion
447447
},
448448
new
449449
{
450-
ID = $"POST{relativePaths[2]}-4.0",
450+
ID = $"POST{relativePaths[2]}",
451451
HttpMethod = Post,
452452
RelativePath = relativePaths[2],
453453
Version = apiVersion
454454
},
455455
new
456456
{
457-
ID = $"DELETE{relativePaths[3]}-4.0",
457+
ID = $"DELETE{relativePaths[3]}",
458458
HttpMethod = Delete,
459459
RelativePath = relativePaths[3],
460460
Version = apiVersion

0 commit comments

Comments
 (0)