
Dynamic Api
ZhonTai.DynamicApi is a dynamic Web API generation component. It can directly convert application classes that follow conventions into RESTful-style APIs, natively invoked by ASP.NET Core MVC, with zero Controller code and no performance overhead. The component seamlessly integrates with Swagger, automatically generating API documentation that is identical to hand-written Controllers.
Use Cases
In DDD architecture application logic layers, this component allows application services to be directly exposed as Web APIs, completely eliminating the repetitive work of manually writing Controllers.
Configure Dynamic Api
Configure dynamic Api in the Program.cs file under the MyApp.Host project
cs
new HostApp(new HostAppOptions
{
ConfigureDynamicApi = options =>
{
// API naming in camelCase
options.NamingConvention = NamingConventionEnum.CamelCase;
// Set the global default API prefix
options.DefaultApiPrefix = "api";
/*
* Clear API suffix, without removing the API suffix;
* If not cleared, CreatUserAsync will become CreateUser
*/
options.RemoveActionPostfixes.Clear();
// Custom ActionName handler
options.GetRestFulActionName = (actionName) => actionName;
// Specify assembly to configure all API requests as POST
options.AddAssemblyOptions(GetType().Assembly, httpVerb: "POST");
/*
* Specify assembly to configure URL prefix as api
* E.g.: http://localhost:8010/api/User/CreateUser
*/
options.AddAssemblyOptions(GetType().Assembly, apiPreFix: "api");
/*
* Specify assembly to configure URL prefix as api, and all requests as POST
* E.g.: http://localhost:8010/api/User/CreateUser
*/
options.AddAssemblyOptions(GetType().Assembly, apiPreFix: "api", httpVerb: "POST");
// Generate request methods ["method name prefix"] = "verb"
AppConsts.HttpVerbs = new Dictionary<string, string>()
{
["add"] = "POST",
["create"] = "POST",
["insert"] = "POST",
["submit"] = "POST",
["post"] = "POST",
["get"] = "GET",
["find"] = "GET",
["fetch"] = "GET",
["query"] = "GET",
["update"] = "PUT",
["change"] = "PUT",
["put"] = "PUT",
["batch"] = "PUT",
["delete"] = "DELETE",
["soft"] = "DELETE",
["remove"] = "DELETE",
["clear"] = "DELETE",
};
}
}).Run(args);If no matching request method is found based on the prefix verb,
HttpPostis used
NamingConvention API naming options:
- CamelCase: camelCase naming
- PascalCase: PascalCase naming
- SnakeCase: snake_case naming
- KebabCase: kebab-case naming
- ExtensionCase: extension.case naming
- Custom: Custom API name, set via options.GetRestFulActionName = (actionName) => actionName;
Using Dynamic Api
Add the [DynamicApi(Area = ApiConsts.AreaName)] attribute to the service and inherit the IDynamicApi interface to automatically generate dynamic Api
cs
/// <summary>
/// Module service
/// </summary>
[DynamicApi(Area = ApiConsts.AreaName)]
public class ModuleService : BaseService, IModuleService, IDynamicApi
{
/// <summary>
/// Get page
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost]
public async Task<PageOutput<ModuleListOutput>> GetPageAsync(PageInput<ModuleGetPageDto> input)
{
var key = input.Filter?.Name;
var list = await _moduleRepository.Select
.WhereIf(key.NotNull(), a => a.Name.Contains(key))
.Count(out var total)
.OrderByDescending(true, c => c.Id)
.Page(input.CurrentPage, input.PageSize)
.ToListAsync<ModuleListOutput>();
var data = new PageOutput<ModuleListOutput>()
{
List = list,
Total = total
};
return data;
}
}Note
ApiConsts.AreaName defines app in the path /api/app/module/get
Dynamic Api generates request methods based on prefix verbs. Use the [HttpPost] attribute to change the request method.
New API documentation URL: http://localhost:8010/admin/index.html
Swagger API documentation URL: http://localhost:8010/admin/swagger/index.html
If the service is a dynamic Api, methods with transactions must be defined as virtual for transaction interception to work properly.
Disable Dynamic Api
Add the [NonAction] attribute to the service method to disable dynamic Api generation, making it a public service method
cs
[NonAction]
public async Task BatchSoftDeleteAsync(long[] ids)
{
await _moduleRepository.SoftDeleteAsync(ids);
}Dynamic Api Rename
Add the following attribute to the service method to rename it
API paths must use absolute paths for renaming to work properly
cs
[HttpPost(template: "/api/[area]/[controller]/[action]")]
public async Task BatchSoftDeleteAsync(long[] ids)
{
await _moduleRepository.SoftDeleteAsync(ids);
}Dynamic Api Ordering
Add the [Order] attribute to the service for ordering
Smaller values appear first
cs
/// <summary>
/// Module service
/// </summary>
[Order(1010)]
[DynamicApi(Area = ApiConsts.AreaName)]
public class ModuleService : BaseService, IModuleService, IDynamicApi
{
}Add the [Order] attribute to the service method for ordering
Larger values appear first
cs
[Order(1010)]
public async Task BatchSoftDeleteAsync(long[] ids)
{
await _moduleRepository.SoftDeleteAsync(ids);
}Dynamic Api Multiple Groups
Add the [DynamicApi] attribute to the service to specify different groups
cs
/// <summary>
/// Module service
/// </summary>
[DynamicApi(Area = ApiConsts.AreaName, GroupNames = new string[] { AdminConsts.AreaName })]
public class ModuleService : BaseService, IModuleService, IDynamicApi
{
}Add the [ApiGroup] attribute to the service method to specify different groups
API not grouped: [ApiGroup(NonGroup = true)]
cs
[ApiGroup(ApiConsts.AreaName, AdminConsts.AreaName)]
//[ApiGroup(GroupNames = new string[] { ApiConsts.AreaName, AdminConsts.AreaName })]
public async Task BatchSoftDeleteAsync(long[] ids)
{
await _moduleRepository.SoftDeleteAsync(ids);
}Dynamic Api Return Raw Data
When you don't want to return the unified wrapped data format
json
{
"success": true,
"code": null,
"msg": null,
"data": 1
}Add the [NonFormatResult] attribute to the service method to disable the unified data wrapper and return only the raw data
cs
[NonFormatResult]
public async Task<long> AddAsync(ModuleAddInput input)
{
var entity = input.Adapt<ModuleEntity>();
await _moduleRepository.InsertAsync(entity);
return entity.Id;
}Dynamic Api Authorization 8.3.0
Add the [ApiAccess("permission_code")] attribute to the service method
Note
[ApiAccess("permission_code_1", "permission_code_2", All = true)]
One or more permission codes can be set
All defaults to false - access is granted if any permission code is met. Set to true to require all permission codes.
cs
[ApiAccess("admin:dict:export")]
public async Task<ActionResult> ExportListAsync()
{
...
}Exclude from Swagger Documentation
When you have API endpoints that should not be publicly exposed or displayed in the API documentation, add the [ApiExplorerSettings(IgnoreApi = true)] attribute to the method.
cs
[ApiExplorerSettings(IgnoreApi = true)]
public async Task GetInternalDataForPublicAsync()
{
...
}Disable Operation Logging
When you have API endpoints that should not record operation logs, add [NoOperationLog] to the method.
cs
[NoOperationLog]
public async Task GetDataListAsync()
{
...
}