AdminBlazor
AdminBlazor copied to clipboard
【讨论】对外提供访问接口的模板
我这边开发时,生成的应用还需要提供出接口供前端访问,这里基于这个需求和聚合根的特点,提供一个案例供大家参考:
首先是分页查询的入参:
/// <summary>
/// 分页查询输入
/// </summary>
public class PagedInput
{
/// <summary>
/// 过滤条件
/// </summary>
public DynamicFilterInfo? Filter { get; set; }
/// <summary>
/// 页码
/// </summary>
public int Page { get; set; }
/// <summary>
/// 分页数
/// </summary>
public int Size { get; set; }
/// <summary>
/// 排序
/// </summary>
public string? OrderBy { get; set; }
/// <summary>
/// 是否正序
/// </summary>
public bool Asc { get; set; }
/// <summary>
/// 包含的导航属性,如果一对多关系,需要包含多级导航属性用逗号分隔
/// </summary>
public string[]? Include { get; set; }
}
返回内容就比较简单了:
/// <summary>
/// 分页查询结果
/// </summary>
public class PagedOutput<TEntity> where TEntity : class
{
/// <summary>
/// 总数
/// </summary>
public long Total { get; set; }
/// <summary>
/// 分页数据
/// </summary>
public List<TEntity> Data { get; set; }
}
然后控制器设置一个 EntityControllerBase 方便其他聚合根实体对外公开接口时继承:
public class EntityControllerBase<TEntity, TKey>(IAggregateRootRepository<TEntity> repository)
where TEntity : class, IEntity<TKey>
{
/// <summary>
/// 查询
/// </summary>
[HttpGet]
public Task<TEntity> GetAsync(TKey id, CancellationToken cancellationToken) =>
repository.Select.Where(t => t.Id.Equals(id)).FirstAsync(cancellationToken);
/// <summary>
/// 分页查询
/// </summary>
[HttpPost]
public Task<PagedOutput<TEntity>> GetPagedAsync([FromBody] PagedInput input, CancellationToken cancellationToken)
{
var select = repository.Select;
if (input.Filter != null)
{
select = select.WhereDynamicFilter(input.Filter);
}
if (input.Include is { Length: > 0 })
{
foreach (var includeProperty in input.Include)
{
if (string.IsNullOrWhiteSpace(includeProperty)) continue;
var includeProperties = includeProperty.Split(",");
// includeProperties 长度不固定,循环构造表达式树获取内容
Expression<Action<ISelect<object>>> exp = null;
for (var i = includeProperties.Length - 1; i > 0; i--)
{
// 创建一个表示输入参数的参数表达式
var parameter = Expression.Parameter(typeof(ISelect<object>), "then" + i);
if (exp == null)
{
// 获取 IncludeByPropertyName 方法的 MethodInfo
var methodInfo = typeof(ISelect<object>).GetMethod("IncludeByPropertyName", [typeof(string)]);
var propertyName = Expression.Constant(includeProperties[i]);
var methodCall = Expression.Call(parameter, methodInfo, propertyName);
// 创建一个带有输入参数和方法调用的 Lambda 表达式
exp = Expression.Lambda<Action<ISelect<object>>>(methodCall, parameter);
}
else
{
// 获取 IncludeByPropertyName 方法的 MethodInfo
var methodInfo = typeof(ISelect<object>).GetMethod("IncludeByPropertyName",
[typeof(string), typeof(Expression<Action<ISelect<object>>>)]);
var propertyName = Expression.Constant(includeProperties[i]);
var methodCall = Expression.Call(parameter, methodInfo, propertyName, exp);
// 创建一个带有输入参数和方法调用的 Lambda 表达式
exp = Expression.Lambda<Action<ISelect<object>>>(methodCall, parameter);
}
}
select = exp == null
? select.IncludeByPropertyName(includeProperties[0])
: select.IncludeByPropertyName(includeProperties[0], exp);
}
}
if (!string.IsNullOrEmpty(input.OrderBy))
{
select = select.OrderBy(input.OrderBy, input.Asc);
}
return select.Count(out var total)
.Page(Math.Max(input.Page, 1), Math.Min(Math.Max(input.Size, 1), 1000))
.ToListAsync(cancellationToken)
.ContinueWith(t => new PagedOutput<TEntity>
{
Total = total,
Data = t.Result
}, cancellationToken);
}
/// <summary>
/// 新增
/// </summary>
[HttpPost]
public Task<TEntity> AddAsync([FromBody] TEntity entity, CancellationToken cancellationToken) =>
repository.InsertAsync(entity, cancellationToken);
/// <summary>
/// 删除
/// </summary>
[HttpDelete]
public Task<int> DeleteAsync(TKey id, CancellationToken cancellationToken) =>
repository.DeleteAsync(t => t.Id.Equals(id), cancellationToken);
/// <summary>
/// 更新
/// </summary>
[HttpPost]
public Task<int> UpdateAsync([FromBody] TEntity entity, CancellationToken cancellationToken)
=> repository.UpdateDiy.SetSource(entity).ExecuteAffrowsAsync(cancellationToken);
/// <summary>
/// 批量新增
/// </summary>
[HttpPost]
public async Task<int> AddMultipleAsync([FromBody] TEntity[] entities, CancellationToken cancellationToken)
{
var count = 0;
foreach (var entity in entities)
{
await repository.InsertAsync(entity, cancellationToken);
count++;
}
return count;
}
/// <summary>
/// 批量删除
/// </summary>
[HttpDelete]
public Task<int> DeleteMultipleAsync(TKey[] id, CancellationToken cancellationToken) =>
repository.DeleteAsync(t => id.Contains(t.Id), cancellationToken);
/// <summary>
/// 批量更新
/// </summary>
[HttpPost]
public Task<int> UpdateMultipleAsync([FromBody] TEntity[] entities, CancellationToken cancellationToken) =>
repository.UpdateDiy.SetSource(entities).ExecuteAffrowsAsync(cancellationToken);
}
例如我有个 Component 实体,对外公开接口,则创建一个 ComponentController 继承 EntityControllerBase:
[Route("api/[controller]/[action]")]
public class ComponentController(IAggregateRootRepository<Component> repository)
: EntityControllerBase<Component, long>(repository)
{
}
主要是分页查询拓展了一下 Include 属性,这里是分页查询 Form 表单的一个入参示例:
{
"filter": {
"logic": "And",
"filters": [
{
"field": "id",
"operator": "GreaterThanOrEqual",
"value": 1
}
]
},
"page": 1,
"size": 10,
"orderBy": "id",
"asc": true,
"include": [
"formGroups",
"formGroups,warehouse",
"formGroups,warehouse.project",
"formItems",
"formItems,formItemProps",
"formItems,formItemProps,componentProp",
"formItems,formItemProps,componentProp.component"
]
}
有想法可以直接PR进来