Skip to content

Commit 1dd6a66

Browse files
committed
添加项目文件。
1 parent ba09ac2 commit 1dd6a66

15 files changed

+972
-0
lines changed

.gitignore

Lines changed: 454 additions & 0 deletions
Large diffs are not rendered by default.

Sample.DynamicWebApi.sln

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.30114.105
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.DynamicWebApi", "Sample.DynamicWebApi\Sample.DynamicWebApi.csproj", "{966D3F6E-B2AD-4D38-B79D-24891A92C6A0}"
7+
EndProject
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.WebApi", "Sample.WebApi\Sample.WebApi.csproj", "{DBA36000-BAE1-400B-BECE-1EB012F5C413}"
9+
EndProject
10+
Global
11+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
12+
Debug|Any CPU = Debug|Any CPU
13+
Release|Any CPU = Release|Any CPU
14+
EndGlobalSection
15+
GlobalSection(SolutionProperties) = preSolution
16+
HideSolutionNode = FALSE
17+
EndGlobalSection
18+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
19+
{966D3F6E-B2AD-4D38-B79D-24891A92C6A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
20+
{966D3F6E-B2AD-4D38-B79D-24891A92C6A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
21+
{966D3F6E-B2AD-4D38-B79D-24891A92C6A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
22+
{966D3F6E-B2AD-4D38-B79D-24891A92C6A0}.Release|Any CPU.Build.0 = Release|Any CPU
23+
{DBA36000-BAE1-400B-BECE-1EB012F5C413}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24+
{DBA36000-BAE1-400B-BECE-1EB012F5C413}.Debug|Any CPU.Build.0 = Debug|Any CPU
25+
{DBA36000-BAE1-400B-BECE-1EB012F5C413}.Release|Any CPU.ActiveCfg = Release|Any CPU
26+
{DBA36000-BAE1-400B-BECE-1EB012F5C413}.Release|Any CPU.Build.0 = Release|Any CPU
27+
EndGlobalSection
28+
EndGlobal
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using Microsoft.AspNetCore.Mvc.Controllers;
2+
using System.Reflection;
3+
4+
namespace Sample.DynamicWebApi;
5+
6+
public class ApplicationServiceControllerFeatureProvider : ControllerFeatureProvider
7+
{
8+
protected override bool IsController(TypeInfo typeInfo)
9+
{
10+
if (typeof(IApplicationService).IsAssignableFrom(typeInfo))
11+
{
12+
if (!typeInfo.IsInterface &&
13+
!typeInfo.IsAbstract &&
14+
!typeInfo.IsGenericType &&
15+
typeInfo.IsPublic)
16+
{
17+
return true;
18+
}
19+
}
20+
21+
return false;
22+
}
23+
}
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
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+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Sample.DynamicWebApi;
2+
3+
public class IApplicationService
4+
{
5+
6+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>net6.0</TargetFramework>
4+
<ImplicitUsings>enable</ImplicitUsings>
5+
<Nullable>enable</Nullable>
6+
</PropertyGroup>
7+
<ItemGroup>
8+
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.8" />
9+
</ItemGroup>
10+
</Project>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using System.Diagnostics;
4+
5+
namespace Sample.DynamicWebApi;
6+
7+
public static class SampleDynamicWebApiExtensions
8+
{
9+
public static IMvcBuilder AddDynamicWebApi(this IMvcBuilder builder)
10+
{
11+
if (builder is null)
12+
{
13+
throw new ArgumentNullException(nameof(builder));
14+
}
15+
16+
builder.ConfigureApplicationPartManager(manager =>
17+
{
18+
manager.FeatureProviders.Add(new ApplicationServiceControllerFeatureProvider());
19+
});
20+
21+
builder.Services.Configure<MvcOptions>(options =>
22+
{
23+
options.Conventions.Add(new ApplicationServiceConvention());
24+
});
25+
26+
return builder;
27+
}
28+
29+
public static IMvcCoreBuilder AddDynamicWebApi(this IMvcCoreBuilder builder)
30+
{
31+
if (builder is null)
32+
{
33+
throw new ArgumentNullException(nameof(builder));
34+
}
35+
36+
builder.ConfigureApplicationPartManager(manager =>
37+
{
38+
manager.FeatureProviders.Add(new ApplicationServiceControllerFeatureProvider());
39+
});
40+
41+
builder.Services.Configure<MvcOptions>(options =>
42+
{
43+
options.Conventions.Add(new ApplicationServiceConvention());
44+
});
45+
46+
return builder;
47+
}
48+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
3+
namespace Sample.WebApi.Controllers;
4+
5+
[ApiController]
6+
[Route("[controller]")]
7+
public class WeatherForecastController : ControllerBase
8+
{
9+
private static readonly string[] Summaries = new[]
10+
{
11+
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
12+
};
13+
14+
private readonly ILogger<WeatherForecastController> _logger;
15+
16+
public WeatherForecastController(ILogger<WeatherForecastController> logger)
17+
{
18+
_logger = logger;
19+
}
20+
21+
[HttpGet(Name = "GetWeatherForecast")]
22+
public IEnumerable<WeatherForecast> Get()
23+
{
24+
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
25+
{
26+
Date = DateTime.Now.AddDays(index),
27+
TemperatureC = Random.Shared.Next(-20, 55),
28+
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
29+
})
30+
.ToArray();
31+
}
32+
}

0 commit comments

Comments
 (0)