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