第四章:动态WebAPI开发
目录
- 动态WebAPI概述
- IDynamicApiController接口
- DynamicApiController特性标记
- 路由规则与约定
- HTTP方法约定
- 自定义路由规则
- API版本管理
- Swagger文档集成与配置
- 请求参数绑定
- 返回值处理
- 接口分组与标签
- 动态WebAPI高级配置
1. 动态WebAPI概述
1.1 什么是动态WebAPI
动态WebAPI是Furion框架最核心、最具特色的功能之一。它允许开发者 无需手动编写Controller类,只需创建普通的服务类,框架便能在运行时自动将其转换为标准的ASP.NET Core Web API控制器。
传统开发方式 vs Furion动态WebAPI:
// ============ 传统方式 ============
// 需要创建Controller,编写大量模板代码
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{private readonly IUserService _userService;public UserController(IUserService userService){_userService = userService;}[HttpGet]public async Task<ActionResult<List<UserDto>>> GetUsers(){var users = await _userService.GetUsersAsync();return Ok(users);}[HttpGet("{id}")]public async Task<ActionResult<UserDto>> GetUser(int id){var user = await _userService.GetUserByIdAsync(id);return Ok(user);}[HttpPost]public async Task<ActionResult<int>> CreateUser(CreateUserInput input){var id = await _userService.CreateUserAsync(input);return Ok(id);}
}// 还需要定义Service接口和实现
public interface IUserService
{Task<List<UserDto>> GetUsersAsync();Task<UserDto> GetUserByIdAsync(int id);Task<int> CreateUserAsync(CreateUserInput input);
}public class UserService : IUserService
{// 实现...
}
// ============ Furion动态WebAPI方式 ============
// 只需一个类,自动生成API接口
public class UserService : IDynamicApiController
{public async Task<List<UserDto>> GetUsersAsync(){// 自动映射为 GET /api/user/usersreturn await _repository.AsQueryable().ToListAsync();}public async Task<UserDto> GetUserByIdAsync(int id){// 自动映射为 GET /api/user/user-by-id?id={id}return await _repository.FindAsync(id);}public async Task<int> CreateUserAsync(CreateUserInput input){// 自动映射为 POST /api/user/create-uservar entity = await _repository.InsertNowAsync(input.Adapt<User>());return entity.Entity.Id;}
}
1.2 动态WebAPI的优势
| 优势 | 说明 |
|---|---|
| 减少代码量 | 无需编写Controller、无需手动定义路由 |
| 消除重复 | 不再需要Controller到Service的参数传递代码 |
| 自动路由 | 根据方法名自动推断HTTP方法和路由 |
| Swagger集成 | 自动生成完整的API文档 |
| 灵活配置 | 支持自定义路由、分组、版本等 |
| 兼容性好 | 生成的是标准ASP.NET Core控制器,完全兼容现有生态 |
1.3 工作原理
动态WebAPI的工作原理是在应用启动时,Furion会扫描所有实现了IDynamicApiController接口或标记了[DynamicApiController]特性的类,然后通过ASP.NET Core的ApplicationPart和ApplicationModel机制,动态将这些类注册为控制器。
应用启动│├── 扫描程序集│ └── 查找IDynamicApiController / [DynamicApiController]│├── 解析方法信息│ ├── 方法名 → HTTP方法 + 路由│ ├── 参数 → 请求参数绑定│ └── 返回值 → 响应类型│├── 创建ControllerModel│ └── 注册到ASP.NET Core路由系统│└── API可用└── Swagger文档自动生成
2. IDynamicApiController接口
2.1 基本用法
IDynamicApiController是一个标记接口(Marker Interface),不包含任何方法定义,实现该接口的类会被自动识别为动态API控制器:
using Furion.DynamicApiController;namespace MyApp.Application.Services;/// <summary>
/// 产品管理服务
/// </summary>
public class ProductService : IDynamicApiController
{/// <summary>/// 获取所有产品/// </summary>/// <returns>产品列表</returns>public List<ProductDto> GetAll(){return new List<ProductDto>{new ProductDto { Id = 1, Name = "笔记本电脑", Price = 5999 },new ProductDto { Id = 2, Name = "机械键盘", Price = 399 },new ProductDto { Id = 3, Name = "无线鼠标", Price = 129 }};}/// <summary>/// 根据ID获取产品/// </summary>/// <param name="id">产品ID</param>/// <returns>产品信息</returns>public ProductDto GetById(int id){return new ProductDto { Id = id, Name = "示例产品", Price = 99 };}/// <summary>/// 创建产品/// </summary>/// <param name="input">产品信息</param>/// <returns>新产品ID</returns>public int CreateProduct(CreateProductInput input){// 业务逻辑...return 1;}/// <summary>/// 更新产品/// </summary>/// <param name="id">产品ID</param>/// <param name="input">更新信息</param>public void UpdateProduct(int id, UpdateProductInput input){// 业务逻辑...}/// <summary>/// 删除产品/// </summary>/// <param name="id">产品ID</param>public void DeleteProduct(int id){// 业务逻辑...}
}
上述代码自动生成以下API接口:
| HTTP方法 | 路由 | 对应方法 |
|---|---|---|
| GET | /api/product/all |
GetAll() |
| GET | /api/product/by-id?id={id} |
GetById(int id) |
| POST | /api/product/create-product |
CreateProduct(...) |
| PUT | /api/product/update-product?id={id} |
UpdateProduct(...) |
| DELETE | /api/product/delete-product?id={id} |
DeleteProduct(...) |
2.2 构造函数注入
动态API控制器完全支持构造函数依赖注入:
public class OrderService : IDynamicApiController
{private readonly IRepository<Order> _orderRepo;private readonly ILogger<OrderService> _logger;private readonly IMapper _mapper;public OrderService(IRepository<Order> orderRepo,ILogger<OrderService> logger,IMapper mapper){_orderRepo = orderRepo;_logger = logger;_mapper = mapper;}public async Task<List<OrderDto>> GetAllAsync(){_logger.LogInformation("正在查询所有订单");var orders = await _orderRepo.AsQueryable().ToListAsync();return _mapper.Map<List<OrderDto>>(orders);}
}
2.3 排除方法
如果不希望某个公开方法被暴露为API接口,可以使用[NonAction]特性:
using Microsoft.AspNetCore.Mvc;public class HelperService : IDynamicApiController
{// ✅ 这个方法会暴露为APIpublic string GetVersion(){return "1.0.0";}// ❌ 这个方法不会暴露为API[NonAction]public string InternalHelper(){return "内部辅助方法";}
}
3. DynamicApiController特性标记
3.1 使用特性标记
除了实现IDynamicApiController接口外,还可以使用[DynamicApiController]特性来标记动态API控制器:
using Furion.DynamicApiController;/// <summary>
/// 使用特性标记方式
/// </summary>
[DynamicApiController]
public class CategoryService
{public List<string> GetCategories(){return new List<string> { "电子产品", "家居用品", "服装鞋帽" };}public string GetCategory(int id){return $"分类 {id}";}
}
3.2 两种方式的对比
| 特性 | IDynamicApiController接口 | [DynamicApiController]特性 |
|---|---|---|
| 使用方式 | 实现接口 | 添加特性 |
| 侵入性 | 需要引用Furion | 需要引用Furion |
| 继承传递 | 子类自动继承 | 子类不自动继承 |
| 适用场景 | 推荐用于标准服务类 | 适用于不想改变继承关系的类 |
| 语义性 | 语义更强 | 更灵活 |
3.3 基类方式
可以创建一个基类来统一实现IDynamicApiController接口:
/// <summary>
/// 应用服务基类
/// </summary>
public abstract class BaseAppService : IDynamicApiController
{/// <summary>/// 获取当前登录用户ID/// </summary>protected long CurrentUserId => App.User?.FindFirst("UserId")?.Value.ToLong() ?? 0;/// <summary>/// 获取当前租户ID/// </summary>protected long CurrentTenantId => App.User?.FindFirst("TenantId")?.Value.ToLong() ?? 0;
}/// <summary>
/// 订单服务 - 继承基类,自动成为动态API
/// </summary>
public class OrderAppService : BaseAppService
{public List<OrderDto> GetMyOrders(){var userId = CurrentUserId;// 查询当前用户的订单...return new List<OrderDto>();}
}
4. 路由规则与约定
4.1 默认路由规则
Furion的动态WebAPI有一套完善的默认路由生成规则:
规则1:控制器名称
类名会被自动处理为路由前缀:
- 移除常见后缀:
Service、AppService、Application - 转换为小写kebab-case格式
UserService → /api/user/...
ProductAppService → /api/product/...
OrderApplication → /api/order/...
CategoryService → /api/category/...
规则2:方法名称转换
方法名会经过以下处理:
- 移除HTTP方法前缀(Get、Post、Put、Delete等)
- 移除Async后缀
- 转换为小写kebab-case格式
public class UserService : IDynamicApiController
{// GET /api/user/allpublic List<User> GetAll() { }// GET /api/user/by-idpublic User GetById(int id) { }// POST /api/userpublic void Post(CreateUserInput input) { }// PUT /api/user/infopublic void UpdateInfo(UpdateUserInput input) { }// DELETE /api/userpublic void Delete(int id) { }// GET /api/user/active-users(异步方法自动移除Async后缀)public Task<List<User>> GetActiveUsersAsync() { }
}
4.2 路由转换规则详解
| 方法名 | 转换后路由 | HTTP方法 |
|---|---|---|
GetAll |
/all |
GET |
GetById |
/by-id |
GET |
GetUserInfo |
/user-info |
GET |
PostUser |
/user |
POST |
CreateOrder |
/create-order |
POST |
UpdateUserName |
/update-user-name |
PUT |
DeleteById |
/by-id |
DELETE |
GetActiveUsersAsync |
/active-users |
GET |
Post |
/ |
POST |
Get |
/ |
GET |
4.3 PascalCase到kebab-case的转换
Furion会自动将方法名从PascalCase转换为kebab-case:
GetUserList → get-user-list → /api/user/user-list (去掉Get前缀)
CreateNewOrder → create-new-order → /api/order/create-new-order
FindByNameAndAge → find-by-name-and-age
UpdateUserProfile → update-user-profile
4.4 默认路由模板
默认的路由模板为:
api/[controller]/[action]
其中:
api是固定前缀[controller]是控制器名称(类名去掉后缀)[action]是操作名称(方法名去掉HTTP前缀)
5. HTTP方法约定
5.1 方法名前缀约定
Furion通过方法名的前缀来自动推断HTTP方法:
| 方法名前缀 | 对应HTTP方法 | 常见用途 |
|---|---|---|
Get、Find、Fetch、Query、Search |
GET | 查询数据 |
Post、Create、Add、Insert、Submit |
POST | 创建数据 |
Put、Update、Modify、Change、Edit |
PUT | 更新数据 |
Delete、Remove、Clear、Cancel |
DELETE | 删除数据 |
Patch |
PATCH | 部分更新 |
public class ArticleService : IDynamicApiController
{// ===== GET 方法 =====public List<Article> GetAll() { } // GET /api/article/allpublic Article FindById(int id) { } // GET /api/article/by-idpublic List<Article> FetchLatest() { } // GET /api/article/latestpublic List<Article> QueryByCategory(int c) {} // GET /api/article/by-categorypublic List<Article> SearchByTitle(string t) {} // GET /api/article/by-title// ===== POST 方法 =====public int CreateArticle(ArticleInput i) { } // POST /api/article/articlepublic int AddComment(CommentInput i) { } // POST /api/article/commentpublic void SubmitReview(int id) { } // POST /api/article/review// ===== PUT 方法 =====public void UpdateTitle(int id, string t) { } // PUT /api/article/titlepublic void ModifyContent(int id, string c) { } // PUT /api/article/contentpublic void EditArticle(ArticleInput i) { } // PUT /api/article/article// ===== DELETE 方法 =====public void DeleteArticle(int id) { } // DELETE /api/article/articlepublic void RemoveComment(int id) { } // DELETE /api/article/commentpublic void ClearDrafts() { } // DELETE /api/article/drafts
}
5.2 显式指定HTTP方法
如果默认约定不满足需求,可以使用ASP.NET Core的HTTP方法特性显式指定:
using Microsoft.AspNetCore.Mvc;public class PaymentService : IDynamicApiController
{// 显式指定为POST(虽然以Get开头)[HttpPost]public PaymentResult GetPaymentUrl(PaymentInput input){// 获取支付链接通常需要POSTreturn new PaymentResult { Url = "https://pay.example.com/..." };}// 显式指定为GET(虽然以Check开头,默认会是POST)[HttpGet]public bool CheckPaymentStatus(string orderId){return true;}// 方法名不以约定前缀开头时,默认为POST// 可以显式指定为其他方法[HttpPut]public void Process(int orderId){// 处理订单}
}
5.3 无前缀方法名的默认行为
如果方法名不以任何约定前缀开头,Furion默认将其视为 POST 方法:
public class TaskService : IDynamicApiController
{public void Execute(int taskId) { } // POST /api/task/executepublic string Process(string data) { } // POST /api/task/processpublic bool Validate(ValidateInput i) { } // POST /api/task/validate
}
6. 自定义路由规则
6.1 使用ApiDescriptionSettings自定义控制器路由
[ApiDescriptionSettings]特性允许自定义控制器级别的路由配置:
using Furion.DynamicApiController;
using Microsoft.AspNetCore.Mvc;// 自定义控制器名称
[ApiDescriptionSettings(Name = "Users")]
public class UserManagementService : IDynamicApiController
{// 生成路由:GET /api/users/list 而不是 /api/user-management/listpublic List<UserDto> GetList() { return new(); }
}// 自定义路由前缀
[ApiDescriptionSettings(Name = "Admin/Users")]
public class AdminUserService : IDynamicApiController
{// 生成路由:GET /api/admin/users/listpublic List<UserDto> GetList() { return new(); }
}
6.2 使用Route特性自定义路由
可以使用ASP.NET Core标准的[Route]特性来完全控制路由:
public class CustomRouteService : IDynamicApiController
{// 完全自定义路由[HttpGet("api/v1/users/{id}")]public UserDto GetUserById(int id){return new UserDto { Id = id, Name = "自定义路由用户" };}// 使用路由模板参数[HttpGet("api/departments/{deptId}/users")]public List<UserDto> GetDepartmentUsers(int deptId){return new List<UserDto>();}// 多路由映射到同一方法[HttpGet("api/users/search")][HttpGet("api/users/find")]public List<UserDto> SearchUsers(string keyword){return new List<UserDto>();}
}
6.3 配置全局路由前缀
在appsettings.json中可以配置全局路由前缀:
{"DynamicApiControllerSettings": {"DefaultRoutePrefix": "api","DefaultHttpMethod": "POST","LowercaseRoute": true,"KeepVerb": false,"KeepName": false,"AsLowerCamelCase": false,"SplitCamelCase": true,"AbandonControllerAffixes": ["Service", "AppService", "Application"],"AbandonActionAffixes": ["Async"]}
}
| 配置项 | 默认值 | 说明 |
|---|---|---|
DefaultRoutePrefix |
"api" |
全局路由前缀 |
DefaultHttpMethod |
"POST" |
无法推断HTTP方法时的默认方法 |
LowercaseRoute |
true |
路由是否转为小写 |
KeepVerb |
false |
是否保留方法名中的HTTP动词前缀 |
KeepName |
false |
是否保留完整方法名 |
SplitCamelCase |
true |
是否将PascalCase拆分为kebab-case |
AbandonControllerAffixes |
[...] |
要移除的控制器名称后缀 |
AbandonActionAffixes |
["Async"] |
要移除的方法名后缀 |
6.4 路由规则示例对照
public class ProductService : IDynamicApiController
{// KeepVerb=false(默认): GET /api/product/list// KeepVerb=true: GET /api/product/get-listpublic List<Product> GetList() { }// KeepName=false(默认): POST /api/product/product// KeepName=true: POST /api/product/create-productpublic int CreateProduct(ProductInput input) { }// SplitCamelCase=true(默认): GET /api/product/by-category-id// SplitCamelCase=false: GET /api/product/bycategoryidpublic List<Product> GetByCategoryId(int categoryId) { }
}
7. API版本管理
7.1 基于路由的版本管理
// V1版本
[ApiDescriptionSettings(Name = "v1/User")]
public class UserServiceV1 : IDynamicApiController
{// GET /api/v1/user/infopublic UserDto GetInfo(int id){return new UserDto { Id = id, Name = "V1接口" };}
}// V2版本
[ApiDescriptionSettings(Name = "v2/User")]
public class UserServiceV2 : IDynamicApiController
{// GET /api/v2/user/infopublic UserDtoV2 GetInfo(int id){return new UserDtoV2 { Id = id, Name = "V2接口", Avatar = "avatar.png" };}
}
7.2 基于分组的版本管理
// V1分组
[ApiDescriptionSettings("V1")]
public class UserServiceV1 : IDynamicApiController
{public UserDto GetInfo(int id) { return new(); }
}// V2分组
[ApiDescriptionSettings("V2")]
public class UserServiceV2 : IDynamicApiController
{public UserDtoV2 GetInfo(int id) { return new(); }
}
在Swagger中会根据分组分别显示不同版本的接口。
7.3 集成ASP.NET Core API版本控制
// 安装版本控制包
// dotnet add package Microsoft.AspNetCore.Mvc.Versioning// 在Startup中配置
services.AddApiVersioning(options =>
{options.DefaultApiVersion = new ApiVersion(1, 0);options.AssumeDefaultVersionWhenUnspecified = true;options.ReportApiVersions = true;
});
8. Swagger文档集成与配置
8.1 基本Swagger配置
Furion通过AddInject()和UseInject()自动集成了Swagger。默认配置下,访问应用根路径即可看到Swagger UI。
8.2 详细Swagger配置
// appsettings.json
{"SpecificationDocumentSettings": {"DocumentTitle": "MyApp API 文档","DefaultGroupName": "Default","FormatAsV2": false,"DocExpansionState": "List","XmlComments": true,"RoutePrefix": "api-docs","ServerDir": "","LoginInfo": {"Enabled": true},"GroupOpenApiInfos": [{"Group": "Default","Title": "通用接口","Description": "通用业务接口","Version": "v1.0","TermsOfService": "https://furion.net","Contact": {"Name": "开发团队","Email": "dev@example.com","Url": "https://example.com"}},{"Group": "System","Title": "系统管理接口","Description": "系统管理和配置相关接口","Version": "v1.0"}],"EnableAuthorized": true,"SecurityDefinitions": [{"Id": "Bearer","Type": "Http","Name": "Authorization","Description": "JWT授权。在下方输入Token(不需要加Bearer前缀)","BearerFormat": "JWT","Scheme": "bearer","In": "Header"}]}
}
8.3 XML注释显示
确保在所有包含动态API的项目中启用XML文档生成:
<!-- 在每个项目的.csproj中添加 -->
<PropertyGroup><GenerateDocumentationFile>true</GenerateDocumentationFile><NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
/// <summary>
/// 订单管理服务
/// </summary>
public class OrderService : IDynamicApiController
{/// <summary>/// 创建订单/// </summary>/// <remarks>/// 请求示例:////// POST /api/order/create/// {/// "productId": 1,/// "quantity": 2,/// "address": "北京市朝阳区"/// }////// </remarks>/// <param name="input">订单创建参数</param>/// <returns>订单编号</returns>/// <response code="200">创建成功</response>/// <response code="400">参数验证失败</response>public string CreateOrder(CreateOrderInput input){return "ORD20240115001";}
}
9. 请求参数绑定
9.1 参数绑定特性
Furion支持ASP.NET Core的所有参数绑定方式:
| 特性 | 说明 | 适用场景 |
|---|---|---|
[FromQuery] |
从URL查询字符串获取 | GET请求的简单参数 |
[FromBody] |
从请求体获取 | POST/PUT的复杂对象 |
[FromRoute] |
从路由模板获取 | 路径参数 |
[FromHeader] |
从请求头获取 | 认证信息、自定义头 |
[FromForm] |
从表单数据获取 | 文件上传、表单提交 |
9.2 默认绑定规则
Furion对不同HTTP方法有不同的默认参数绑定规则:
public class BindingDemoService : IDynamicApiController
{// GET方法:简单类型默认从Query获取// GET /api/binding-demo/user?id=1&name=张三public UserDto GetUser(int id, string name){// id 和 name 自动从QueryString获取return new UserDto { Id = id, Name = name };}// POST方法:复杂类型默认从Body获取// POST /api/binding-demo/user// Body: { "name": "张三", "email": "..." }public int CreateUser(CreateUserInput input){// input 自动从RequestBody获取return 1;}// PUT方法:复杂类型默认从Body获取// PUT /api/binding-demo/user?id=1// Body: { "name": "新名称" }public void UpdateUser(int id, UpdateUserInput input){// id 从QueryString获取// input 从RequestBody获取}
}
9.3 显式指定参数绑定
using Microsoft.AspNetCore.Mvc;public class ExplicitBindingService : IDynamicApiController
{// 从路由获取参数[HttpGet("api/users/{userId}/orders/{orderId}")]public OrderDto GetOrder([FromRoute] int userId,[FromRoute] int orderId){return new OrderDto();}// 从请求头获取参数[HttpGet]public UserDto GetCurrentUser([FromHeader(Name = "Authorization")] string token,[FromHeader(Name = "X-Tenant-Id")] string tenantId){return new UserDto();}// 混合绑定[HttpPost("api/products/{categoryId}")]public int CreateProduct([FromRoute] int categoryId,[FromQuery] string source,[FromBody] CreateProductInput input){return 1;}// 文件上传[HttpPost]public string UploadFile([FromForm] string description,[FromForm] IFormFile file){return file.FileName;}
}
9.4 复杂查询参数
// 分页查询DTO
public class PagedQueryInput
{public int PageIndex { get; set; } = 1;public int PageSize { get; set; } = 20;public string Keyword { get; set; }public string OrderBy { get; set; }public bool IsDesc { get; set; }
}public class UserQueryService : IDynamicApiController
{// GET /api/user-query/paged?pageIndex=1&pageSize=20&keyword=张// 复杂类型在GET方法中需要显式标记[FromQuery]public PagedResult<UserDto> GetPaged([FromQuery] PagedQueryInput input){return new PagedResult<UserDto>{Total = 100,PageIndex = input.PageIndex,PageSize = input.PageSize,Items = new List<UserDto>()};}
}
10. 返回值处理
10.1 基本返回类型
Furion的动态WebAPI支持各种返回类型:
public class ReturnDemoService : IDynamicApiController
{// 返回简单类型public string GetString() => "Hello";public int GetNumber() => 42;public bool GetBool() => true;// 返回对象public UserDto GetUser(){return new UserDto { Id = 1, Name = "张三" };}// 返回集合public List<UserDto> GetUsers(){return new List<UserDto>();}// 返回匿名对象public object GetDashboard(){return new{UserCount = 1000,OrderCount = 5000,Revenue = 1234567.89};}// 无返回值(void)public void PostAction(){// 返回204 No Content}// 返回Taskpublic async Task PostAsyncAction(){await Task.Delay(100);}// 返回Task<T>public async Task<UserDto> GetUserAsync(){return await Task.FromResult(new UserDto());}
}
10.2 规范化结果
Furion提供了规范化结果功能,将所有API返回值包装为统一格式:
// 启用规范化结果
services.AddControllers().AddInject();// 规范化返回格式示例:
// {
// "statusCode": 200,
// "succeeded": true,
// "data": { ... }, // 实际返回数据
// "errors": null,
// "extras": null,
// "timestamp": 1705312000
// }
自定义规范化结果提供器:
using Furion.DataValidation;
using Furion.UnifyResult;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;/// <summary>
/// 自定义规范化结果提供器
/// </summary>
[UnifyModel(typeof(ApiResult<>))]
public class CustomResultProvider : IUnifyResultProvider
{public IActionResult OnSucceeded(ActionExecutedContext context, object data){return new JsonResult(new ApiResult<object>{Code = 200,Success = true,Message = "请求成功",Data = data});}public IActionResult OnValidateFailed(ActionExecutingContext context, ValidationMetadata metadata){return new JsonResult(new ApiResult<object>{Code = 400,Success = false,Message = metadata.Message,Data = null});}public async Task OnResponseStatusCodes(HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings = null){switch (statusCode){case 401:await context.Response.WriteAsJsonAsync(new ApiResult<object>{Code = 401,Success = false,Message = "未授权",Data = null});break;case 403:await context.Response.WriteAsJsonAsync(new ApiResult<object>{Code = 403,Success = false,Message = "禁止访问",Data = null});break;}}public IActionResult OnException(ExceptionContext context, ExceptionMetadata metadata){return new JsonResult(new ApiResult<object>{Code = 500,Success = false,Message = metadata.Message,Data = null});}
}/// <summary>
/// 统一返回结果模型
/// </summary>
public class ApiResult<T>
{public int Code { get; set; }public bool Success { get; set; }public string Message { get; set; }public T Data { get; set; }
}
10.3 跳过规范化结果
某些接口不需要规范化结果包装时,可以使用特性跳过:
using Furion.UnifyResult;public class FileService : IDynamicApiController
{// 文件下载不需要规范化结果包装[NonUnify][HttpGet]public IActionResult DownloadFile(string fileName){var bytes = System.IO.File.ReadAllBytes($"uploads/{fileName}");return new FileContentResult(bytes, "application/octet-stream"){FileDownloadName = fileName};}// 健康检查不需要包装[NonUnify]public string HealthCheck() => "OK";
}
11. 接口分组与标签
11.1 使用ApiDescriptionSettings分组
// 系统管理分组
[ApiDescriptionSettings("System", Name = "SysUser", Order = 1)]
public class SysUserService : IDynamicApiController
{/// <summary>/// 获取系统用户列表/// </summary>public List<UserDto> GetList() => new();/// <summary>/// 创建系统用户/// </summary>public int Create(CreateUserInput input) => 1;
}// 业务管理分组
[ApiDescriptionSettings("Business", Name = "Order", Order = 1)]
public class OrderService : IDynamicApiController
{/// <summary>/// 获取订单列表/// </summary>public List<OrderDto> GetList() => new();
}// 报表分组
[ApiDescriptionSettings("Report", Name = "SalesReport", Order = 1)]
public class SalesReportService : IDynamicApiController
{/// <summary>/// 获取销售报表/// </summary>public object GetSalesReport() => new { };
}
11.2 Swagger分组配置
{"SpecificationDocumentSettings": {"GroupOpenApiInfos": [{"Group": "Default","Title": "默认分组","Description": "未分类的接口","Version": "v1.0"},{"Group": "System","Title": "系统管理","Description": "系统管理相关接口,包括用户、角色、菜单等","Version": "v1.0"},{"Group": "Business","Title": "业务管理","Description": "核心业务相关接口","Version": "v1.0"},{"Group": "Report","Title": "报表统计","Description": "数据报表和统计分析接口","Version": "v1.0"}]}
}
11.3 方法级别的分组
public class MixedService : IDynamicApiController
{// 同一个服务中的方法可以属于不同分组[ApiDescriptionSettings("System")]public string GetSystemInfo() => "系统信息";[ApiDescriptionSettings("Business")]public string GetBusinessData() => "业务数据";// 可以同时出现在多个分组中[ApiDescriptionSettings("System,Business")]public string GetCommonData() => "公共数据";
}
11.4 排序控制
// 通过Order控制接口在Swagger中的显示顺序
[ApiDescriptionSettings("System", Order = 1)] // 第一个显示
public class UserService : IDynamicApiController { }[ApiDescriptionSettings("System", Order = 2)] // 第二个显示
public class RoleService : IDynamicApiController { }[ApiDescriptionSettings("System", Order = 3)] // 第三个显示
public class MenuService : IDynamicApiController { }
12. 动态WebAPI高级配置
12.1 控制器命名规则配置
// 自定义控制器名称
[ApiDescriptionSettings(Name = "UserMgmt", // 自定义名称Tag = "用户管理", // Swagger标签Order = 1, // 排序Description = "用户管理相关接口" // 描述
)]
public class UserManagementService : IDynamicApiController
{public List<UserDto> GetList() => new();
}
12.2 隐藏接口
public class InternalService : IDynamicApiController
{// 公开接口public string GetPublicData() => "公开数据";// 隐藏接口(在Swagger中不显示,但仍可访问)[ApiDescriptionSettings(false)]public string GetHiddenData() => "隐藏数据";
}
12.3 自定义模型绑定
// 自定义日期格式绑定
public class DateRangeInput
{[ModelBinder(BinderType = typeof(DateTimeModelBinder))]public DateTime StartDate { get; set; }[ModelBinder(BinderType = typeof(DateTimeModelBinder))]public DateTime EndDate { get; set; }
}
12.4 动态WebAPI与传统Controller共存
Furion的动态WebAPI可以与传统的ASP.NET Core Controller共存,互不影响:
// 传统Controller
[ApiController]
[Route("api/[controller]")]
public class LegacyController : ControllerBase
{[HttpGet]public IActionResult Get() => Ok("传统Controller");
}// 动态WebAPI
public class ModernService : IDynamicApiController
{public string GetData() => "动态WebAPI";
}// 两者可以在同一个项目中同时使用
// GET /api/legacy → 传统Controller
// GET /api/modern/data → 动态WebAPI
12.5 性能优化建议
| 建议 | 说明 |
|---|---|
| 合理使用异步方法 | IO密集型操作使用async/await |
| 避免大对象返回 | 使用分页和投影减少返回数据量 |
| 利用缓存 | 频繁查询的数据使用缓存 |
| 压缩响应 | 启用gzip/brotli压缩 |
| 限制请求大小 | 配置最大请求体大小 |
// 异步方法示例
public class OptimizedService : IDynamicApiController
{private readonly IRepository<Product> _repo;private readonly IMemoryCache _cache;public OptimizedService(IRepository<Product> repo, IMemoryCache cache){_repo = repo;_cache = cache;}/// <summary>/// 分页查询(高性能)/// </summary>public async Task<PagedResult<ProductDto>> GetPagedAsync([FromQuery] PagedInput input){var cacheKey = $"products:{input.PageIndex}:{input.PageSize}:{input.Keyword}";return await _cache.GetOrCreateAsync(cacheKey, async entry =>{entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);var query = _repo.AsQueryable();if (!string.IsNullOrEmpty(input.Keyword)){query = query.Where(p => p.Name.Contains(input.Keyword));}var total = await query.CountAsync();var items = await query.Skip((input.PageIndex - 1) * input.PageSize).Take(input.PageSize).Select(p => new ProductDto { Id = p.Id, Name = p.Name }).ToListAsync();return new PagedResult<ProductDto>{Total = total,Items = items};});}
}
总结
本章全面介绍了Furion动态WebAPI的开发方法,这是Furion框架最核心的特色功能。通过动态WebAPI,开发者可以大幅减少样板代码,专注于业务逻辑的实现。
关键要点:
- 实现
IDynamicApiController接口或使用[DynamicApiController]特性即可创建动态API - 方法名前缀约定自动推断HTTP方法(Get→GET, Create→POST, Update→PUT, Delete→DELETE)
- 路由自动生成遵循
api/[controller]/[action]的模板,支持kebab-case转换 - 参数绑定遵循HTTP方法的默认规则,也可以显式指定
- 规范化结果提供统一的API返回格式
- 接口分组通过
ApiDescriptionSettings实现,方便API文档组织
下一章预告:第五章将详细介绍Furion的依赖注入与服务注册机制,包括接口标记注入、构造函数注入、属性注入等多种注入方式。
