1
+ using Microsoft . AspNetCore . Http ;
2
+ using Microsoft . AspNetCore . Mvc ;
3
+ using Microsoft . AspNetCore . Mvc . ActionConstraints ;
4
+ using Microsoft . AspNetCore . Mvc . ApplicationModels ;
5
+ using Microsoft . AspNetCore . Mvc . ModelBinding ;
6
+ using System . Text ;
7
+
8
+ namespace Sample . DynamicWebApi ;
9
+
10
+ public class ApplicationServiceConvention : IApplicationModelConvention
11
+ {
12
+ public void Apply ( ApplicationModel application )
13
+ {
14
+ foreach ( var controller in application . Controllers )
15
+ {
16
+ if ( typeof ( IApplicationService ) . IsAssignableFrom ( controller . ControllerType ) )
17
+ {
18
+ ConfigureApplicationService ( controller ) ;
19
+ }
20
+ }
21
+ }
22
+
23
+ private void ConfigureApplicationService ( ControllerModel controller )
24
+ {
25
+ ConfigureApiExplorer ( controller ) ;
26
+ ConfigureSelector ( controller ) ;
27
+ ConfigureParameters ( controller ) ;
28
+ }
29
+
30
+ private void ConfigureApiExplorer ( ControllerModel controller )
31
+ {
32
+ if ( ! controller . ApiExplorer . IsVisible . HasValue )
33
+ {
34
+ controller . ApiExplorer . IsVisible = true ;
35
+ }
36
+
37
+ foreach ( var action in controller . Actions )
38
+ {
39
+ if ( ! action . ApiExplorer . IsVisible . HasValue )
40
+ {
41
+ action . ApiExplorer . IsVisible = true ;
42
+ }
43
+ }
44
+ }
45
+
46
+ private void ConfigureSelector ( ControllerModel controller )
47
+ {
48
+ RemoveEmptySelectors ( controller . Selectors ) ;
49
+
50
+ if ( controller . Selectors . Any ( temp => temp . AttributeRouteModel != null ) )
51
+ {
52
+ return ;
53
+ }
54
+
55
+ foreach ( var action in controller . Actions )
56
+ {
57
+ ConfigureSelector ( action ) ;
58
+ }
59
+ }
60
+
61
+ private void ConfigureSelector ( ActionModel action )
62
+ {
63
+ RemoveEmptySelectors ( action . Selectors ) ;
64
+
65
+ if ( action . Selectors . Count <= 0 )
66
+ {
67
+ AddApplicationServiceSelector ( action ) ;
68
+ }
69
+ else
70
+ {
71
+ NormalizeSelectorRoutes ( action ) ;
72
+ }
73
+ }
74
+
75
+ private void ConfigureParameters ( ControllerModel controller )
76
+ {
77
+ foreach ( var action in controller . Actions )
78
+ {
79
+ foreach ( var parameter in action . Parameters )
80
+ {
81
+ if ( parameter . BindingInfo != null )
82
+ {
83
+ continue ;
84
+ }
85
+
86
+ if ( parameter . ParameterType . IsClass &&
87
+ parameter . ParameterType != typeof ( string ) &&
88
+ parameter . ParameterType != typeof ( IFormFile ) )
89
+ {
90
+ var httpMethods = action . Selectors . SelectMany ( temp => temp . ActionConstraints ) . OfType < HttpMethodActionConstraint > ( ) . SelectMany ( temp => temp . HttpMethods ) . ToList ( ) ;
91
+ if ( httpMethods . Contains ( "GET" ) ||
92
+ httpMethods . Contains ( "DELETE" ) ||
93
+ httpMethods . Contains ( "TRACE" ) ||
94
+ httpMethods . Contains ( "HEAD" ) )
95
+ {
96
+ continue ;
97
+ }
98
+
99
+ parameter . BindingInfo = BindingInfo . GetBindingInfo ( new [ ] { new FromBodyAttribute ( ) } ) ;
100
+ }
101
+ }
102
+ }
103
+ }
104
+
105
+ private void NormalizeSelectorRoutes ( ActionModel action )
106
+ {
107
+ foreach ( var selector in action . Selectors )
108
+ {
109
+ if ( selector . AttributeRouteModel == null )
110
+ {
111
+ selector . AttributeRouteModel = new AttributeRouteModel ( new RouteAttribute ( CalculateRouteTemplate ( action ) ) ) ;
112
+ }
113
+
114
+ if ( selector . ActionConstraints . OfType < HttpMethodActionConstraint > ( ) . FirstOrDefault ( ) ? . HttpMethods ? . FirstOrDefault ( ) == null )
115
+ {
116
+ selector . ActionConstraints . Add ( new HttpMethodActionConstraint ( new [ ] { GetHttpMethod ( action ) } ) ) ;
117
+ }
118
+ }
119
+ }
120
+
121
+ private void AddApplicationServiceSelector ( ActionModel action )
122
+ {
123
+ var selector = new SelectorModel ( ) ;
124
+ selector . AttributeRouteModel = new AttributeRouteModel ( new RouteAttribute ( CalculateRouteTemplate ( action ) ) ) ;
125
+ selector . ActionConstraints . Add ( new HttpMethodActionConstraint ( new [ ] { GetHttpMethod ( action ) } ) ) ;
126
+
127
+ action . Selectors . Add ( selector ) ;
128
+ }
129
+
130
+ private string CalculateRouteTemplate ( ActionModel action )
131
+ {
132
+ var routeTemplate = new StringBuilder ( ) ;
133
+ routeTemplate . Append ( "api" ) ;
134
+
135
+ // 控制器名称部分
136
+ var controllerName = action . Controller . ControllerName ;
137
+ if ( controllerName . EndsWith ( "ApplicationService" ) )
138
+ {
139
+ controllerName = controllerName . Substring ( 0 , controllerName . Length - "ApplicationService" . Length ) ;
140
+ }
141
+ else if ( controllerName . EndsWith ( "AppService" ) )
142
+ {
143
+ controllerName = controllerName . Substring ( 0 , controllerName . Length - "AppService" . Length ) ;
144
+ }
145
+ controllerName += "s" ;
146
+ routeTemplate . Append ( $ "/{ controllerName } ") ;
147
+
148
+ // id 部分
149
+ if ( action . Parameters . Any ( temp => temp . ParameterName == "id" ) )
150
+ {
151
+ routeTemplate . Append ( "/{id}" ) ;
152
+ }
153
+
154
+ // Action 名称部分
155
+ var actionName = action . ActionName ;
156
+ if ( actionName . EndsWith ( "Async" ) )
157
+ {
158
+ actionName = actionName . Substring ( 0 , actionName . Length - "Async" . Length ) ;
159
+ }
160
+ var trimPrefixes = new [ ]
161
+ {
162
+ "GetAll" , "GetList" , "Get" ,
163
+ "Post" , "Create" , "Add" , "Insert" ,
164
+ "Put" , "Update" ,
165
+ "Delete" , "Remove" ,
166
+ "Patch"
167
+ } ;
168
+ foreach ( var trimPrefix in trimPrefixes )
169
+ {
170
+ if ( actionName . StartsWith ( trimPrefix ) )
171
+ {
172
+ actionName = actionName . Substring ( trimPrefix . Length ) ;
173
+ break ;
174
+ }
175
+ }
176
+ if ( ! string . IsNullOrEmpty ( actionName ) )
177
+ {
178
+ routeTemplate . Append ( $ "/{ actionName } ") ;
179
+ }
180
+
181
+ return routeTemplate . ToString ( ) ;
182
+ }
183
+
184
+ private string GetHttpMethod ( ActionModel action )
185
+ {
186
+ var actionName = action . ActionName ;
187
+ if ( actionName . StartsWith ( "Get" ) )
188
+ {
189
+ return "GET" ;
190
+ }
191
+
192
+ if ( actionName . StartsWith ( "Put" ) || actionName . StartsWith ( "Update" ) )
193
+ {
194
+ return "PUT" ;
195
+ }
196
+
197
+ if ( actionName . StartsWith ( "Delete" ) || actionName . StartsWith ( "Remove" ) )
198
+ {
199
+ return "DELETE" ;
200
+ }
201
+
202
+ if ( actionName . StartsWith ( "Patch" ) )
203
+ {
204
+ return "PATCH" ;
205
+ }
206
+
207
+ return "POST" ;
208
+ }
209
+
210
+ private void RemoveEmptySelectors ( IList < SelectorModel > selectors )
211
+ {
212
+ for ( var i = selectors . Count - 1 ; i >= 0 ; i -- )
213
+ {
214
+ var selector = selectors [ i ] ;
215
+ if ( selector . AttributeRouteModel == null &&
216
+ ( selector . ActionConstraints == null || selector . ActionConstraints . Count <= 0 ) &&
217
+ ( selector . EndpointMetadata == null || selector . EndpointMetadata . Count <= 0 ) )
218
+ {
219
+ selectors . Remove ( selector ) ;
220
+ }
221
+ }
222
+ }
223
+ }
0 commit comments