Skip to content
Promote Your Product

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, HttpPost is 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()
{
    ...
}