77using System . Reflection ;
88using Microsoft . AspNetCore . Routing ;
99using Microsoft . AspNetCore . Routing . Internal ;
10+ using Microsoft . AspNetCore . Routing . Patterns ;
1011
1112namespace Microsoft . AspNetCore . Builder
1213{
@@ -15,6 +16,12 @@ namespace Microsoft.AspNetCore.Builder
1516 /// </summary>
1617 public static class MapActionEndpointRouteBuilderExtensions
1718 {
19+ // Avoid creating a new array every call
20+ private static readonly string [ ] GetVerb = new [ ] { "GET" } ;
21+ private static readonly string [ ] PostVerb = new [ ] { "POST" } ;
22+ private static readonly string [ ] PutVerb = new [ ] { "PUT" } ;
23+ private static readonly string [ ] DeleteVerb = new [ ] { "DELETE" } ;
24+
1825 /// <summary>
1926 /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches the pattern specified via attributes.
2027 /// </summary>
@@ -76,5 +83,203 @@ public static MapActionEndpointConventionBuilder MapAction(
7683
7784 return new MapActionEndpointConventionBuilder ( conventionBuilders ) ;
7885 }
86+
87+ /// <summary>
88+ /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP GET requests
89+ /// for the specified pattern.
90+ /// </summary>
91+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
92+ /// <param name="pattern">The route pattern.</param>
93+ /// <param name="action">The delegate executed when the endpoint is matched.</param>
94+ /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
95+ public static MapActionEndpointConventionBuilder MapGet (
96+ this IEndpointRouteBuilder endpoints ,
97+ string pattern ,
98+ Delegate action )
99+ {
100+ return MapMethods ( endpoints , pattern , GetVerb , action ) ;
101+ }
102+
103+ /// <summary>
104+ /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP POST requests
105+ /// for the specified pattern.
106+ /// </summary>
107+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
108+ /// <param name="pattern">The route pattern.</param>
109+ /// <param name="action">The delegate executed when the endpoint is matched.</param>
110+ /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
111+ public static MapActionEndpointConventionBuilder MapPost (
112+ this IEndpointRouteBuilder endpoints ,
113+ string pattern ,
114+ Delegate action )
115+ {
116+ return MapMethods ( endpoints , pattern , PostVerb , action ) ;
117+ }
118+
119+ /// <summary>
120+ /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP PUT requests
121+ /// for the specified pattern.
122+ /// </summary>
123+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
124+ /// <param name="pattern">The route pattern.</param>
125+ /// <param name="action">The delegate executed when the endpoint is matched.</param>
126+ /// <returns>A <see cref="IEndpointConventionBuilder"/> that canaction be used to further customize the endpoint.</returns>
127+ public static MapActionEndpointConventionBuilder MapPut (
128+ this IEndpointRouteBuilder endpoints ,
129+ string pattern ,
130+ Delegate action )
131+ {
132+ return MapMethods ( endpoints , pattern , PutVerb , action ) ;
133+ }
134+
135+ /// <summary>
136+ /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP DELETE requests
137+ /// for the specified pattern.
138+ /// </summary>
139+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
140+ /// <param name="pattern">The route pattern.</param>
141+ /// <param name="action">The delegate executed when the endpoint is matched.</param>
142+ /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
143+ public static MapActionEndpointConventionBuilder MapDelete (
144+ this IEndpointRouteBuilder endpoints ,
145+ string pattern ,
146+ Delegate action )
147+ {
148+ return MapMethods ( endpoints , pattern , DeleteVerb , action ) ;
149+ }
150+
151+ /// <summary>
152+ /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
153+ /// for the specified HTTP methods and pattern.
154+ /// </summary>
155+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
156+ /// <param name="pattern">The route pattern.</param>
157+ /// <param name="action">The delegate executed when the endpoint is matched.</param>
158+ /// <param name="httpMethods">HTTP methods that the endpoint will match.</param>
159+ /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
160+ public static MapActionEndpointConventionBuilder MapMethods (
161+ this IEndpointRouteBuilder endpoints ,
162+ string pattern ,
163+ IEnumerable < string > httpMethods ,
164+ Delegate action )
165+ {
166+ if ( httpMethods is null )
167+ {
168+ throw new ArgumentNullException ( nameof ( httpMethods ) ) ;
169+ }
170+
171+ var displayName = $ "{ pattern } HTTP: { string . Join ( ", " , httpMethods ) } ";
172+ var builder = endpoints . Map ( RoutePatternFactory . Parse ( pattern ) , action , displayName ) ;
173+ builder . WithMetadata ( new HttpMethodMetadata ( httpMethods ) ) ;
174+ return builder ;
175+ }
176+
177+ /// <summary>
178+ /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
179+ /// for the specified pattern.
180+ /// </summary>
181+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
182+ /// <param name="pattern">The route pattern.</param>
183+ /// <param name="action">The delegate executed when the endpoint is matched.</param>
184+ /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
185+ public static MapActionEndpointConventionBuilder Map (
186+ this IEndpointRouteBuilder endpoints ,
187+ string pattern ,
188+ Delegate action )
189+ {
190+ return Map ( endpoints , RoutePatternFactory . Parse ( pattern ) , action ) ;
191+ }
192+
193+ /// <summary>
194+ /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
195+ /// for the specified pattern.
196+ /// </summary>
197+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
198+ /// <param name="pattern">The route pattern.</param>
199+ /// <param name="action">The delegate executed when the endpoint is matched.</param>
200+ /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
201+ public static MapActionEndpointConventionBuilder Map (
202+ this IEndpointRouteBuilder endpoints ,
203+ RoutePattern pattern ,
204+ Delegate action )
205+ {
206+ return Map ( endpoints , pattern , action , displayName : null ) ;
207+ }
208+
209+ private static MapActionEndpointConventionBuilder Map (
210+ this IEndpointRouteBuilder endpoints ,
211+ RoutePattern pattern ,
212+ Delegate action ,
213+ string ? displayName )
214+ {
215+ if ( endpoints is null )
216+ {
217+ throw new ArgumentNullException ( nameof ( endpoints ) ) ;
218+ }
219+
220+ if ( pattern is null )
221+ {
222+ throw new ArgumentNullException ( nameof ( pattern ) ) ;
223+ }
224+
225+ if ( action is null )
226+ {
227+ throw new ArgumentNullException ( nameof ( action ) ) ;
228+ }
229+
230+ const int defaultOrder = 0 ;
231+
232+ var builder = new RouteEndpointBuilder (
233+ MapActionExpressionTreeBuilder . BuildRequestDelegate ( action ) ,
234+ pattern ,
235+ defaultOrder )
236+ {
237+ DisplayName = pattern . RawText ?? pattern . DebuggerToString ( ) ,
238+ } ;
239+
240+ // Add delegate attributes as metadata
241+ var attributes = action . Method . GetCustomAttributes ( ) ;
242+ string ? routeName = null ;
243+ int ? routeOrder = null ;
244+
245+ // This can be null if the delegate is a dynamic method or compiled from an expression tree
246+ if ( attributes is not null )
247+ {
248+ foreach ( var attribute in attributes )
249+ {
250+ if ( attribute is IRoutePatternMetadata patternMetadata && patternMetadata . RoutePattern is not null )
251+ {
252+ throw new InvalidOperationException ( $ "'{ attribute . GetType ( ) } ' implements { nameof ( IRoutePatternMetadata ) } which is not supported by this method.") ;
253+ }
254+ if ( attribute is IHttpMethodMetadata methodMetadata && methodMetadata . HttpMethods . Any ( ) )
255+ {
256+ throw new InvalidOperationException ( $ "'{ attribute . GetType ( ) } ' implements { nameof ( IHttpMethodMetadata ) } which is not supported by this method.") ;
257+ }
258+
259+ if ( attribute is IRouteNameMetadata nameMetadata && nameMetadata . RouteName is string name )
260+ {
261+ routeName = name ;
262+ }
263+ if ( attribute is IRouteOrderMetadata orderMetadata && orderMetadata . RouteOrder is int order )
264+ {
265+ routeOrder = order ;
266+ }
267+
268+ builder . Metadata . Add ( attribute ) ;
269+ }
270+ }
271+
272+ builder . DisplayName = routeName ?? displayName ?? builder . DisplayName ;
273+ builder . Order = routeOrder ?? defaultOrder ;
274+
275+ var dataSource = endpoints . DataSources . OfType < ModelEndpointDataSource > ( ) . FirstOrDefault ( ) ;
276+ if ( dataSource is null )
277+ {
278+ dataSource = new ModelEndpointDataSource ( ) ;
279+ endpoints . DataSources . Add ( dataSource ) ;
280+ }
281+
282+ return new MapActionEndpointConventionBuilder ( dataSource . AddEndpointBuilder ( builder ) ) ;
283+ }
79284 }
80285}
0 commit comments