Skip to content

Commit 5250465

Browse files
Add complex media type API version reader support. Resolves #887
1 parent d4b6cfc commit 5250465

File tree

9 files changed

+1497
-0
lines changed

9 files changed

+1497
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
3+
namespace Asp.Versioning;
4+
5+
using Asp.Versioning.Routing;
6+
using System.Globalization;
7+
using System.Net.Http.Headers;
8+
using System.Web.Http.Routing;
9+
10+
/// <content>
11+
/// Provides additional implementation specific to ASP.NET Web API.
12+
/// </content>
13+
public partial class MediaTypeApiVersionReaderBuilder
14+
{
15+
/// <summary>
16+
/// Adds a template used to read an API version from a media type.
17+
/// </summary>
18+
/// <param name="template">The template used to match the media type.</param>
19+
/// <param name="parameterName">The optional name of the API version parameter in the template.
20+
/// If a value is not specified, there is expected to be a single template parameter.</param>
21+
/// <returns>The current <see cref="MediaTypeApiVersionReaderBuilder"/>.</returns>
22+
/// <remarks>The template syntax is the same used by route templates; however, constraints are not supported.</remarks>
23+
#pragma warning disable CA1716 // Identifiers should not match keywords
24+
public virtual MediaTypeApiVersionReaderBuilder Template( string template, string? parameterName = default )
25+
#pragma warning restore CA1716 // Identifiers should not match keywords
26+
{
27+
if ( string.IsNullOrEmpty( template ) )
28+
{
29+
throw new ArgumentNullException( nameof( template ) );
30+
}
31+
32+
if ( string.IsNullOrEmpty( parameterName ) )
33+
{
34+
var parser = new RouteParser();
35+
var parsedRoute = parser.Parse( template );
36+
var parameters = from content in parsedRoute.PathSegments.OfType<IPathContentSegment>()
37+
from parameter in content.Subsegments.OfType<IPathParameterSubsegment>()
38+
select parameter;
39+
40+
if ( parameters.Count() > 1 )
41+
{
42+
var message = string.Format( CultureInfo.CurrentCulture, CommonSR.InvalidMediaTypeTemplate, template );
43+
throw new ArgumentException( message, nameof( template ) );
44+
}
45+
}
46+
47+
var route = new HttpRoute( template );
48+
49+
AddReader( mediaTypes => ReadMediaTypePattern( mediaTypes, route, parameterName ) );
50+
51+
return this;
52+
}
53+
54+
private static IReadOnlyList<string> ReadMediaTypePattern(
55+
IReadOnlyList<MediaTypeHeaderValue> mediaTypes,
56+
HttpRoute route,
57+
string? parameterName )
58+
{
59+
var assumeOneParameter = string.IsNullOrEmpty( parameterName );
60+
var version = default( string );
61+
var versions = default( List<string> );
62+
using var request = new HttpRequestMessage();
63+
64+
for ( var i = 0; i < mediaTypes.Count; i++ )
65+
{
66+
var mediaType = mediaTypes[i].MediaType;
67+
request.RequestUri = new Uri( "http://localhost/" + mediaType );
68+
var data = route.GetRouteData( string.Empty, request );
69+
70+
if ( data == null )
71+
{
72+
continue;
73+
}
74+
75+
var values = data.Values;
76+
77+
if ( values.Count == 0 )
78+
{
79+
continue;
80+
}
81+
82+
object datum;
83+
84+
if ( assumeOneParameter )
85+
{
86+
datum = values.Values.First();
87+
}
88+
else if ( !values.TryGetValue( parameterName, out datum ) )
89+
{
90+
continue;
91+
}
92+
93+
if ( datum is not string value || string.IsNullOrEmpty( value ) )
94+
{
95+
continue;
96+
}
97+
98+
if ( version == null )
99+
{
100+
version = value;
101+
}
102+
else if ( versions == null )
103+
{
104+
versions = new( capacity: mediaTypes.Count - i + 1 )
105+
{
106+
version,
107+
value,
108+
};
109+
}
110+
else
111+
{
112+
versions.Add( value );
113+
}
114+
}
115+
116+
if ( version is null )
117+
{
118+
return Array.Empty<string>();
119+
}
120+
121+
return versions is null ? new[] { version } : versions.ToArray();
122+
}
123+
}

0 commit comments

Comments
 (0)