├── assets
├── 0F703DDD.png
├── 1560265120580.png
└── Alipay.AopSdk.AspnetCore.svg
├── version.props
├── src
└── Panda.DynamicWebApi
│ ├── IDynamicWebApi.cs
│ ├── Attributes
│ ├── NonDynamicWebApiAttribute.cs
│ ├── NonDynamicMethodAttribute.cs
│ └── DynamicWebApiAttribute.cs
│ ├── DynamicWebApiControllerFeatureProvider.cs
│ ├── AssemblyDynamicWebApiOptions.cs
│ ├── ISelectController.cs
│ ├── IActionRouteFactory.cs
│ ├── AppConsts.cs
│ ├── Panda.DynamicWebApi.csproj
│ ├── Helpers
│ ├── TypeHelper.cs
│ ├── ServiceCollectionExtensions.cs
│ ├── ReflectionHelper.cs
│ └── ExtensionMethods.cs
│ ├── DynamicWebApiOptions.cs
│ ├── DynamicWebApiServiceExtensions.cs
│ └── DynamicWebApiConvention.cs
├── samples
├── Panda.DynamicWebApiSample
│ ├── appsettings.json
│ ├── appsettings.Development.json
│ ├── Dtos
│ │ └── UpdateAppleDto.cs
│ ├── Dynamic
│ │ ├── IApplicationService.cs
│ │ ├── MyService.cs
│ │ ├── ServiceLocalSelectController.cs
│ │ ├── ServiceActionRouteFactory.cs
│ │ └── AppleAppService.cs
│ ├── Program.cs
│ ├── Properties
│ │ └── launchSettings.json
│ ├── Panda.DynamicWebApiSample.csproj
│ ├── Controllers
│ │ └── ValuesController.cs
│ └── Startup.cs
├── Service.Attribute
│ ├── ServiceAbsAttribute.csproj
│ └── ServiceAttribute.cs
└── Other.Controller
│ ├── Other.Controller.csproj
│ └── OtherService.cs
├── practice
└── XiaoChen.StudentManagement
│ ├── src
│ ├── Xc.StuMgr.WebHost
│ │ ├── appsettings.json
│ │ ├── appsettings.Development.json
│ │ ├── Program.cs
│ │ ├── Properties
│ │ │ └── launchSettings.json
│ │ ├── Xc.StuMgr.WebHost.csproj
│ │ ├── Controllers
│ │ │ └── ValuesController.cs
│ │ └── Startup.cs
│ └── Xc.StuMgr.Application
│ │ ├── Dtos
│ │ ├── CreateStudentInput.cs
│ │ ├── StudentOutput.cs
│ │ └── UpdateStudentInput.cs
│ │ ├── IApplicationService.cs
│ │ ├── Xc.StuMgr.Application.csproj
│ │ ├── IStudentAppService.cs
│ │ └── StudentAppService.cs
│ └── XiaoChen.StudentManagement.sln
├── nupkg
├── common.ps1
├── push_packages.ps1
└── pack.ps1
├── .github
└── workflows
│ └── build_and_publish_nuget.yml
├── Panda.DynamicWebApi.sln
├── README.md
├── .gitignore
├── README_zh-CN.md
└── LICENSE
/assets/0F703DDD.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pda-team/Panda.DynamicWebApi/HEAD/assets/0F703DDD.png
--------------------------------------------------------------------------------
/assets/1560265120580.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pda-team/Panda.DynamicWebApi/HEAD/assets/1560265120580.png
--------------------------------------------------------------------------------
/version.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 1.2.2
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/Panda.DynamicWebApi/IDynamicWebApi.cs:
--------------------------------------------------------------------------------
1 | namespace Panda.DynamicWebApi
2 | {
3 | public interface IDynamicWebApi
4 | {
5 |
6 | }
7 | }
--------------------------------------------------------------------------------
/samples/Panda.DynamicWebApiSample/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Warning"
5 | }
6 | },
7 | "AllowedHosts": "*"
8 | }
9 |
--------------------------------------------------------------------------------
/practice/XiaoChen.StudentManagement/src/Xc.StuMgr.WebHost/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Warning"
5 | }
6 | },
7 | "AllowedHosts": "*"
8 | }
9 |
--------------------------------------------------------------------------------
/samples/Service.Attribute/ServiceAbsAttribute.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net5.0
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/practice/XiaoChen.StudentManagement/src/Xc.StuMgr.Application/Dtos/CreateStudentInput.cs:
--------------------------------------------------------------------------------
1 |
2 |
3 | namespace Xc.StuMgr.Application.Dtos
4 | {
5 | public class CreateStudentInput
6 | {
7 |
8 | }
9 | }
--------------------------------------------------------------------------------
/samples/Panda.DynamicWebApiSample/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug",
5 | "System": "Information",
6 | "Microsoft": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/practice/XiaoChen.StudentManagement/src/Xc.StuMgr.WebHost/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug",
5 | "System": "Information",
6 | "Microsoft": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/samples/Panda.DynamicWebApiSample/Dtos/UpdateAppleDto.cs:
--------------------------------------------------------------------------------
1 |
2 |
3 | namespace Panda.DynamicWebApiSample.Dtos
4 | {
5 | public class UpdateAppleDto
6 | {
7 | public int Id { get; set; }
8 | public string Name { get; set; }
9 | }
10 | }
--------------------------------------------------------------------------------
/samples/Panda.DynamicWebApiSample/Dynamic/IApplicationService.cs:
--------------------------------------------------------------------------------
1 | using Panda.DynamicWebApi;
2 | using Panda.DynamicWebApi.Attributes;
3 |
4 | namespace Panda.DynamicWebApiSample.Dynamic
5 | {
6 | [DynamicWebApi]
7 | public interface IApplicationService:IDynamicWebApi
8 | {
9 |
10 | }
11 | }
--------------------------------------------------------------------------------
/nupkg/common.ps1:
--------------------------------------------------------------------------------
1 | # 路径
2 | $packFolder = (Get-Item -Path "./" -Verbose).FullName # 当前路径
3 | $rootFolder = Join-Path $packFolder "../" # 项目根目录
4 | $packOutputFolder = Join-Path $packFolder "dist" # 输出nuget package 目录
5 |
6 |
7 |
8 | # 所有的项目名称
9 | $projects = (
10 | "Panda.DynamicWebApi"
11 | )
12 |
--------------------------------------------------------------------------------
/practice/XiaoChen.StudentManagement/src/Xc.StuMgr.Application/Dtos/StudentOutput.cs:
--------------------------------------------------------------------------------
1 |
2 |
3 | namespace Xc.StuMgr.Application.Dtos
4 | {
5 | public class StudentOutput
6 | {
7 | public int Id { get; set; }
8 |
9 | public string Name { get; set; }
10 |
11 | public int Age { get; set; }
12 | }
13 | }
--------------------------------------------------------------------------------
/practice/XiaoChen.StudentManagement/src/Xc.StuMgr.Application/IApplicationService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Panda.DynamicWebApi;
3 | using Panda.DynamicWebApi.Attributes;
4 |
5 | namespace Xc.StuMgr.Application
6 | {
7 | [DynamicWebApi]
8 | public interface IApplicationService:IDynamicWebApi
9 | {
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/samples/Other.Controller/Other.Controller.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net5.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/practice/XiaoChen.StudentManagement/src/Xc.StuMgr.Application/Dtos/UpdateStudentInput.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace Xc.StuMgr.Application.Dtos
3 | {
4 | public class UpdateStudentInput
5 | {
6 | public int Id { get; set; }
7 |
8 | public string Name { get; set; }
9 |
10 | public int Age { get; set; }
11 | }
12 | }
--------------------------------------------------------------------------------
/src/Panda.DynamicWebApi/Attributes/NonDynamicWebApiAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Panda.DynamicWebApi.Attributes
4 | {
5 | [Serializable]
6 | [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Method)]
7 | public class NonDynamicWebApiAttribute:Attribute
8 | {
9 |
10 | }
11 | }
--------------------------------------------------------------------------------
/samples/Other.Controller/OtherService.cs:
--------------------------------------------------------------------------------
1 | using ServiceAbsAttribute;
2 | using System;
3 | using System.Threading.Tasks;
4 |
5 | namespace Other.Controller
6 | {
7 | [Service("Other.Server")]
8 | public class OtherService
9 | {
10 | public int Show()
11 | {
12 | return 100;
13 | }
14 |
15 | public Task TaskIntAsync()
16 | {
17 | return Task.FromResult(100);
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Panda.DynamicWebApi/Attributes/NonDynamicMethodAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Panda.DynamicWebApi.Attributes
8 | {
9 | [Serializable]
10 | [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Method)]
11 | public class NonDynamicMethodAttribute : Attribute
12 | {
13 |
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/samples/Panda.DynamicWebApiSample/Dynamic/MyService.cs:
--------------------------------------------------------------------------------
1 | using Other.Controller;
2 | using ServiceAbsAttribute;
3 | using System.Threading.Tasks;
4 |
5 | namespace Panda.DynamicWebApiSample.Dynamic
6 | {
7 | [Service("My.Server")]
8 | public class MyService
9 | {
10 | public int Show()
11 | {
12 | return 100;
13 | }
14 |
15 | public Task TaskIntAsync()
16 | {
17 | return Task.FromResult(100);
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/samples/Panda.DynamicWebApiSample/Dynamic/ServiceLocalSelectController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using Other.Controller;
4 | using Panda.DynamicWebApi;
5 | using ServiceAbsAttribute;
6 |
7 | namespace Panda.DynamicWebApiSample.Dynamic
8 | {
9 | internal class ServiceLocalSelectController : ISelectController
10 | {
11 | public bool IsController(Type type)
12 | {
13 | return type.IsPublic && type.GetCustomAttribute() != null;
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/samples/Service.Attribute/ServiceAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ServiceAbsAttribute
4 | {
5 | [AttributeUsage(AttributeTargets.Class)]
6 | public class ServiceAttribute : Attribute
7 | {
8 | public ServiceAttribute()
9 | {
10 | ServiceName = string.Empty;
11 | }
12 |
13 | public ServiceAttribute(string serviceName)
14 | {
15 | ServiceName = serviceName;
16 | }
17 |
18 | public string ServiceName { get; }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/practice/XiaoChen.StudentManagement/src/Xc.StuMgr.WebHost/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore;
2 | using Microsoft.AspNetCore.Hosting;
3 |
4 | namespace Xc.StuMgr.WebApiHost
5 | {
6 | public class Program
7 | {
8 | public static void Main(string[] args)
9 | {
10 | CreateWebHostBuilder(args).Build().Run();
11 | }
12 |
13 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
14 | WebHost.CreateDefaultBuilder(args)
15 | .UseStartup();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/practice/XiaoChen.StudentManagement/src/Xc.StuMgr.Application/Xc.StuMgr.Application.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 | bin\Debug\netcoreapp2.2\Xc.StuMgr.Application.xml
9 | 1701;1702;CS1591
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/samples/Panda.DynamicWebApiSample/Program.cs:
--------------------------------------------------------------------------------
1 |
2 |
3 | using Microsoft.AspNetCore.Hosting;
4 | using Microsoft.Extensions.Hosting;
5 |
6 | namespace Panda.DynamicWebApiSample
7 | {
8 | public class Program
9 | {
10 | public static void Main(string[] args)
11 | {
12 | CreateHostBuilder(args).Build().Run();
13 | }
14 |
15 | public static IHostBuilder CreateHostBuilder(string[] args) =>
16 | Host.CreateDefaultBuilder(args)
17 | .ConfigureWebHostDefaults(webBuilder =>
18 | {
19 | webBuilder.UseStartup();
20 | });
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/nupkg/push_packages.ps1:
--------------------------------------------------------------------------------
1 | . ".\common.ps1"
2 |
3 | $apiKey = $args[0]
4 | if ([System.String]::IsNullOrWhiteSpace($apiKey))
5 | {
6 | $apiKey = $env:NUGET_KEY
7 | }
8 |
9 | # 获取版本
10 | [xml]$versionPropsXml = Get-Content (Join-Path $rootFolder "version.props")
11 | $version = $versionPropsXml.Project.PropertyGroup.Version
12 | $versionStr = $version.Trim()
13 |
14 | # 发布所有包
15 | foreach($project in $projects) {
16 | $projectName = $project
17 | $packageFullPath = Join-Path $packOutputFolder ($projectName + "." + $versionStr + ".nupkg")
18 |
19 | $packageFullPath
20 |
21 | & dotnet nuget push $packageFullPath -s https://api.nuget.org/v3/index.json --api-key "$apiKey" --skip-duplicate
22 | }
23 |
24 | # 返回脚本执行目录
25 | Set-Location $packFolder
26 |
--------------------------------------------------------------------------------
/src/Panda.DynamicWebApi/DynamicWebApiControllerFeatureProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using Microsoft.AspNetCore.Mvc.Controllers;
3 | using Panda.DynamicWebApi.Attributes;
4 | using Panda.DynamicWebApi.Helpers;
5 |
6 | namespace Panda.DynamicWebApi
7 | {
8 | public class DynamicWebApiControllerFeatureProvider: ControllerFeatureProvider
9 | {
10 | private ISelectController _selectController;
11 |
12 | public DynamicWebApiControllerFeatureProvider(ISelectController selectController)
13 | {
14 | _selectController = selectController;
15 | }
16 |
17 | protected override bool IsController(TypeInfo typeInfo)
18 | {
19 | return _selectController.IsController(typeInfo);
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/practice/XiaoChen.StudentManagement/src/Xc.StuMgr.WebHost/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:5000"
8 | }
9 | },
10 | "profiles": {
11 | "IIS Express": {
12 | "commandName": "IISExpress",
13 | "launchBrowser": true,
14 | "launchUrl": "api/values",
15 | "environmentVariables": {
16 | "ASPNETCORE_ENVIRONMENT": "Development"
17 | }
18 | },
19 | "Xc.StuMgr.WebHost": {
20 | "commandName": "Project",
21 | "launchBrowser": true,
22 | "launchUrl": "swagger/index.html",
23 | "applicationUrl": "http://localhost:5000",
24 | "environmentVariables": {
25 | "ASPNETCORE_ENVIRONMENT": "Development"
26 | }
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/.github/workflows/build_and_publish_nuget.yml:
--------------------------------------------------------------------------------
1 | name: "build and publish to nuget"
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 |
12 | # set os
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | # checkout souce code
17 | - uses: actions/checkout@v2
18 |
19 | # install dotnet sdk
20 | - name: Setup .NET Core
21 | uses: actions/setup-dotnet@v1
22 | with:
23 | dotnet-version: 3.1.100
24 |
25 | # build nuget packages
26 | - name: build_nuget_packages
27 | run: ./pack.ps1
28 | shell: pwsh
29 | working-directory: ./nupkg
30 |
31 | # publish to nuget
32 | - name: publish_to_nuget
33 | env:
34 | NUGET_KEY: ${{ secrets.NUGET_KEY }}
35 | run: ./push_packages.ps1
36 | shell: pwsh
37 | working-directory: ./nupkg
38 |
--------------------------------------------------------------------------------
/assets/Alipay.AopSdk.AspnetCore.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/samples/Panda.DynamicWebApiSample/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:11820",
8 | "sslPort": 0
9 | }
10 | },
11 | "profiles": {
12 | "IIS Express": {
13 | "commandName": "IISExpress",
14 | "launchBrowser": true,
15 | "launchUrl": "swagger/index.html",
16 | "environmentVariables": {
17 | "ASPNETCORE_ENVIRONMENT": "Development"
18 | }
19 | },
20 | "Panda.DynamicWebApiSample": {
21 | "commandName": "Project",
22 | "launchBrowser": true,
23 | "launchUrl": "swagger/index.html",
24 | "applicationUrl": "http://localhost:5006",
25 | "environmentVariables": {
26 | "ASPNETCORE_ENVIRONMENT": "Development"
27 | }
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/samples/Panda.DynamicWebApiSample/Dynamic/ServiceActionRouteFactory.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc.ApplicationModels;
2 | using Other.Controller;
3 | using Panda.DynamicWebApi;
4 | using ServiceAbsAttribute;
5 | using System;
6 | using System.Reflection;
7 |
8 | namespace Panda.DynamicWebApiSample.Dynamic
9 | {
10 | internal class ServiceActionRouteFactory : IActionRouteFactory
11 | {
12 | public string CreateActionRouteModel(string areaName, string controllerName, ActionModel action)
13 | {
14 | var controllerType = action.ActionMethod.DeclaringType;
15 | var serviceAttribute = controllerType.GetCustomAttribute();
16 |
17 | var _controllerName = serviceAttribute.ServiceName == string.Empty ? controllerName.Replace("Service", "") : serviceAttribute.ServiceName.Replace("Service", "");
18 |
19 | return $"api/{_controllerName}/{action.ActionName.Replace("Async", "")}";
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/nupkg/pack.ps1:
--------------------------------------------------------------------------------
1 | # 执行公用脚本
2 | . ".\common.ps1"
3 |
4 | # 解决方案路径
5 | $slnPath = Join-Path $packFolder "../"
6 | $srcPath = Join-Path $slnPath "src"
7 |
8 | # 创建文件夹
9 | if(!(Test-Path $packOutputFolder)){
10 | mkdir $packOutputFolder
11 | }
12 |
13 | # 解决方案还原依赖
14 | Set-Location $slnPath
15 | & dotnet restore
16 |
17 | # 创建并移动过所有的 nuget 包到输出目录
18 | foreach($project in $projects) {
19 |
20 | # 拼接项目目录
21 | $projectFolder = Join-Path $srcPath $project
22 |
23 | # 创建 nuget 包
24 | Set-Location $projectFolder
25 | Get-ChildItem (Join-Path $projectFolder "bin/Release") -ErrorAction SilentlyContinue | Remove-Item -Recurse
26 | & dotnet msbuild /p:Configuration=Release /p:SourceLinkCreate=true
27 | & dotnet msbuild /t:pack /p:Configuration=Release /p:SourceLinkCreate=true
28 |
29 | # 复制 nuget 包
30 | $projectPackPath = Join-Path $projectFolder ("/bin/Release/" + $project + ".*.nupkg")
31 | Move-Item $projectPackPath $packOutputFolder
32 |
33 | }
34 |
35 | # 返回脚本启动目录
36 | Set-Location $packFolder
--------------------------------------------------------------------------------
/samples/Panda.DynamicWebApiSample/Panda.DynamicWebApiSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | InProcess
6 |
7 |
8 |
9 | bin\Debug\net6.0\Panda.DynamicWebApiSample.xml
10 | 1701;1702;CS1591
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/Panda.DynamicWebApi/AssemblyDynamicWebApiOptions.cs:
--------------------------------------------------------------------------------
1 | namespace Panda.DynamicWebApi
2 | {
3 | ///
4 | /// Specifies the dynamic webapi options for the assembly.
5 | ///
6 | public class AssemblyDynamicWebApiOptions
7 | {
8 | ///
9 | /// Routing prefix for all APIs
10 | ///
11 | /// Default value is null.
12 | ///
13 | public string ApiPrefix { get; }
14 |
15 | ///
16 | /// API HTTP Verb.
17 | ///
18 | /// Default value is null.
19 | ///
20 | public string HttpVerb { get; }
21 |
22 | ///
23 | ///
24 | ///
25 | /// Routing prefix for all APIs
26 | /// API HTTP Verb.
27 | public AssemblyDynamicWebApiOptions(string apiPrefix = null, string httpVerb = null)
28 | {
29 | ApiPrefix = apiPrefix;
30 | HttpVerb = httpVerb;
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/practice/XiaoChen.StudentManagement/src/Xc.StuMgr.WebHost/Xc.StuMgr.WebHost.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.2
5 | InProcess
6 | Xc.StuMgr.WebApiHost
7 | Xc.StuMgr.WebApiHost
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | bin\Debug\netcoreapp2.2\Xc.StuMgr.WebApiHost.xml
23 | 1701;1702;CS1591
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/practice/XiaoChen.StudentManagement/src/Xc.StuMgr.WebHost/Controllers/ValuesController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Microsoft.AspNetCore.Mvc;
3 |
4 | namespace Xc.StuMgr.WebApiHost.Controllers
5 | {
6 | [Route("api/[controller]")]
7 | [ApiController]
8 | public class ValuesController : ControllerBase
9 | {
10 | // GET api/values
11 | [HttpGet]
12 | public ActionResult> Get()
13 | {
14 | return new string[] { "value1", "value2" };
15 | }
16 |
17 | // GET api/values/5
18 | [HttpGet("{id}")]
19 | public ActionResult Get(int id)
20 | {
21 | return "value";
22 | }
23 |
24 | // POST api/values
25 | [HttpPost]
26 | public void Post([FromBody] string value)
27 | {
28 | }
29 |
30 | // PUT api/values/5
31 | [HttpPut("{id}")]
32 | public void Put(int id, [FromBody] string value)
33 | {
34 | }
35 |
36 | // DELETE api/values/5
37 | [HttpDelete("{id}")]
38 | public void Delete(int id)
39 | {
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Panda.DynamicWebApi/ISelectController.cs:
--------------------------------------------------------------------------------
1 | using Panda.DynamicWebApi.Attributes;
2 | using Panda.DynamicWebApi.Helpers;
3 | using System;
4 | using System.Reflection;
5 |
6 | namespace Panda.DynamicWebApi
7 | {
8 | public interface ISelectController
9 | {
10 | bool IsController(Type type);
11 | }
12 |
13 | internal class DefaultSelectController : ISelectController
14 | {
15 | public bool IsController(Type type)
16 | {
17 | var typeInfo = type.GetTypeInfo();
18 |
19 | if (!typeof(IDynamicWebApi).IsAssignableFrom(type) ||
20 | !typeInfo.IsPublic || typeInfo.IsAbstract || typeInfo.IsGenericType)
21 | {
22 | return false;
23 | }
24 |
25 |
26 | var attr = ReflectionHelper.GetSingleAttributeOrDefaultByFullSearch(typeInfo);
27 |
28 | if (attr == null)
29 | {
30 | return false;
31 | }
32 |
33 | if (ReflectionHelper.GetSingleAttributeOrDefaultByFullSearch(typeInfo) != null)
34 | {
35 | return false;
36 | }
37 |
38 | return true;
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/practice/XiaoChen.StudentManagement/src/Xc.StuMgr.Application/IStudentAppService.cs:
--------------------------------------------------------------------------------
1 |
2 |
3 | using System.Collections.Generic;
4 | using Xc.StuMgr.Application.Dtos;
5 |
6 | namespace Xc.StuMgr.Application
7 | {
8 | public interface IStudentAppService : IApplicationService
9 | {
10 | ///
11 | /// 根据ID获取学生
12 | ///
13 | ///
14 | ///
15 | StudentOutput Get(int id);
16 |
17 | ///
18 | /// 获取所有学生
19 | ///
20 | ///
21 | List Get();
22 |
23 | ///
24 | /// 更新学生信息
25 | ///
26 | ///
27 | void Update(UpdateStudentInput input);
28 |
29 | ///
30 | /// 更新学生年龄
31 | ///
32 | ///
33 | void UpdateAge(int age);
34 |
35 | ///
36 | /// 根据ID删除学生
37 | ///
38 | ///
39 | void Delete(int id);
40 |
41 | ///
42 | /// 添加学生
43 | ///
44 | ///
45 | void Create(CreateStudentInput input);
46 | }
47 | }
--------------------------------------------------------------------------------
/src/Panda.DynamicWebApi/IActionRouteFactory.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 | using Microsoft.AspNetCore.Mvc.ApplicationModels;
3 |
4 | namespace Panda.DynamicWebApi
5 | {
6 | public interface IActionRouteFactory
7 | {
8 | string CreateActionRouteModel(string areaName, string controllerName, ActionModel action);
9 | }
10 |
11 | internal class DefaultActionRouteFactory : IActionRouteFactory
12 | {
13 | private static string GetApiPreFix(ActionModel action)
14 | {
15 | var getValueSuccess = AppConsts.AssemblyDynamicWebApiOptions
16 | .TryGetValue(action.Controller.ControllerType.Assembly, out AssemblyDynamicWebApiOptions assemblyDynamicWebApiOptions);
17 | if (getValueSuccess && !string.IsNullOrWhiteSpace(assemblyDynamicWebApiOptions?.ApiPrefix))
18 | {
19 | return assemblyDynamicWebApiOptions.ApiPrefix;
20 | }
21 |
22 | return AppConsts.DefaultApiPreFix;
23 | }
24 |
25 | public string CreateActionRouteModel(string areaName, string controllerName, ActionModel action)
26 | {
27 | var apiPreFix = GetApiPreFix(action);
28 | var routeStr = $"{apiPreFix}/{areaName}/{controllerName}/{action.ActionName}".Replace("//", "/");
29 | return routeStr; }
30 | }
31 | }
--------------------------------------------------------------------------------
/samples/Panda.DynamicWebApiSample/Controllers/ValuesController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Authorization;
6 | using Microsoft.AspNetCore.Http;
7 | using Microsoft.AspNetCore.Mvc;
8 |
9 | namespace Panda.DynamicWebApiSample.Controllers
10 | {
11 | [Route("api/[controller]")]
12 | [Authorize]
13 | [ApiController]
14 | public class ValuesController : ControllerBase
15 | {
16 | // GET api/values
17 | [HttpGet]
18 | public ActionResult> Get()
19 | {
20 | return new string[] { "value1", "value2" };
21 | }
22 |
23 | // GET api/values/5
24 | [HttpGet("{id}")]
25 | public ActionResult Get(int id)
26 | {
27 | return "value";
28 | }
29 |
30 | // POST api/values
31 | [HttpPost]
32 | public void Post([FromBody] string value)
33 | {
34 | }
35 |
36 | // PUT api/values/5
37 | [HttpPut("{id}")]
38 | public void Put(int id, [FromBody] string value)
39 | {
40 | }
41 |
42 | ///
43 | /// Delete
44 | ///
45 | /// xxx
46 | [HttpDelete("{id}")]
47 | public void Delete(int id)
48 | {
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Panda.DynamicWebApi/AppConsts.cs:
--------------------------------------------------------------------------------
1 | using Panda.DynamicWebApi.Helpers;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Reflection;
5 |
6 | namespace Panda.DynamicWebApi
7 | {
8 | public static class AppConsts
9 | {
10 | public static string DefaultHttpVerb { get; set; }
11 |
12 | public static string DefaultAreaName { get; set; }
13 |
14 | public static string DefaultApiPreFix { get; set; }
15 |
16 | public static List ControllerPostfixes { get; set; }
17 | public static List ActionPostfixes { get; set; }
18 |
19 | public static List FormBodyBindingIgnoredTypes { get; set; }
20 |
21 | public static Dictionary HttpVerbs { get; set; }
22 |
23 | public static Func GetRestFulActionName { get; set; }
24 |
25 | public static Dictionary AssemblyDynamicWebApiOptions { get; set; }
26 |
27 | static AppConsts()
28 | {
29 | HttpVerbs=new Dictionary()
30 | {
31 | ["add"]="POST",
32 | ["create"]="POST",
33 | ["post"]="POST",
34 |
35 | ["get"]="GET",
36 | ["find"]="GET",
37 | ["fetch"]="GET",
38 | ["query"]="GET",
39 |
40 | ["update"]="PUT",
41 | ["put"]= "PUT",
42 |
43 | ["delete"] = "DELETE",
44 | ["remove"] = "DELETE",
45 | };
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/src/Panda.DynamicWebApi/Panda.DynamicWebApi.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | net5.0;net6.0;net7.0;net8.0;net9.0;netcoreapp3.1
7 |
8 |
9 |
10 | true
11 | ASP.NET Core Dynamic Restful WebApi. Generating WebApi from Classes. Such as: Direct Generation of WebApi Based on Business Logic Layer.
12 |
13 | https://github.com/dotnetauth/Panda.DynamicWebApi
14 | https://github.com/dotnetauth/Panda.DynamicWebApi
15 | git
16 | ASP.NET Core,Dynamic,WebApi
17 | stulzq,staneee
18 | LICENSE
19 | Support Endpoint Route.
20 | true
21 | snupkg
22 |
23 |
24 |
25 | bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml
26 | 1701;1702;CS1591
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | True
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/Panda.DynamicWebApi/Helpers/TypeHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 |
4 | namespace Panda.DynamicWebApi.Helpers
5 | {
6 | internal static class TypeHelper
7 | {
8 | public static bool IsFunc(object obj)
9 | {
10 | if (obj == null)
11 | {
12 | return false;
13 | }
14 |
15 | var type = obj.GetType();
16 | if (!type.GetTypeInfo().IsGenericType)
17 | {
18 | return false;
19 | }
20 |
21 | return type.GetGenericTypeDefinition() == typeof(Func<>);
22 | }
23 |
24 | public static bool IsFunc(object obj)
25 | {
26 | return obj != null && obj.GetType() == typeof(Func);
27 | }
28 |
29 | public static bool IsPrimitiveExtendedIncludingNullable(Type type, bool includeEnums = false)
30 | {
31 | if (IsPrimitiveExtended(type, includeEnums))
32 | {
33 | return true;
34 | }
35 |
36 | if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
37 | {
38 | return IsPrimitiveExtended(type.GenericTypeArguments[0], includeEnums);
39 | }
40 |
41 | return false;
42 | }
43 |
44 | private static bool IsPrimitiveExtended(Type type, bool includeEnums)
45 | {
46 | if (type.GetTypeInfo().IsPrimitive)
47 | {
48 | return true;
49 | }
50 |
51 | if (includeEnums && type.GetTypeInfo().IsEnum)
52 | {
53 | return true;
54 | }
55 |
56 | return type == typeof(string) ||
57 | type == typeof(decimal) ||
58 | type == typeof(DateTime) ||
59 | type == typeof(DateTimeOffset) ||
60 | type == typeof(TimeSpan) ||
61 | type == typeof(Guid);
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Panda.DynamicWebApi/Attributes/DynamicWebApiAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using Panda.DynamicWebApi.Helpers;
4 |
5 | namespace Panda.DynamicWebApi.Attributes
6 | {
7 |
8 | [Serializable]
9 | [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class)]
10 | public class DynamicWebApiAttribute : Attribute
11 | {
12 | ///
13 | /// Equivalent to AreaName
14 | ///
15 | public string Module { get; set; }
16 |
17 | internal static bool IsExplicitlyEnabledFor(Type type)
18 | {
19 | var remoteServiceAttr = type.GetTypeInfo().GetSingleAttributeOrNull();
20 | return remoteServiceAttr != null;
21 | }
22 |
23 | internal static bool IsExplicitlyDisabledFor(Type type)
24 | {
25 | var remoteServiceAttr = type.GetTypeInfo().GetSingleAttributeOrNull();
26 | return remoteServiceAttr != null;
27 | }
28 |
29 | internal static bool IsMetadataExplicitlyEnabledFor(Type type)
30 | {
31 | var remoteServiceAttr = type.GetTypeInfo().GetSingleAttributeOrNull();
32 | return remoteServiceAttr != null;
33 | }
34 |
35 | internal static bool IsMetadataExplicitlyDisabledFor(Type type)
36 | {
37 | var remoteServiceAttr = type.GetTypeInfo().GetSingleAttributeOrNull();
38 | return remoteServiceAttr != null;
39 | }
40 |
41 | internal static bool IsMetadataExplicitlyDisabledFor(MethodInfo method)
42 | {
43 | var remoteServiceAttr = method.GetSingleAttributeOrNull();
44 | return remoteServiceAttr != null;
45 | }
46 |
47 | internal static bool IsMetadataExplicitlyEnabledFor(MethodInfo method)
48 | {
49 | var remoteServiceAttr = method.GetSingleAttributeOrNull();
50 | return remoteServiceAttr != null;
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/practice/XiaoChen.StudentManagement/src/Xc.StuMgr.WebHost/Startup.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Reflection;
3 | using Microsoft.AspNetCore.Builder;
4 | using Microsoft.AspNetCore.Hosting;
5 | using Microsoft.AspNetCore.Mvc;
6 | using Microsoft.Extensions.Configuration;
7 | using Microsoft.Extensions.DependencyInjection;
8 | using Panda.DynamicWebApi;
9 | using Swashbuckle.AspNetCore.Swagger;
10 |
11 | namespace Xc.StuMgr.WebApiHost
12 | {
13 | public class Startup
14 | {
15 | public Startup(IConfiguration configuration)
16 | {
17 | Configuration = configuration;
18 | }
19 |
20 | public IConfiguration Configuration { get; }
21 |
22 | // This method gets called by the runtime. Use this method to add services to the container.
23 | public void ConfigureServices(IServiceCollection services)
24 | {
25 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
26 |
27 |
28 | // 添加动态WebApi 需放在 AddMvc 之后
29 | services.AddDynamicWebApi();
30 |
31 | services.AddSwaggerGen(options =>
32 | {
33 | options.SwaggerDoc("v1", new Info { Title = "晓晨学生管理系统 WebApi", Version = "v1" });
34 |
35 | options.DocInclusionPredicate((docName, description) => true);
36 |
37 | options.IncludeXmlComments(@"bin\Debug\netcoreapp2.2\Xc.StuMgr.WebApiHost.xml");
38 | options.IncludeXmlComments(@"bin\Debug\netcoreapp2.2\Xc.StuMgr.Application.xml");
39 | });
40 |
41 | }
42 |
43 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
44 | public void Configure(IApplicationBuilder app, IHostingEnvironment env)
45 | {
46 | if (env.IsDevelopment())
47 | {
48 | app.UseDeveloperExceptionPage();
49 | }
50 |
51 | app.UseSwagger();
52 | app.UseSwaggerUI(c =>
53 | {
54 | c.SwaggerEndpoint("/swagger/v1/swagger.json", "晓晨学生管理系统 WebApi");
55 | });
56 |
57 | app.UseMvc();
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/practice/XiaoChen.StudentManagement/src/Xc.StuMgr.Application/StudentAppService.cs:
--------------------------------------------------------------------------------
1 |
2 |
3 | using System.Collections.Generic;
4 | using Microsoft.AspNetCore.Mvc;
5 | using Xc.StuMgr.Application.Dtos;
6 |
7 | namespace Xc.StuMgr.Application
8 | {
9 | public class StudentAppService: IStudentAppService
10 | {
11 | ///
12 | /// 根据ID获取学生
13 | ///
14 | ///
15 | ///
16 | [HttpGet("{id:int}")]
17 | public StudentOutput Get(int id)
18 | {
19 | return new StudentOutput() {Id = 1, Age = 18, Name = "张三"};
20 | }
21 |
22 | ///
23 | /// 获取所有学生
24 | ///
25 | ///
26 | public List Get()
27 | {
28 | return new List()
29 | {
30 | new StudentOutput(){Id = 1,Age = 18,Name = "张三"},
31 | new StudentOutput(){Id = 2,Age = 19,Name = "李四"}
32 | };
33 | }
34 |
35 | ///
36 | /// 更新学生信息
37 | ///
38 | ///
39 | public void Update(UpdateStudentInput input)
40 | {
41 | throw new System.NotImplementedException();
42 | }
43 |
44 | ///
45 | /// 更新学生年龄
46 | ///
47 | ///
48 | [HttpPatch("{id:int}/age")]
49 | public void UpdateAge(int age)
50 | {
51 | throw new System.NotImplementedException();
52 | }
53 |
54 | ///
55 | /// 根据ID删除学生
56 | ///
57 | ///
58 | [HttpDelete("{id:int}")]
59 | public void Delete(int id)
60 | {
61 | throw new System.NotImplementedException();
62 | }
63 |
64 | ///
65 | /// 添加学生
66 | ///
67 | ///
68 | public void Create(CreateStudentInput input)
69 | {
70 | throw new System.NotImplementedException();
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/samples/Panda.DynamicWebApiSample/Dynamic/AppleAppService.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Net.Http;
3 | using System.Threading.Tasks;
4 | using Microsoft.AspNetCore.Authorization;
5 | using Microsoft.AspNetCore.Mvc;
6 | using Panda.DynamicWebApi;
7 | using Panda.DynamicWebApi.Attributes;
8 | using Panda.DynamicWebApiSample.Dtos;
9 |
10 | namespace Panda.DynamicWebApiSample.Dynamic
11 | {
12 |
13 | [DynamicWebApi]
14 | // [Authorize]
15 | public class AppleAppService: IDynamicWebApi
16 | {
17 | private static readonly Dictionary Apples = new Dictionary()
18 | {
19 | [1] = "Big Apple",
20 | [2] = "Small Apple"
21 | };
22 |
23 | [AllowAnonymous]
24 | public async Task UpdateAppleAsync(UpdateAppleDto dto)
25 | {
26 | await Task.Run(() => {
27 | if (Apples.ContainsKey(dto.Id))
28 | {
29 | Apples[dto.Id] = dto.Name;
30 | }
31 | });
32 |
33 | }
34 |
35 | ///
36 | /// Get An Apple.
37 | ///
38 | ///
39 | ///
40 | [HttpGet("{id:int}")]
41 | public string Get(int id)
42 | {
43 | if (Apples.ContainsKey(id))
44 | {
45 | return Apples[id];
46 | }
47 | else
48 | {
49 | return "No Apple!";
50 | }
51 | }
52 |
53 | ///
54 | /// Get All Apple Async.
55 | ///
56 | ///
57 | //[NonDynamicMethod]
58 | public IEnumerable GetAllAsync()
59 | {
60 | return Apples.Values;
61 | }
62 |
63 | ///
64 | /// Get All Apple.
65 | ///
66 | ///
67 | public IEnumerable Get()
68 | {
69 | return Apples.Values;
70 | }
71 |
72 | ///
73 | /// Get All Apple.
74 | ///
75 | ///
76 | public IEnumerable GetBigApple()
77 | {
78 | return Apples.Values;
79 | }
80 |
81 | ///
82 | /// Update Apple
83 | ///
84 | ///
85 | public void Update(UpdateAppleDto dto)
86 | {
87 | if (Apples.ContainsKey(dto.Id))
88 | {
89 | Apples[dto.Id] = dto.Name;
90 | }
91 | }
92 |
93 | ///
94 | /// Delete Apple
95 | ///
96 | /// Apple Id
97 | [HttpDelete("{id:int}")]
98 | public void Delete(int id)
99 | {
100 | if (Apples.ContainsKey(id))
101 | {
102 | Apples.Remove(id);
103 | }
104 | }
105 |
106 | }
107 | }
--------------------------------------------------------------------------------
/src/Panda.DynamicWebApi/Helpers/ServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Reflection;
4 | using Microsoft.Extensions.DependencyInjection;
5 |
6 | namespace Panda.DynamicWebApi.Helpers
7 | {
8 | internal static class ServiceCollectionExtensions
9 | {
10 | public static bool IsAdded(this IServiceCollection services)
11 | {
12 | return services.IsAdded(typeof(T));
13 | }
14 |
15 | public static bool IsAdded(this IServiceCollection services, Type type)
16 | {
17 | return services.Any(d => d.ServiceType == type);
18 | }
19 |
20 | public static T GetSingletonInstanceOrNull(this IServiceCollection services)
21 | {
22 | return (T)services
23 | .FirstOrDefault(d => d.ServiceType == typeof(T))
24 | ?.ImplementationInstance;
25 | }
26 |
27 | public static T GetSingletonInstance(this IServiceCollection services)
28 | {
29 | var service = services.GetSingletonInstanceOrNull();
30 | if (service == null)
31 | {
32 | throw new InvalidOperationException("Could not find singleton service: " + typeof(T).AssemblyQualifiedName);
33 | }
34 |
35 | return service;
36 | }
37 |
38 | public static IServiceProvider BuildServiceProviderFromFactory(this IServiceCollection services)
39 | {
40 | foreach (var service in services)
41 | {
42 | var factoryInterface = service.ImplementationInstance?.GetType()
43 | .GetTypeInfo()
44 | .GetInterfaces()
45 | .FirstOrDefault(i => i.GetTypeInfo().IsGenericType &&
46 | i.GetGenericTypeDefinition() == typeof(IServiceProviderFactory<>));
47 |
48 | if (factoryInterface == null)
49 | {
50 | continue;
51 | }
52 |
53 | var containerBuilderType = factoryInterface.GenericTypeArguments[0];
54 | return (IServiceProvider)typeof(ServiceCollectionExtensions)
55 | .GetTypeInfo()
56 | .GetMethods()
57 | .Single(m => m.Name == nameof(BuildServiceProviderFromFactory) && m.IsGenericMethod)
58 | .MakeGenericMethod(containerBuilderType)
59 | .Invoke(null, new object[] { services, null });
60 | }
61 |
62 | return services.BuildServiceProvider();
63 | }
64 |
65 | public static IServiceProvider BuildServiceProviderFromFactory(this IServiceCollection services, Action builderAction = null)
66 | {
67 |
68 | var serviceProviderFactory = services.GetSingletonInstanceOrNull>();
69 | if (serviceProviderFactory == null)
70 | {
71 | throw new Exception($"Could not find {typeof(IServiceProviderFactory).FullName} in {services}.");
72 | }
73 |
74 | var builder = serviceProviderFactory.CreateBuilder(services);
75 | builderAction?.Invoke(builder);
76 | return serviceProviderFactory.CreateServiceProvider(builder);
77 | }
78 | }
79 | }
--------------------------------------------------------------------------------
/src/Panda.DynamicWebApi/Helpers/ReflectionHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 |
6 | namespace Panda.DynamicWebApi.Helpers
7 | {
8 | internal static class ReflectionHelper
9 | {
10 |
11 | public static TAttribute GetSingleAttributeOrDefaultByFullSearch(TypeInfo info)
12 | where TAttribute : Attribute
13 | {
14 | var attributeType = typeof(TAttribute);
15 | if (info.IsDefined(attributeType, true))
16 | {
17 | return info.GetCustomAttributes(attributeType, true).Cast().First();
18 | }
19 | else
20 | {
21 | foreach (var implInter in info.ImplementedInterfaces)
22 | {
23 | var res = GetSingleAttributeOrDefaultByFullSearch(implInter.GetTypeInfo());
24 |
25 | if (res != null)
26 | {
27 | return res;
28 | }
29 | }
30 | }
31 |
32 | return null;
33 | }
34 |
35 | public static TAttribute GetSingleAttributeOrDefault(MemberInfo memberInfo, TAttribute defaultValue = default(TAttribute), bool inherit = true)
36 | where TAttribute : Attribute
37 | {
38 | var attributeType = typeof(TAttribute);
39 | if (memberInfo.IsDefined(typeof(TAttribute), inherit))
40 | {
41 | return memberInfo.GetCustomAttributes(attributeType, inherit).Cast().First();
42 | }
43 |
44 | return defaultValue;
45 | }
46 |
47 |
48 | ///
49 | /// Gets a single attribute for a member.
50 | ///
51 | /// Type of the attribute
52 | /// The member that will be checked for the attribute
53 | /// Include inherited attributes
54 | /// Returns the attribute object if found. Returns null if not found.
55 | public static TAttribute GetSingleAttributeOrNull(this MemberInfo memberInfo, bool inherit = true)
56 | where TAttribute : Attribute
57 | {
58 | if (memberInfo == null)
59 | {
60 | throw new ArgumentNullException(nameof(memberInfo));
61 | }
62 |
63 | var attrs = memberInfo.GetCustomAttributes(typeof(TAttribute), inherit).ToArray();
64 | if (attrs.Length > 0)
65 | {
66 | return (TAttribute)attrs[0];
67 | }
68 |
69 | return default(TAttribute);
70 | }
71 |
72 |
73 | public static TAttribute GetSingleAttributeOfTypeOrBaseTypesOrNull(this Type type, bool inherit = true)
74 | where TAttribute : Attribute
75 | {
76 | var attr = type.GetTypeInfo().GetSingleAttributeOrNull();
77 | if (attr != null)
78 | {
79 | return attr;
80 | }
81 |
82 | if (type.GetTypeInfo().BaseType == null)
83 | {
84 | return null;
85 | }
86 |
87 | return type.GetTypeInfo().BaseType.GetSingleAttributeOfTypeOrBaseTypesOrNull(inherit);
88 | }
89 |
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/practice/XiaoChen.StudentManagement/XiaoChen.StudentManagement.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.28922.388
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{79076E25-5302-46C1-ACA2-D15B06EA4DB4}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xc.StuMgr.Application", "src\Xc.StuMgr.Application\Xc.StuMgr.Application.csproj", "{613C8308-4697-4821-87EF-8B776AFD8D73}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "02 Application", "02 Application", "{F47A65FE-7F50-4DCE-8A70-8D9DEFF4CB03}"
11 | EndProject
12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "01 UI", "01 UI", "{E28C776B-7945-4B2B-A75C-8F43E5FB3F4D}"
13 | EndProject
14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xc.StuMgr.WebHost", "src\Xc.StuMgr.WebHost\Xc.StuMgr.WebHost.csproj", "{4DB32EDE-4F46-4C7C-83C4-B749F65F5480}"
15 | EndProject
16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "03 Domain", "03 Domain", "{D787DC15-5E7D-425A-ABFE-D1248CD81639}"
17 | EndProject
18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "04 Infrastructure", "04 Infrastructure", "{022746B2-FA8B-49D6-9059-91CBA1AB6B3C}"
19 | EndProject
20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Panda.DynamicWebApi", "..\..\src\Panda.DynamicWebApi\Panda.DynamicWebApi.csproj", "{F86F7EAB-1574-4E6F-9EF4-0B690AEA7ABC}"
21 | EndProject
22 | Global
23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
24 | Debug|Any CPU = Debug|Any CPU
25 | Release|Any CPU = Release|Any CPU
26 | EndGlobalSection
27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
28 | {613C8308-4697-4821-87EF-8B776AFD8D73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {613C8308-4697-4821-87EF-8B776AFD8D73}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {613C8308-4697-4821-87EF-8B776AFD8D73}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {613C8308-4697-4821-87EF-8B776AFD8D73}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {4DB32EDE-4F46-4C7C-83C4-B749F65F5480}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {4DB32EDE-4F46-4C7C-83C4-B749F65F5480}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {4DB32EDE-4F46-4C7C-83C4-B749F65F5480}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {4DB32EDE-4F46-4C7C-83C4-B749F65F5480}.Release|Any CPU.Build.0 = Release|Any CPU
36 | {F86F7EAB-1574-4E6F-9EF4-0B690AEA7ABC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37 | {F86F7EAB-1574-4E6F-9EF4-0B690AEA7ABC}.Debug|Any CPU.Build.0 = Debug|Any CPU
38 | {F86F7EAB-1574-4E6F-9EF4-0B690AEA7ABC}.Release|Any CPU.ActiveCfg = Release|Any CPU
39 | {F86F7EAB-1574-4E6F-9EF4-0B690AEA7ABC}.Release|Any CPU.Build.0 = Release|Any CPU
40 | EndGlobalSection
41 | GlobalSection(SolutionProperties) = preSolution
42 | HideSolutionNode = FALSE
43 | EndGlobalSection
44 | GlobalSection(NestedProjects) = preSolution
45 | {613C8308-4697-4821-87EF-8B776AFD8D73} = {F47A65FE-7F50-4DCE-8A70-8D9DEFF4CB03}
46 | {F47A65FE-7F50-4DCE-8A70-8D9DEFF4CB03} = {79076E25-5302-46C1-ACA2-D15B06EA4DB4}
47 | {E28C776B-7945-4B2B-A75C-8F43E5FB3F4D} = {79076E25-5302-46C1-ACA2-D15B06EA4DB4}
48 | {4DB32EDE-4F46-4C7C-83C4-B749F65F5480} = {E28C776B-7945-4B2B-A75C-8F43E5FB3F4D}
49 | {D787DC15-5E7D-425A-ABFE-D1248CD81639} = {79076E25-5302-46C1-ACA2-D15B06EA4DB4}
50 | {022746B2-FA8B-49D6-9059-91CBA1AB6B3C} = {79076E25-5302-46C1-ACA2-D15B06EA4DB4}
51 | {F86F7EAB-1574-4E6F-9EF4-0B690AEA7ABC} = {022746B2-FA8B-49D6-9059-91CBA1AB6B3C}
52 | EndGlobalSection
53 | GlobalSection(ExtensibilityGlobals) = postSolution
54 | SolutionGuid = {45B2E270-11C2-4658-B7A6-482A46B87160}
55 | EndGlobalSection
56 | EndGlobal
57 |
--------------------------------------------------------------------------------
/src/Panda.DynamicWebApi/DynamicWebApiOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Reflection;
4 | using Microsoft.AspNetCore.Http;
5 |
6 | namespace Panda.DynamicWebApi
7 | {
8 | public class DynamicWebApiOptions
9 | {
10 | public DynamicWebApiOptions()
11 | {
12 | RemoveControllerPostfixes = new List() { "AppService", "ApplicationService" };
13 | RemoveActionPostfixes = new List() { "Async" };
14 | FormBodyBindingIgnoredTypes = new List() { typeof(IFormFile) };
15 | DefaultHttpVerb = "POST";
16 | DefaultApiPrefix = "api";
17 | AssemblyDynamicWebApiOptions = new Dictionary();
18 | }
19 |
20 |
21 | ///
22 | /// API HTTP Verb.
23 | ///
24 | /// Default value is "POST".
25 | ///
26 | public string DefaultHttpVerb { get; set; }
27 |
28 | public string DefaultAreaName { get; set; }
29 |
30 | ///
31 | /// Routing prefix for all APIs
32 | ///
33 | /// Default value is "api".
34 | ///
35 | public string DefaultApiPrefix { get; set; }
36 |
37 | ///
38 | /// Remove the dynamic API class(Controller) name postfix.
39 | ///
40 | /// Default value is {"AppService", "ApplicationService"}.
41 | ///
42 | public List RemoveControllerPostfixes { get; set; }
43 |
44 | ///
45 | /// Remove the dynamic API class's method(Action) postfix.
46 | ///
47 | /// Default value is {"Async"}.
48 | ///
49 | public List RemoveActionPostfixes { get; set; }
50 |
51 | ///
52 | /// Ignore MVC Form Binding types.
53 | ///
54 | public List FormBodyBindingIgnoredTypes { get; set; }
55 |
56 | ///
57 | /// The method that processing the name of the action.
58 | ///
59 | public Func GetRestFulActionName { get; set; }
60 |
61 | ///
62 | /// Specifies the dynamic webapi options for the assembly.
63 | ///
64 | public Dictionary AssemblyDynamicWebApiOptions { get; }
65 |
66 | public ISelectController SelectController { get; set; } = new DefaultSelectController();
67 | public IActionRouteFactory ActionRouteFactory { get; set; } = new DefaultActionRouteFactory();
68 |
69 | ///
70 | /// Verify that all configurations are valid
71 | ///
72 | public void Valid()
73 | {
74 | if (string.IsNullOrEmpty(DefaultHttpVerb))
75 | {
76 | throw new ArgumentException($"{nameof(DefaultHttpVerb)} can not be empty.");
77 | }
78 |
79 | if (string.IsNullOrEmpty(DefaultAreaName))
80 | {
81 | DefaultAreaName = string.Empty;
82 | }
83 |
84 | if (string.IsNullOrEmpty(DefaultApiPrefix))
85 | {
86 | DefaultApiPrefix = string.Empty;
87 | }
88 |
89 | if (FormBodyBindingIgnoredTypes == null)
90 | {
91 | throw new ArgumentException($"{nameof(FormBodyBindingIgnoredTypes)} can not be null.");
92 | }
93 |
94 | if (RemoveControllerPostfixes == null)
95 | {
96 | throw new ArgumentException($"{nameof(RemoveControllerPostfixes)} can not be null.");
97 | }
98 | }
99 |
100 | ///
101 | /// Add the dynamic webapi options for the assembly.
102 | ///
103 | ///
104 | ///
105 | ///
106 | public void AddAssemblyOptions(Assembly assembly, string apiPreFix = null, string httpVerb = null)
107 | {
108 | if (assembly == null)
109 | {
110 | throw new ArgumentException($"{nameof(assembly)} can not be null.");
111 | }
112 |
113 | this.AssemblyDynamicWebApiOptions[assembly] = new AssemblyDynamicWebApiOptions(apiPreFix, httpVerb);
114 | }
115 | }
116 | }
--------------------------------------------------------------------------------
/Panda.DynamicWebApi.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.28922.388
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Panda.DynamicWebApi", "src\Panda.DynamicWebApi\Panda.DynamicWebApi.csproj", "{08AA6AB6-3A60-4806-892C-25248B11FBE3}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "01 src", "01 src", "{1F23CF3B-77BD-4D65-867B-31F6ADD54789}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "03 samples", "03 samples", "{2892E206-E3D4-479E-9BA3-AD636839CA9B}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Panda.DynamicWebApiSample", "samples\Panda.DynamicWebApiSample\Panda.DynamicWebApiSample.csproj", "{4E1F3A34-9AE4-41A4-AA9A-36F3B63A08A2}"
13 | EndProject
14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "00 Solution Items", "00 Solution Items", "{053F6381-4E5B-4D5E-9A23-1E914AA033DD}"
15 | ProjectSection(SolutionItems) = preProject
16 | .gitignore = .gitignore
17 | README.md = README.md
18 | README_zh-CN.md = README_zh-CN.md
19 | version.props = version.props
20 | EndProjectSection
21 | EndProject
22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{808F59BE-31D7-4BC1-81F8-454BF9BB43EC}"
23 | EndProject
24 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{E871DB62-B840-4421-8875-A7FEB72B7537}"
25 | ProjectSection(SolutionItems) = preProject
26 | .github\workflows\build_and_publish_nuget.yml = .github\workflows\build_and_publish_nuget.yml
27 | EndProjectSection
28 | EndProject
29 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nupkg", "nupkg", "{24E0E36B-2B3E-48DC-8C6E-7FA81F4910ED}"
30 | ProjectSection(SolutionItems) = preProject
31 | nupkg\common.ps1 = nupkg\common.ps1
32 | nupkg\pack.ps1 = nupkg\pack.ps1
33 | nupkg\push_packages.ps1 = nupkg\push_packages.ps1
34 | EndProjectSection
35 | EndProject
36 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Other.Controller", "samples\Other.Controller\Other.Controller.csproj", "{9C5DBF00-F938-4C04-BF21-A4D1F852574F}"
37 | EndProject
38 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceAbsAttribute", "samples\Service.Attribute\ServiceAbsAttribute.csproj", "{B7E08632-5B70-4008-B273-86D2A936B4DE}"
39 | EndProject
40 | Global
41 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
42 | Debug|Any CPU = Debug|Any CPU
43 | Release|Any CPU = Release|Any CPU
44 | EndGlobalSection
45 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
46 | {08AA6AB6-3A60-4806-892C-25248B11FBE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
47 | {08AA6AB6-3A60-4806-892C-25248B11FBE3}.Debug|Any CPU.Build.0 = Debug|Any CPU
48 | {08AA6AB6-3A60-4806-892C-25248B11FBE3}.Release|Any CPU.ActiveCfg = Release|Any CPU
49 | {08AA6AB6-3A60-4806-892C-25248B11FBE3}.Release|Any CPU.Build.0 = Release|Any CPU
50 | {4E1F3A34-9AE4-41A4-AA9A-36F3B63A08A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
51 | {4E1F3A34-9AE4-41A4-AA9A-36F3B63A08A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
52 | {4E1F3A34-9AE4-41A4-AA9A-36F3B63A08A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
53 | {4E1F3A34-9AE4-41A4-AA9A-36F3B63A08A2}.Release|Any CPU.Build.0 = Release|Any CPU
54 | {9C5DBF00-F938-4C04-BF21-A4D1F852574F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
55 | {9C5DBF00-F938-4C04-BF21-A4D1F852574F}.Debug|Any CPU.Build.0 = Debug|Any CPU
56 | {9C5DBF00-F938-4C04-BF21-A4D1F852574F}.Release|Any CPU.ActiveCfg = Release|Any CPU
57 | {9C5DBF00-F938-4C04-BF21-A4D1F852574F}.Release|Any CPU.Build.0 = Release|Any CPU
58 | {B7E08632-5B70-4008-B273-86D2A936B4DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
59 | {B7E08632-5B70-4008-B273-86D2A936B4DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
60 | {B7E08632-5B70-4008-B273-86D2A936B4DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
61 | {B7E08632-5B70-4008-B273-86D2A936B4DE}.Release|Any CPU.Build.0 = Release|Any CPU
62 | EndGlobalSection
63 | GlobalSection(SolutionProperties) = preSolution
64 | HideSolutionNode = FALSE
65 | EndGlobalSection
66 | GlobalSection(NestedProjects) = preSolution
67 | {08AA6AB6-3A60-4806-892C-25248B11FBE3} = {1F23CF3B-77BD-4D65-867B-31F6ADD54789}
68 | {4E1F3A34-9AE4-41A4-AA9A-36F3B63A08A2} = {2892E206-E3D4-479E-9BA3-AD636839CA9B}
69 | {808F59BE-31D7-4BC1-81F8-454BF9BB43EC} = {053F6381-4E5B-4D5E-9A23-1E914AA033DD}
70 | {E871DB62-B840-4421-8875-A7FEB72B7537} = {808F59BE-31D7-4BC1-81F8-454BF9BB43EC}
71 | {24E0E36B-2B3E-48DC-8C6E-7FA81F4910ED} = {053F6381-4E5B-4D5E-9A23-1E914AA033DD}
72 | {B7E08632-5B70-4008-B273-86D2A936B4DE} = {2892E206-E3D4-479E-9BA3-AD636839CA9B}
73 | {9C5DBF00-F938-4C04-BF21-A4D1F852574F} = {2892E206-E3D4-479E-9BA3-AD636839CA9B}
74 | EndGlobalSection
75 | GlobalSection(ExtensibilityGlobals) = postSolution
76 | SolutionGuid = {FF8BD8DF-F990-49F9-B5FE-7003FC8A139E}
77 | EndGlobalSection
78 | EndGlobal
79 |
--------------------------------------------------------------------------------
/samples/Panda.DynamicWebApiSample/Startup.cs:
--------------------------------------------------------------------------------
1 |
2 | using System.IdentityModel.Tokens.Jwt;
3 | using System.IO;
4 |
5 | using Microsoft.AspNetCore.Builder;
6 | using Microsoft.AspNetCore.Hosting;
7 | using Microsoft.Extensions.Configuration;
8 | using Microsoft.Extensions.DependencyInjection;
9 | using Microsoft.Extensions.Hosting;
10 | using Microsoft.IdentityModel.Tokens;
11 | using Microsoft.OpenApi.Models;
12 | using Other.Controller;
13 | using Panda.DynamicWebApi;
14 | using Panda.DynamicWebApiSample.Dynamic;
15 | using ServiceAbsAttribute;
16 |
17 | namespace Panda.DynamicWebApiSample
18 | {
19 | public class Startup
20 | {
21 | public Startup(IConfiguration configuration)
22 | {
23 | Configuration = configuration;
24 | }
25 |
26 | public IConfiguration Configuration { get; }
27 |
28 | // This method gets called by the runtime. Use this method to add services to the container.
29 | public void ConfigureServices(IServiceCollection services)
30 | {
31 | services.AddControllers().AddNewtonsoftJson();
32 |
33 | // 注册Swagger生成器,定义一个和多个Swagger 文档
34 | services.AddSwaggerGen(options =>
35 | {
36 | options.SwaggerDoc("v1", new OpenApiInfo() { Title = "Panda Dynamic WebApi", Version = "v1" });
37 |
38 | // TODO:一定要返回true!
39 | options.DocInclusionPredicate((docName, description) => true);
40 |
41 | var baseDirectory = System.AppDomain.CurrentDomain.BaseDirectory;
42 | var xmlFile = System.AppDomain.CurrentDomain.FriendlyName + ".xml";
43 | var xmlPath = Path.Combine(baseDirectory, xmlFile);
44 | options.IncludeXmlComments(xmlPath);
45 | });
46 |
47 | JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
48 | services.AddAuthentication("Bearer")
49 | .AddJwtBearer("Bearer", options =>
50 | {
51 | options.Authority = "http://www.google.com";
52 | options.RequireHttpsMetadata = false;
53 | options.Audience = "Panda-Api";
54 | });
55 |
56 | //services.AddPandaWebApiForSwagger();
57 |
58 |
59 | // 自定义配置
60 | //services.AddDynamicWebApi((options) =>
61 | //{
62 | // // 指定全局默认的 api 前缀
63 | // options.DefaultApiPrefix = "apis";
64 |
65 | // /**
66 | // * 清空API结尾,不删除API结尾;
67 | // * 若不清空 CreatUserAsync 将变为 CreateUser
68 | // */
69 | // options.RemoveActionPostfixes.Clear();
70 |
71 | // /**
72 | // * 自定义 ActionName 处理函数;
73 | // */
74 | // options.GetRestFulActionName = (actionName) => actionName;
75 |
76 | // /**
77 | // * 指定程序集 配置 url 前缀为 apis
78 | // * 如: http://localhost:8080/apis/User/CreateUser
79 | // */
80 | // options.AddAssemblyOptions(this.GetType().Assembly, apiPreFix: "apis");
81 |
82 | // /**
83 | // * 指定程序集 配置所有的api请求方式都为 POST
84 | // */
85 | // options.AddAssemblyOptions(this.GetType().Assembly, httpVerb: "POST");
86 |
87 | // /**
88 | // * 指定程序集 配置 url 前缀为 apis, 且所有请求方式都为POST
89 | // * 如: http://localhost:8080/apis/User/CreateUser
90 | // */
91 | // options.AddAssemblyOptions(this.GetType().Assembly, apiPreFix: "apis", httpVerb: "POST");
92 | //});
93 |
94 | services.AddDynamicWebApiCore();
95 | }
96 |
97 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
98 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
99 | {
100 | if (env.IsDevelopment())
101 | {
102 | app.UseDeveloperExceptionPage();
103 | }
104 |
105 | app.UseDynamicWebApi((serviceProvider,options) =>
106 | {
107 | options.AddAssemblyOptions(typeof(OtherService).Assembly);
108 | });
109 |
110 | app.UseRouting();
111 | app.UseAuthentication();
112 | app.UseAuthorization();
113 | app.UseEndpoints(endpoints =>
114 | {
115 | endpoints.MapControllers();
116 | });
117 |
118 | //启用中间件服务生成Swagger作为JSON终结点
119 | app.UseSwagger();
120 | //启用中间件服务对swagger-ui,指定Swagger JSON终结点
121 | app.UseSwaggerUI(c =>
122 | {
123 | c.SwaggerEndpoint("/swagger/v1/swagger.json", "Panda Dynamic WebApi");
124 | });
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/Panda.DynamicWebApi/Helpers/ExtensionMethods.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text.RegularExpressions;
4 |
5 | namespace Panda.DynamicWebApi.Helpers
6 | {
7 | internal static class ExtensionMethods
8 | {
9 | public static bool IsNullOrEmpty(this string str)
10 | {
11 | return string.IsNullOrEmpty(str);
12 | }
13 |
14 | public static bool IsNullOrEmpty(this ICollection source)
15 | {
16 | return source == null || source.Count <= 0;
17 | }
18 |
19 | public static bool IsIn(this string str, params string[] data)
20 | {
21 | foreach (var item in data)
22 | {
23 | if (str == item)
24 | {
25 | return true;
26 | }
27 | }
28 | return false;
29 | }
30 |
31 | public static string RemovePostFix(this string str, params string[] postFixes)
32 | {
33 | if (str == null)
34 | {
35 | return null;
36 | }
37 |
38 | if (str == string.Empty)
39 | {
40 | return string.Empty;
41 | }
42 |
43 | if (postFixes.IsNullOrEmpty())
44 | {
45 | return str;
46 | }
47 |
48 | foreach (var postFix in postFixes)
49 | {
50 | if (str.EndsWith(postFix))
51 | {
52 | return str.Left(str.Length - postFix.Length);
53 | }
54 | }
55 |
56 | return str;
57 | }
58 |
59 | public static string RemovePreFix(this string str, params string[] preFixes)
60 | {
61 | if (str == null)
62 | {
63 | return null;
64 | }
65 |
66 | if (str == string.Empty)
67 | {
68 | return string.Empty;
69 | }
70 |
71 | if (preFixes.IsNullOrEmpty())
72 | {
73 | return str;
74 | }
75 |
76 | foreach (var preFix in preFixes)
77 | {
78 | if (str.StartsWith(preFix))
79 | {
80 | return str.Right(str.Length - preFix.Length);
81 | }
82 | }
83 |
84 | return str;
85 | }
86 |
87 |
88 | public static string Left(this string str, int len)
89 | {
90 | if (str == null)
91 | {
92 | throw new ArgumentNullException("str");
93 | }
94 |
95 | if (str.Length < len)
96 | {
97 | throw new ArgumentException("len argument can not be bigger than given string's length!");
98 | }
99 |
100 | return str.Substring(0, len);
101 | }
102 |
103 |
104 | public static string Right(this string str, int len)
105 | {
106 | if (str == null)
107 | {
108 | throw new ArgumentNullException("str");
109 | }
110 |
111 | if (str.Length < len)
112 | {
113 | throw new ArgumentException("len argument can not be bigger than given string's length!");
114 | }
115 |
116 | return str.Substring(str.Length - len, len);
117 | }
118 |
119 | public static string GetCamelCaseFirstWord(this string str)
120 | {
121 | if (str == null)
122 | {
123 | throw new ArgumentNullException(nameof(str));
124 | }
125 |
126 | if (str.Length == 1)
127 | {
128 | return str;
129 | }
130 |
131 | var res = Regex.Split(str, @"(?=\p{Lu}\p{Ll})|(?<=\p{Ll})(?=\p{Lu})");
132 |
133 | if (res.Length < 1)
134 | {
135 | return str;
136 | }
137 | else
138 | {
139 | return res[0];
140 | }
141 | }
142 |
143 | public static string GetPascalCaseFirstWord(this string str)
144 | {
145 | if (str == null)
146 | {
147 | throw new ArgumentNullException(nameof(str));
148 | }
149 |
150 | if (str.Length == 1)
151 | {
152 | return str;
153 | }
154 |
155 | var res = Regex.Split(str, @"(?=\p{Lu}\p{Ll})|(?<=\p{Ll})(?=\p{Lu})");
156 |
157 | if (res.Length < 2)
158 | {
159 | return str;
160 | }
161 | else
162 | {
163 | return res[1];
164 | }
165 | }
166 |
167 | public static string GetPascalOrCamelCaseFirstWord(this string str)
168 | {
169 | if (str == null)
170 | {
171 | throw new ArgumentNullException(nameof(str));
172 | }
173 |
174 | if (str.Length <= 1)
175 | {
176 | return str;
177 | }
178 |
179 | if (str[0] >= 65 && str[0] <= 90)
180 | {
181 | return GetPascalCaseFirstWord(str);
182 | }
183 | else
184 | {
185 | return GetCamelCaseFirstWord(str);
186 | }
187 | }
188 |
189 |
190 | }
191 | }
--------------------------------------------------------------------------------
/src/Panda.DynamicWebApi/DynamicWebApiServiceExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Reflection;
4 | using Microsoft.AspNetCore.Builder;
5 | using Microsoft.AspNetCore.Mvc;
6 | using Microsoft.AspNetCore.Mvc.Abstractions;
7 | using Microsoft.AspNetCore.Mvc.ApplicationParts;
8 | using Microsoft.Extensions.DependencyInjection;
9 | using Microsoft.Extensions.Options;
10 | using Panda.DynamicWebApi.Helpers;
11 |
12 | namespace Panda.DynamicWebApi
13 | {
14 | ///
15 | /// Add Dynamic WebApi
16 | ///
17 | public static class DynamicWebApiServiceExtensions
18 | {
19 | ///
20 | /// Use Dynamic WebApi to Configure
21 | ///
22 | ///
23 | ///
24 | ///
25 | public static IApplicationBuilder UseDynamicWebApi(this IApplicationBuilder application, Action optionsAction)
26 | {
27 | var options = new DynamicWebApiOptions();
28 |
29 | optionsAction?.Invoke(application.ApplicationServices,options);
30 |
31 | options.Valid();
32 |
33 | AppConsts.DefaultAreaName = options.DefaultAreaName;
34 | AppConsts.DefaultHttpVerb = options.DefaultHttpVerb;
35 | AppConsts.DefaultApiPreFix = options.DefaultApiPrefix;
36 | AppConsts.ControllerPostfixes = options.RemoveControllerPostfixes;
37 | AppConsts.ActionPostfixes = options.RemoveActionPostfixes;
38 | AppConsts.FormBodyBindingIgnoredTypes = options.FormBodyBindingIgnoredTypes;
39 | AppConsts.GetRestFulActionName = options.GetRestFulActionName;
40 | AppConsts.AssemblyDynamicWebApiOptions = options.AssemblyDynamicWebApiOptions;
41 |
42 | var partManager = application.ApplicationServices.GetRequiredService();
43 |
44 | // Add a custom controller checker
45 | var featureProviders = application.ApplicationServices.GetRequiredService();
46 | partManager.FeatureProviders.Add(featureProviders);
47 |
48 | foreach(var assembly in options.AssemblyDynamicWebApiOptions.Keys)
49 | {
50 | var partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly);
51 |
52 | foreach(var part in partFactory.GetApplicationParts(assembly))
53 | {
54 | partManager.ApplicationParts.Add(part);
55 | }
56 | }
57 |
58 |
59 | var mvcOptions = application.ApplicationServices.GetRequiredService>();
60 | var dynamicWebApiConvention = application.ApplicationServices.GetRequiredService();
61 |
62 | mvcOptions.Value.Conventions.Add(dynamicWebApiConvention);
63 |
64 | return application;
65 | }
66 |
67 | public static IServiceCollection AddDynamicWebApiCore(this IServiceCollection services)
68 | where TSelectController: class,ISelectController
69 | where TActionRouteFactory: class, IActionRouteFactory
70 | {
71 | services.AddSingleton();
72 | services.AddSingleton();
73 | services.AddSingleton();
74 | services.AddSingleton();
75 | return services;
76 | }
77 |
78 | ///
79 | /// Add Dynamic WebApi to Container
80 | ///
81 | ///
82 | /// configuration
83 | ///
84 | public static IServiceCollection AddDynamicWebApi(this IServiceCollection services, DynamicWebApiOptions options)
85 | {
86 | if (options == null)
87 | {
88 | throw new ArgumentException(nameof(options));
89 | }
90 |
91 | options.Valid();
92 |
93 | AppConsts.DefaultAreaName = options.DefaultAreaName;
94 | AppConsts.DefaultHttpVerb = options.DefaultHttpVerb;
95 | AppConsts.DefaultApiPreFix = options.DefaultApiPrefix;
96 | AppConsts.ControllerPostfixes = options.RemoveControllerPostfixes;
97 | AppConsts.ActionPostfixes = options.RemoveActionPostfixes;
98 | AppConsts.FormBodyBindingIgnoredTypes = options.FormBodyBindingIgnoredTypes;
99 | AppConsts.GetRestFulActionName = options.GetRestFulActionName;
100 | AppConsts.AssemblyDynamicWebApiOptions = options.AssemblyDynamicWebApiOptions;
101 |
102 | var partManager = services.GetSingletonInstanceOrNull();
103 |
104 | if (partManager == null)
105 | {
106 | throw new InvalidOperationException("\"AddDynamicWebApi\" must be after \"AddMvc\".");
107 | }
108 |
109 | // Add a custom controller checker
110 | partManager.FeatureProviders.Add(new DynamicWebApiControllerFeatureProvider(options.SelectController));
111 |
112 | services.Configure(o =>
113 | {
114 | // Register Controller Routing Information Converter
115 | o.Conventions.Add(new DynamicWebApiConvention(options.SelectController, options.ActionRouteFactory));
116 | });
117 |
118 | return services;
119 | }
120 |
121 | public static IServiceCollection AddDynamicWebApi(this IServiceCollection services)
122 | {
123 | return AddDynamicWebApi(services, new DynamicWebApiOptions());
124 | }
125 |
126 | public static IServiceCollection AddDynamicWebApi(this IServiceCollection services, Action optionsAction)
127 | {
128 | var dynamicWebApiOptions = new DynamicWebApiOptions();
129 |
130 | optionsAction?.Invoke(dynamicWebApiOptions);
131 |
132 | return AddDynamicWebApi(services, dynamicWebApiOptions);
133 | }
134 |
135 | }
136 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Panda.DynamicWebApi
2 |
3 | Language: English | [中文](README_zh-CN.md)
4 |
5 | `Panda.DynamicWebApi` is a component that dynamically generates WebApi. The generated API conforms to the Restful style and is inspired by ABP. It can generate WebApi based on qualified classes. The logic is directly called by the MVC framework. There is no performance problem. It is perfectly compatible with Swagger to build the API documentation. There is no difference compared with manually writing the Controller.
6 |
7 | Application Scenario: The application logic layer in the DDD architecture, which can be used to directly generate WebApi without using the Controller.
8 |
9 | [](https://www.nuget.org/packages/Panda.DynamicWebApi/)
10 |
11 | .NET 9.0 support coming soon.
12 |
13 | ## 1.Quick Start
14 |
15 | (1)Create a new ASP.NET Core WebApi (or MVC) project
16 |
17 | (2)Install Package
18 |
19 | ````shell
20 | Install-Package Panda.DynamicWebApi
21 | ````
22 |
23 | (3)Create a class named `AppleAppService`, implement the `IDynamicWebApi` interface, and add the attribute `[DynamicWebApi]`
24 |
25 | ````csharp
26 | [DynamicWebApi]
27 | public class AppleAppService: IDynamicWebApi
28 | {
29 | private static readonly Dictionary Apples=new Dictionary()
30 | {
31 | [1]="Big Apple",
32 | [2]="Small Apple"
33 | };
34 |
35 | ///
36 | /// Get An Apple.
37 | ///
38 | ///
39 | ///
40 | [HttpGet("{id:int}")]
41 | public string Get(int id)
42 | {
43 | if (Apples.ContainsKey(id))
44 | {
45 | return Apples[id];
46 | }
47 | else
48 | {
49 | return "No Apple!";
50 | }
51 | }
52 |
53 | ///
54 | /// Get All Apple.
55 | ///
56 | ///
57 | public IEnumerable Get()
58 | {
59 | return Apples.Values;
60 | }
61 |
62 | public void Update(UpdateAppleDto dto)
63 | {
64 | if (Apples.ContainsKey(dto.Id))
65 | {
66 | Apples[dto.Id] =dto.Name;
67 | }
68 | }
69 |
70 | ///
71 | /// Delete Apple
72 | ///
73 | /// Apple Id
74 | [HttpDelete("{id:int}")]
75 | public void Delete(int id)
76 | {
77 | if (Apples.ContainsKey(id))
78 | {
79 | Apples.Remove(id);
80 | }
81 | }
82 |
83 | }
84 | ````
85 |
86 | (4)Register DynamicWebApi in Startup
87 |
88 | ````csharp
89 | public void ConfigureServices(IServiceCollection services)
90 | {
91 | services.AddDynamicWebApi();
92 | }
93 | ````
94 |
95 | (5)Add Swagger
96 |
97 | (6)Run
98 |
99 | After running the browser, visit `/swagger/index.html` and you will see the WebApi generated for our `AppleAppService`.
100 |
101 | 
102 |
103 | This quick start Demo Address: [link](/samples/Panda.DynamicWebApiSample)
104 |
105 | ## 2.Advanced
106 |
107 | (1)There are two conditions that need to be met for a class to generate a dynamic API. One is that the class **direct** or **indirect** implements `IDynamicWebApi`, and the class **itself** or **parent abstract class** or **Implemented interface** has the property `DynamicWebApi`
108 |
109 | (2)Adding the attribute `[NonDynamicWebApi]` allows a class or a method to not generate an API, and `[NonDynamicWebApi]` has the highest priority.
110 |
111 | (3)The suffix of the dynamic API **class name** that conforms to the rule is deleted. For example, our quick start `AppleAppService` will delete the AppService suffix. This rule can be dynamically configured.
112 |
113 | (4)The API route prefix is automatically added, and the `api` prefix is added by default for all APIs.
114 |
115 | (5)The default HTTP verb is `POST`, which can be understood as the Http Method of the API. But it can be overridden by `HttpGet/HttpPost/HttpDelete ` and other ASP.NET Core built-in attribute.
116 |
117 | (6)You can override the default route with built-in attribute such as `HttpGet/HttpPost/HttpDelete `
118 |
119 | (7)By default, HTTP verbs are set according to your method name. For example, the API verb generated by CreateApple or Create is `POST`. The comparison table is as follows. If you hit (ignore uppercase) the comparison table, then this part of the API name will be Omitted, such as CreateApple will become Apple, if not in the following comparison table, will use the default verb `POST`
120 |
121 | | MethodName Start With | Http Verb |
122 | | --------------------- | --------- |
123 | | create | POST |
124 | | add | POST |
125 | | post | POST |
126 | | get | GET |
127 | | find | GET |
128 | | fetch | GET |
129 | | query | GET |
130 | | update | PUT |
131 | | put | PUT |
132 | | delete | DELETE |
133 | | remove | DELETE |
134 |
135 | (8)It is highly recommended that the method name use the PascalCase specification and use the verbs from the above table. Such as:
136 |
137 | Create Apple Info-> Add/AddApple/Create/CreateApple
138 |
139 | Update Apple Info -> Update/UpdateApple
140 |
141 | ...
142 |
143 | (9)The `[DynamicWebApi]` attribute can be inherited, so it is forbidden to be placed on a parent class other than an abstract class or interface for the parent class to be misidentified.
144 |
145 | ## 3.Configuration
146 |
147 | All configurations are in the object `DynamicWebApiOptions`, as explained below:
148 |
149 | | Property | Require | 说明 |
150 | | --------------------------- | -------- | --------------------------------------------------------- |
151 | | DefaultHttpVerb | False | Default:POST. |
152 | | DefaultAreaName | False | Default:Empty. |
153 | | DefaultApiPrefix | False | Default:api. Web API route prefix. |
154 | | RemoveControllerPostfixes | False | Default:AppService/ApplicationService. The suffix of the class name that needs to be removed. |
155 | | RemoveActionPostfixes | False | Default:Async. Method name needs to be removed from the suffix. |
156 | | FormBodyBindingIgnoredTypes | False | Default:IFormFile。Ignore type for MVC Form Binding. |
157 |
158 | ## 4.Q&A
159 |
160 | If you encounter problems, you can use [Issues](https://github.com/dotnetauth/Panda.DynamicWebApi/issues) to ask questions.
161 |
162 | ## 5.Reference project
163 |
164 | > This project directly or indirectly refers to the following items
165 |
166 | - [ABP](https://github.com/aspnetboilerplate/aspnetboilerplate)
167 |
168 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015/2017 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # Visual Studio 2017 auto generated files
33 | Generated\ Files/
34 |
35 | # MSTest test Results
36 | [Tt]est[Rr]esult*/
37 | [Bb]uild[Ll]og.*
38 |
39 | # NUNIT
40 | *.VisualState.xml
41 | TestResult.xml
42 |
43 | # Build Results of an ATL Project
44 | [Dd]ebugPS/
45 | [Rr]eleasePS/
46 | dlldata.c
47 |
48 | # Benchmark Results
49 | BenchmarkDotNet.Artifacts/
50 |
51 | # .NET Core
52 | project.lock.json
53 | project.fragment.lock.json
54 | artifacts/
55 | #**/Properties/launchSettings.json
56 |
57 | # StyleCop
58 | StyleCopReport.xml
59 |
60 | # Files built by Visual Studio
61 | *_i.c
62 | *_p.c
63 | *_i.h
64 | *.ilk
65 | *.meta
66 | *.obj
67 | *.iobj
68 | *.pch
69 | *.pdb
70 | *.ipdb
71 | *.pgc
72 | *.pgd
73 | *.rsp
74 | *.sbr
75 | *.tlb
76 | *.tli
77 | *.tlh
78 | *.tmp
79 | *.tmp_proj
80 | *.log
81 | *.vspscc
82 | *.vssscc
83 | .builds
84 | *.pidb
85 | *.svclog
86 | *.scc
87 |
88 | # Chutzpah Test files
89 | _Chutzpah*
90 |
91 | # Visual C++ cache files
92 | ipch/
93 | *.aps
94 | *.ncb
95 | *.opendb
96 | *.opensdf
97 | *.sdf
98 | *.cachefile
99 | *.VC.db
100 | *.VC.VC.opendb
101 |
102 | # Visual Studio profiler
103 | *.psess
104 | *.vsp
105 | *.vspx
106 | *.sap
107 |
108 | # Visual Studio Trace Files
109 | *.e2e
110 |
111 | # TFS 2012 Local Workspace
112 | $tf/
113 |
114 | # Guidance Automation Toolkit
115 | *.gpState
116 |
117 | # ReSharper is a .NET coding add-in
118 | _ReSharper*/
119 | *.[Rr]e[Ss]harper
120 | *.DotSettings.user
121 |
122 | # JustCode is a .NET coding add-in
123 | .JustCode
124 |
125 | # TeamCity is a build add-in
126 | _TeamCity*
127 |
128 | # DotCover is a Code Coverage Tool
129 | *.dotCover
130 |
131 | # AxoCover is a Code Coverage Tool
132 | .axoCover/*
133 | !.axoCover/settings.json
134 |
135 | # Visual Studio code coverage results
136 | *.coverage
137 | *.coveragexml
138 |
139 | # NCrunch
140 | _NCrunch_*
141 | .*crunch*.local.xml
142 | nCrunchTemp_*
143 |
144 | # MightyMoose
145 | *.mm.*
146 | AutoTest.Net/
147 |
148 | # Web workbench (sass)
149 | .sass-cache/
150 |
151 | # Installshield output folder
152 | [Ee]xpress/
153 |
154 | # DocProject is a documentation generator add-in
155 | DocProject/buildhelp/
156 | DocProject/Help/*.HxT
157 | DocProject/Help/*.HxC
158 | DocProject/Help/*.hhc
159 | DocProject/Help/*.hhk
160 | DocProject/Help/*.hhp
161 | DocProject/Help/Html2
162 | DocProject/Help/html
163 |
164 | # Click-Once directory
165 | publish/
166 |
167 | # Publish Web Output
168 | *.[Pp]ublish.xml
169 | *.azurePubxml
170 | # Note: Comment the next line if you want to checkin your web deploy settings,
171 | # but database connection strings (with potential passwords) will be unencrypted
172 | *.pubxml
173 | *.publishproj
174 |
175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
176 | # checkin your Azure Web App publish settings, but sensitive information contained
177 | # in these scripts will be unencrypted
178 | PublishScripts/
179 |
180 | # NuGet Packages
181 | *.nupkg
182 | # The packages folder can be ignored because of Package Restore
183 | **/[Pp]ackages/*
184 | # except build/, which is used as an MSBuild target.
185 | !**/[Pp]ackages/build/
186 | # Uncomment if necessary however generally it will be regenerated when needed
187 | #!**/[Pp]ackages/repositories.config
188 | # NuGet v3's project.json files produces more ignorable files
189 | *.nuget.props
190 | *.nuget.targets
191 |
192 | # Microsoft Azure Build Output
193 | csx/
194 | *.build.csdef
195 |
196 | # Microsoft Azure Emulator
197 | ecf/
198 | rcf/
199 |
200 | # Windows Store app package directories and files
201 | AppPackages/
202 | BundleArtifacts/
203 | Package.StoreAssociation.xml
204 | _pkginfo.txt
205 | *.appx
206 |
207 | # Visual Studio cache files
208 | # files ending in .cache can be ignored
209 | *.[Cc]ache
210 | # but keep track of directories ending in .cache
211 | !*.[Cc]ache/
212 |
213 | # Others
214 | ClientBin/
215 | ~$*
216 | *~
217 | *.dbmdl
218 | *.dbproj.schemaview
219 | *.jfm
220 | *.pfx
221 | *.publishsettings
222 | orleans.codegen.cs
223 |
224 | # Including strong name files can present a security risk
225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
226 | #*.snk
227 |
228 | # Since there are multiple workflows, uncomment next line to ignore bower_components
229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
230 | #bower_components/
231 |
232 | # RIA/Silverlight projects
233 | Generated_Code/
234 |
235 | # Backup & report files from converting an old project file
236 | # to a newer Visual Studio version. Backup files are not needed,
237 | # because we have git ;-)
238 | _UpgradeReport_Files/
239 | Backup*/
240 | UpgradeLog*.XML
241 | UpgradeLog*.htm
242 | ServiceFabricBackup/
243 | *.rptproj.bak
244 |
245 | # SQL Server files
246 | *.mdf
247 | *.ldf
248 | *.ndf
249 |
250 | # Business Intelligence projects
251 | *.rdl.data
252 | *.bim.layout
253 | *.bim_*.settings
254 | *.rptproj.rsuser
255 |
256 | # Microsoft Fakes
257 | FakesAssemblies/
258 |
259 | # GhostDoc plugin setting file
260 | *.GhostDoc.xml
261 |
262 | # Node.js Tools for Visual Studio
263 | .ntvs_analysis.dat
264 | node_modules/
265 |
266 | # Visual Studio 6 build log
267 | *.plg
268 |
269 | # Visual Studio 6 workspace options file
270 | *.opt
271 |
272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
273 | *.vbw
274 |
275 | # Visual Studio LightSwitch build output
276 | **/*.HTMLClient/GeneratedArtifacts
277 | **/*.DesktopClient/GeneratedArtifacts
278 | **/*.DesktopClient/ModelManifest.xml
279 | **/*.Server/GeneratedArtifacts
280 | **/*.Server/ModelManifest.xml
281 | _Pvt_Extensions
282 |
283 | # Paket dependency manager
284 | .paket/paket.exe
285 | paket-files/
286 |
287 | # FAKE - F# Make
288 | .fake/
289 |
290 | # JetBrains Rider
291 | .idea/
292 | *.sln.iml
293 |
294 | # CodeRush
295 | .cr/
296 |
297 | # Python Tools for Visual Studio (PTVS)
298 | __pycache__/
299 | *.pyc
300 |
301 | # Cake - Uncomment if you are using it
302 | # tools/**
303 | # !tools/packages.config
304 |
305 | # Tabs Studio
306 | *.tss
307 |
308 | # Telerik's JustMock configuration file
309 | *.jmconfig
310 |
311 | # BizTalk build output
312 | *.btp.cs
313 | *.btm.cs
314 | *.odx.cs
315 | *.xsd.cs
316 |
317 | # OpenCover UI analysis results
318 | OpenCover/
319 |
320 | # Azure Stream Analytics local run output
321 | ASALocalRun/
322 |
323 | # MSBuild Binary and Structured Log
324 | *.binlog
325 |
326 | # NVidia Nsight GPU debugger configuration file
327 | *.nvuser
328 |
329 | # MFractors (Xamarin productivity tool) working folder
330 | .mfractor/
331 |
--------------------------------------------------------------------------------
/README_zh-CN.md:
--------------------------------------------------------------------------------
1 | # Panda.DynamicWebApi
2 |
3 | `Panda.DynamicWebApi` 是一个动态生成WebApi的组件,生成的API符合Restful风格,受启发于ABP。它可以根据符合条件的类来生成WebApi,由MVC框架直接调用逻辑,无性能问题,完美兼容Swagger来构建API说明文档,与手动编写Controller相比并无区别。
4 |
5 | 应用场景:DDD架构中的应用逻辑层,可使用本组件来直接生成WebApi,而无需再用Controller来调用。
6 |
7 | [](https://www.nuget.org/packages/Panda.DynamicWebApi/)
8 |
9 | ## 1.快速入门
10 |
11 | (1)新建一个 ASP.NET Core WebApi(或MVC) 项目
12 |
13 | (2)通过Nuget安装组件
14 |
15 | ````shell
16 | Install-Package Panda.DynamicWebApi
17 | ````
18 |
19 | (3)创建一个类命名为 `AppleAppService`,实现 `IDynamicWebApi` 接口,并加入特性 `[DynamicWebApi]`
20 |
21 | ````csharp
22 | [DynamicWebApi]
23 | public class AppleAppService: IDynamicWebApi
24 | {
25 | private static readonly Dictionary Apples=new Dictionary()
26 | {
27 | [1]="Big Apple",
28 | [2]="Small Apple"
29 | };
30 |
31 | ///
32 | /// Get An Apple.
33 | ///
34 | ///
35 | ///
36 | [HttpGet("{id:int}")]
37 | public string Get(int id)
38 | {
39 | if (Apples.ContainsKey(id))
40 | {
41 | return Apples[id];
42 | }
43 | else
44 | {
45 | return "No Apple!";
46 | }
47 | }
48 |
49 | ///
50 | /// Get All Apple.
51 | ///
52 | ///
53 | public IEnumerable Get()
54 | {
55 | return Apples.Values;
56 | }
57 |
58 | public void Update(UpdateAppleDto dto)
59 | {
60 | if (Apples.ContainsKey(dto.Id))
61 | {
62 | Apples[dto.Id] =dto.Name;
63 | }
64 | }
65 |
66 | ///
67 | /// Delete Apple
68 | ///
69 | /// Apple Id
70 | [HttpDelete("{id:int}")]
71 | public void Delete(int id)
72 | {
73 | if (Apples.ContainsKey(id))
74 | {
75 | Apples.Remove(id);
76 | }
77 | }
78 |
79 | }
80 | ````
81 |
82 | (4)在 Startup 中注册 DynamicWebApi
83 |
84 | ````csharp
85 | public void ConfigureServices(IServiceCollection services)
86 | {
87 | // 默认配置
88 | services.AddDynamicWebApi();
89 |
90 | // 自定义配置
91 | services.AddDynamicWebApi((options) =>
92 | {
93 | // 指定全局默认的 api 前缀
94 | options.DefaultApiPrefix = "apis";
95 |
96 | /**
97 | * 清空API结尾,不删除API结尾;
98 | * 若不清空 CreatUserAsync 将变为 CreateUser
99 | */
100 | options.RemoveActionPostfixes.Clear();
101 |
102 | /**
103 | * 自定义 ActionName 处理函数;
104 | */
105 | options.GetRestFulActionName = (actionName) => actionName;
106 |
107 | /**
108 | * 指定程序集 配置 url 前缀为 apis
109 | * 如: http://localhost:8080/apis/User/CreateUser
110 | */
111 | options.AddAssemblyOptions(this.GetType().Assembly, apiPreFix: "apis");
112 |
113 | /**
114 | * 指定程序集 配置所有的api请求方式都为 POST
115 | */
116 | options.AddAssemblyOptions(this.GetType().Assembly, httpVerb: "POST");
117 |
118 | /**
119 | * 指定程序集 配置 url 前缀为 apis, 且所有请求方式都为POST
120 | * 如: http://localhost:8080/apis/User/CreateUser
121 | */
122 | options.AddAssemblyOptions(this.GetType().Assembly, apiPreFix: "apis", httpVerb: "POST");
123 | });
124 | }
125 | ````
126 |
127 | (5)添加 Swagger
128 |
129 | (6)运行
130 |
131 | 运行浏览器以后访问 `<你的项目地址>/swagger/index.html`,将会看到为我们 `AppleAppService` 生成的 WebApi
132 |
133 | 
134 |
135 | 本快速入门 Demo 地址:[点我](/samples/Panda.DynamicWebApiSample)
136 |
137 | ## 2.更进一步
138 |
139 | (1)要让类生成动态API需要满足两个条件,一个是该类**直接**或**间接**实现 `IDynamicWebApi`,同时该类**本身**或者**父抽象类**或者**实现的接口**具有特性 `DynamicWebApi`
140 |
141 | (2)添加特性 `[NonDynamicWebApi]` 可使一个类或者一个方法不生成API,`[NonDynamicWebApi]` 具有最高的优先级。
142 |
143 | (3)会对符合规则的动态API**类名**进行后缀的删除,如:我们快速入门的 `AppleAppService`,会被删除 AppService 后缀,这个规则是可以动态配置的。
144 |
145 | (4)会自动添加API路由前缀,默认会为所有API添加 `api`前缀
146 |
147 | (5)默认的HTTP动词为`POST`,可以理解为API的 Http Method。但可以通过 `HttpGet/HttpPost/HttpDelete `等等ASP.NET Core 内置特性来覆盖
148 |
149 | (6)可以通过`HttpGet/HttpPost/HttpDelete `等内置特性来覆盖默认路由
150 |
151 | (7)默认会根据你的方法名字来设置HTTP动词,如 CreateApple 或者 Create 生成的API动词为 `POST`,对照表如下,若命中(忽略大小写)对照表那么该API的名称中的这部分将会被省略,如 CreateApple 将会变成 Apple,如未在以下对照表中,将会使用默认动词 `POST`
152 |
153 | | 方法名开头 | 动词 |
154 | | ---------- | ------ |
155 | | create | POST |
156 | | add | POST |
157 | | post | POST |
158 | | get | GET |
159 | | find | GET |
160 | | fetch | GET |
161 | | query | GET |
162 | | update | PUT |
163 | | put | PUT |
164 | | delete | DELETE |
165 | | remove | DELETE |
166 |
167 | (8)强烈建议方法名称使用帕斯卡命名(PascalCase)规范,且使用以上对照表的动词。如:
168 |
169 | 添加苹果 -> Add/AddApple/Create/CreateApple
170 |
171 | 更新苹果 -> Update/UpdateApple
172 |
173 | ...
174 |
175 | (9)`[DynamicWebApi]` 特性因为可被继承,所以为了父类被误识别,禁止放在除抽象类、接口以外的父类上。
176 | (10)自定义 WebApi注册
177 | ### 1.基础功能
178 | ```csharp
179 | public void ConfigureServices(IServiceCollection services)
180 | {
181 | // 自定义配置
182 | services.AddDynamicWebApi((options) =>
183 | {
184 | //自定义注册的WebApi
185 | options.SelectController = new ServiceLocalSelectController();
186 | //自定义WebApi路由地址
187 | options.ActionRouteFactory = new ServiceActionRouteFactory();
188 | });
189 | }
190 | ```
191 |
192 | 根据 ServiceAttribute 注册WebApi和分配路由地址
193 | ```csharp
194 | [AttributeUsage(AttributeTargets.Class)]
195 | public class ServiceAttribute : Attribute
196 | {
197 | public ServiceAttribute()
198 | {
199 | ServiceName = string.Empty;
200 | }
201 |
202 | public ServiceAttribute(string serviceName)
203 | {
204 | ServiceName = serviceName;
205 | }
206 |
207 | public string ServiceName { get; }
208 | }
209 | ```
210 |
211 | 实现 ISelectController 接口,通过查找类是否有ServiceAttribute特性为注册WebApi条件
212 | ```csharp
213 | internal class ServiceLocalSelectController : ISelectController
214 | {
215 | public bool IsController(Type type)
216 | {
217 | return type.IsPublic && type.GetCustomAttribute() != null;
218 | }
219 | }
220 | ```
221 |
222 | 实现 IActionRouteFactory 接口,生成路由规则 /api/ServiceName/Method
223 | ```csharp
224 | internal class ServiceActionRouteFactory : IActionRouteFactory
225 | {
226 | public string CreateActionRouteModel(string areaName, string controllerName, ActionModel action)
227 | {
228 | var controllerType = action.ActionMethod.DeclaringType;
229 | var serviceAttribute = controllerType.GetCustomAttribute();
230 |
231 | var _controllerName = serviceAttribute.ServiceName == string.Empty ? controllerName.Replace("Service", "") : serviceAttribute.ServiceName.Replace("Service", "");
232 |
233 | return $"api/{_controllerName}/{action.ActionName.Replace("Async", "")}";
234 | }
235 | }
236 | ```
237 | ServiceAttribute.ServiceName为空时,controllerName 替换 "Service" 字符串为空,反之则 ServiceAttribute.ServiceName 替换 "Service" 字符串为空。
238 | Method 名替换 "Async" 字符串为空。
239 |
240 | ### 2.外部动态 WebApi
241 | 创建一个 Other.Controller
242 | 实现 一个外部 Service
243 | ```csharp
244 | [Service("Other.Server")]
245 | public class OtherService
246 | {
247 | public int Show()
248 | {
249 | return 100;
250 | }
251 |
252 | public Task TaskIntAsync()
253 | {
254 | return Task.FromResult(100);
255 | }
256 | }
257 | ```
258 |
259 | ```csharp
260 | public void ConfigureServices(IServiceCollection services)
261 | {
262 | // 自定义配置
263 | services.AddDynamicWebApiCore();
264 | }
265 |
266 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
267 | {
268 | if (env.IsDevelopment())
269 | {
270 | app.UseDeveloperExceptionPage();
271 | }
272 |
273 | app.UseDynamicWebApi((serviceProvider,options) =>
274 | {
275 | options.AddAssemblyOptions(typeof(OtherService).Assembly);
276 | });
277 | }
278 | ```
279 |
280 | ## 3.配置
281 |
282 | 所有的配置均在对象 `DynamicWebApiOptions` 中,说明如下:
283 |
284 | | 属性名 | 是否必须 | 说明 |
285 | | --------------------------- | -------- | --------------------------------------------------------- |
286 | | DefaultHttpVerb | 否 | 默认值:POST。默认HTTP动词 |
287 | | DefaultAreaName | 否 | 默认值:空。Area 路由名称 |
288 | | DefaultApiPrefix | 否 | 默认值:api。API路由前缀 |
289 | | RemoveControllerPostfixes | 否 | 默认值:AppService/ApplicationService。类名需要移除的后缀 |
290 | | RemoveActionPostfixes | 否 | 默认值:Async。方法名需要移除的后缀 |
291 | | FormBodyBindingIgnoredTypes | 否 | 默认值:IFormFile。不通过MVC绑定到参数列表的类型。 |
292 |
293 | ## 4.疑难解答
294 |
295 | 若遇到问题,可使用 [Issues](https://github.com/dotnetauth/Panda.DynamicWebApi/issues) 进行提问。
296 |
297 | ## 5.引用项目说明
298 |
299 | > 本项目直接或间接引用了以下项目
300 |
301 | - [ABP](https://github.com/aspnetboilerplate/aspnetboilerplate)
302 |
303 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/src/Panda.DynamicWebApi/DynamicWebApiConvention.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 | using Microsoft.AspNetCore.Authorization;
6 | using Microsoft.AspNetCore.Mvc;
7 | using Microsoft.AspNetCore.Mvc.ActionConstraints;
8 | using Microsoft.AspNetCore.Mvc.ApplicationModels;
9 | using Microsoft.AspNetCore.Mvc.ModelBinding;
10 | using Microsoft.Extensions.DependencyInjection;
11 | using Panda.DynamicWebApi.Attributes;
12 | using Panda.DynamicWebApi.Helpers;
13 |
14 | namespace Panda.DynamicWebApi
15 | {
16 | public class DynamicWebApiConvention : IApplicationModelConvention
17 | {
18 | private readonly ISelectController _selectController;
19 | private readonly IActionRouteFactory _actionRouteFactory;
20 |
21 | public DynamicWebApiConvention(ISelectController selectController, IActionRouteFactory actionRouteFactory)
22 | {
23 | _selectController = selectController;
24 | _actionRouteFactory = actionRouteFactory;
25 | }
26 |
27 | public void Apply(ApplicationModel application)
28 | {
29 | foreach (var controller in application.Controllers)
30 | {
31 | var type = controller.ControllerType.AsType();
32 | var dynamicWebApiAttr = ReflectionHelper.GetSingleAttributeOrDefaultByFullSearch(type.GetTypeInfo());
33 |
34 | if (!(_selectController is DefaultSelectController) && _selectController.IsController(type))
35 | {
36 | controller.ControllerName = controller.ControllerName.RemovePostFix(AppConsts.ControllerPostfixes.ToArray());
37 | ConfigureDynamicWebApi(controller, dynamicWebApiAttr);
38 | }
39 | else
40 | {
41 | if (typeof(IDynamicWebApi).GetTypeInfo().IsAssignableFrom(type))
42 | {
43 | controller.ControllerName = controller.ControllerName.RemovePostFix(AppConsts.ControllerPostfixes.ToArray());
44 | ConfigureArea(controller, dynamicWebApiAttr);
45 | ConfigureDynamicWebApi(controller, dynamicWebApiAttr);
46 | }
47 | else
48 | {
49 | if (dynamicWebApiAttr != null)
50 | {
51 | ConfigureArea(controller, dynamicWebApiAttr);
52 | ConfigureDynamicWebApi(controller, dynamicWebApiAttr);
53 | }
54 | }
55 | }
56 | }
57 | }
58 |
59 | private void ConfigureArea(ControllerModel controller, DynamicWebApiAttribute attr)
60 | {
61 | if (!controller.RouteValues.ContainsKey("area"))
62 | {
63 | if (attr == null)
64 | {
65 | throw new ArgumentException(nameof(attr));
66 | }
67 |
68 | if (!string.IsNullOrEmpty(attr.Module))
69 | {
70 | controller.RouteValues["area"] = attr.Module;
71 | }
72 | else if (!string.IsNullOrEmpty(AppConsts.DefaultAreaName))
73 | {
74 | controller.RouteValues["area"] = AppConsts.DefaultAreaName;
75 | }
76 | }
77 |
78 | }
79 |
80 | private void ConfigureDynamicWebApi(ControllerModel controller, DynamicWebApiAttribute controllerAttr)
81 | {
82 | ConfigureApiExplorer(controller);
83 | ConfigureSelector(controller, controllerAttr);
84 | ConfigureParameters(controller);
85 | }
86 |
87 |
88 | private void ConfigureParameters(ControllerModel controller)
89 | {
90 | foreach (var action in controller.Actions)
91 | {
92 | if (!CheckNoMapMethod(action))
93 | foreach (var para in action.Parameters)
94 | {
95 | if (para.BindingInfo != null)
96 | {
97 | continue;
98 | }
99 |
100 | if (!TypeHelper.IsPrimitiveExtendedIncludingNullable(para.ParameterInfo.ParameterType))
101 | {
102 | if (CanUseFormBodyBinding(action, para))
103 | {
104 | para.BindingInfo = BindingInfo.GetBindingInfo(new[] { new FromBodyAttribute() });
105 | }
106 | }
107 | }
108 | }
109 | }
110 |
111 |
112 | private bool CanUseFormBodyBinding(ActionModel action, ParameterModel parameter)
113 | {
114 | if (AppConsts.FormBodyBindingIgnoredTypes.Any(t => t.IsAssignableFrom(parameter.ParameterInfo.ParameterType)))
115 | {
116 | return false;
117 | }
118 |
119 | foreach (var selector in action.Selectors)
120 | {
121 | if (selector.ActionConstraints == null)
122 | {
123 | continue;
124 | }
125 |
126 | foreach (var actionConstraint in selector.ActionConstraints)
127 | {
128 |
129 | var httpMethodActionConstraint = actionConstraint as HttpMethodActionConstraint;
130 | if (httpMethodActionConstraint == null)
131 | {
132 | continue;
133 | }
134 |
135 | if (httpMethodActionConstraint.HttpMethods.All(hm => hm.IsIn("GET", "DELETE", "TRACE", "HEAD")))
136 | {
137 | return false;
138 | }
139 | }
140 | }
141 |
142 | return true;
143 | }
144 |
145 |
146 | #region ConfigureApiExplorer
147 |
148 | private void ConfigureApiExplorer(ControllerModel controller)
149 | {
150 | if (controller.ApiExplorer.GroupName.IsNullOrEmpty())
151 | {
152 | controller.ApiExplorer.GroupName = controller.ControllerName;
153 | }
154 |
155 | if (controller.ApiExplorer.IsVisible == null)
156 | {
157 | controller.ApiExplorer.IsVisible = true;
158 | }
159 |
160 | foreach (var action in controller.Actions)
161 | {
162 | if (!CheckNoMapMethod(action))
163 | ConfigureApiExplorer(action);
164 | }
165 | }
166 |
167 | private void ConfigureApiExplorer(ActionModel action)
168 | {
169 | if (action.ApiExplorer.IsVisible == null)
170 | {
171 | action.ApiExplorer.IsVisible = true;
172 | }
173 | }
174 |
175 | #endregion
176 | ///
177 | /// //不映射指定的方法
178 | ///
179 | ///
180 | ///
181 | private bool CheckNoMapMethod(ActionModel action)
182 | {
183 | bool isExist = false;
184 | var noMapMethod = ReflectionHelper.GetSingleAttributeOrDefault(action.ActionMethod);
185 |
186 | if (noMapMethod != null)
187 | {
188 | action.ApiExplorer.IsVisible = false;//对应的Api不映射
189 | isExist = true;
190 | }
191 |
192 | return isExist;
193 | }
194 | private void ConfigureSelector(ControllerModel controller, DynamicWebApiAttribute controllerAttr)
195 | {
196 |
197 | if (controller.Selectors.Any(selector => selector.AttributeRouteModel != null))
198 | {
199 | return;
200 | }
201 |
202 | var areaName = string.Empty;
203 |
204 | if (controllerAttr != null)
205 | {
206 | areaName = controllerAttr.Module;
207 | }
208 |
209 | foreach (var action in controller.Actions)
210 | {
211 | if (!CheckNoMapMethod(action))
212 | ConfigureSelector(areaName, controller.ControllerName, action);
213 | }
214 | }
215 |
216 | private void ConfigureSelector(string areaName, string controllerName, ActionModel action)
217 | {
218 |
219 | var nonAttr = ReflectionHelper.GetSingleAttributeOrDefault(action.ActionMethod);
220 |
221 | if (nonAttr != null)
222 | {
223 | return;
224 | }
225 |
226 | if (action.Selectors.IsNullOrEmpty() || action.Selectors.Any(a => a.ActionConstraints.IsNullOrEmpty()))
227 | {
228 | if (!CheckNoMapMethod(action))
229 | AddAppServiceSelector(areaName, controllerName, action);
230 | }
231 | else
232 | {
233 | NormalizeSelectorRoutes(areaName, controllerName, action);
234 | }
235 | }
236 |
237 | private void AddAppServiceSelector(string areaName, string controllerName, ActionModel action)
238 | {
239 |
240 | var verb = GetHttpVerb(action);
241 |
242 | action.ActionName = GetRestFulActionName(action.ActionName);
243 |
244 | var appServiceSelectorModel = action.Selectors[0];
245 |
246 | if (appServiceSelectorModel.AttributeRouteModel == null)
247 | {
248 | appServiceSelectorModel.AttributeRouteModel = CreateActionRouteModel(areaName, controllerName, action);
249 | }
250 |
251 | if (!appServiceSelectorModel.ActionConstraints.Any())
252 | {
253 | appServiceSelectorModel.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { verb }));
254 | switch (verb)
255 | {
256 | case "GET":
257 | appServiceSelectorModel.EndpointMetadata.Add(new HttpGetAttribute());
258 | break;
259 | case "POST":
260 | appServiceSelectorModel.EndpointMetadata.Add(new HttpPostAttribute());
261 | break;
262 | case "PUT":
263 | appServiceSelectorModel.EndpointMetadata.Add(new HttpPutAttribute());
264 | break;
265 | case "DELETE":
266 | appServiceSelectorModel.EndpointMetadata.Add(new HttpDeleteAttribute());
267 | break;
268 | default:
269 | throw new Exception($"Unsupported http verb: {verb}.");
270 | }
271 | }
272 |
273 |
274 | }
275 |
276 |
277 |
278 | ///
279 | /// Processing action name
280 | ///
281 | ///
282 | ///
283 | private static string GetRestFulActionName(string actionName)
284 | {
285 | // custom process action name
286 | var appConstsActionName = AppConsts.GetRestFulActionName?.Invoke(actionName);
287 | if (appConstsActionName != null)
288 | {
289 | return appConstsActionName;
290 | }
291 |
292 | // default process action name.
293 |
294 | // Remove Postfix
295 | actionName = actionName.RemovePostFix(AppConsts.ActionPostfixes.ToArray());
296 |
297 | // Remove Prefix
298 | var verbKey = actionName.GetPascalOrCamelCaseFirstWord().ToLower();
299 | if (AppConsts.HttpVerbs.ContainsKey(verbKey))
300 | {
301 | if (actionName.Length == verbKey.Length)
302 | {
303 | return "";
304 | }
305 | else
306 | {
307 | return actionName.Substring(verbKey.Length);
308 | }
309 | }
310 | else
311 | {
312 | return actionName;
313 | }
314 | }
315 |
316 | private void NormalizeSelectorRoutes(string areaName, string controllerName, ActionModel action)
317 | {
318 | action.ActionName = GetRestFulActionName(action.ActionName);
319 | foreach (var selector in action.Selectors)
320 | {
321 | selector.AttributeRouteModel = selector.AttributeRouteModel == null ?
322 | CreateActionRouteModel(areaName, controllerName, action) :
323 | AttributeRouteModel.CombineAttributeRouteModel(CreateActionRouteModel(areaName, controllerName, action), selector.AttributeRouteModel);
324 | }
325 | }
326 |
327 | private static string GetHttpVerb(ActionModel action)
328 | {
329 | var getValueSuccess = AppConsts.AssemblyDynamicWebApiOptions
330 | .TryGetValue(action.Controller.ControllerType.Assembly, out AssemblyDynamicWebApiOptions assemblyDynamicWebApiOptions);
331 | if (getValueSuccess && !string.IsNullOrWhiteSpace(assemblyDynamicWebApiOptions?.HttpVerb))
332 | {
333 | return assemblyDynamicWebApiOptions.HttpVerb;
334 | }
335 |
336 |
337 | var verbKey = action.ActionName.GetPascalOrCamelCaseFirstWord().ToLower();
338 |
339 | var verb = AppConsts.HttpVerbs.ContainsKey(verbKey) ? AppConsts.HttpVerbs[verbKey] : AppConsts.DefaultHttpVerb;
340 | return verb;
341 | }
342 |
343 | private AttributeRouteModel CreateActionRouteModel(string areaName, string controllerName, ActionModel action)
344 | {
345 | var route = _actionRouteFactory.CreateActionRouteModel(areaName, controllerName, action);
346 |
347 | return new AttributeRouteModel(new RouteAttribute(route));
348 | }
349 | }
350 | }
--------------------------------------------------------------------------------