在同学学习过程,部分同学向我反馈说每次新增接口都要在接口表里手动添加一条接口很麻烦,因此我把项目代码做了一个改动,使我们不需要手动添加,每次项目运行起来后就会自动把新的接口地址添加进去。
一、实现
首先,我们需要在项目根目录新建Initialization
文件夹,以后我们会将初始化项目、初始化数据库数据等类放在这里。接着我们在该文件夹下新建AddPath
类,这个类用于初始化数据库中SysUrl
表,代码如下:
using System.Reflection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Routing;
using SporeAccounting.Controllers;
using SporeAccounting.Models;
using SporeAccounting.Server.Interface;
namespace SporeAccounting.Initialization;
/// <summary>
/// 新增web api路径
/// </summary>
public static class AddPath
{
/// <summary>
/// 初始化
/// </summary>
/// <param name="serviceProvider"></param>
public static void Init(IServiceProvider serviceProvider)
{
using (var scope = serviceProvider.CreateScope())
{
var sysUrlServer = scope.ServiceProvider.GetRequiredService<ISysUrlServer>();
List<SysUrl> sysUrls = new List<SysUrl>();
//1. 通过反射获取所有的控制器
var controllers = Assembly.GetExecutingAssembly().GetTypes()
.Where(type => typeof(BaseController).IsAssignableFrom(type));
//2. 获取控制器的Route特性
foreach (var controller in controllers)
{
var routeAttribute = controller.GetCustomAttribute<RouteAttribute>();
if (routeAttribute != null)
{
//3. 根据特性生成完整的路径,
//如果路径中包含[controller],则替换为控制器的名称,反之直接使用路径
var controllerName = routeAttribute.Template;
if (controllerName.Contains("[controller]"))
{
controllerName =
controllerName.Replace("[controller]", controller.Name.Replace("Controller", ""));
}
//4. 获取controller的所有Action
var actions = controller.GetMethods()
.Where(method => method.IsPublic && !method.GetCustomAttributes<NonActionAttribute>().Any());
for (int i = 0; i < actions.Count(); i++)
{
var action = actions.ElementAt(i);
var actionRouteAttribute = action.GetCustomAttribute<RouteAttribute>();
if (actionRouteAttribute != null)
{
var actionName = actionRouteAttribute.Template;
if (actionName.Contains("[action]"))
{
actionName = actionName.Replace("[action]", action.Name);
}
actionName = actionName.Split("/")[0];
var httpMethod = action.GetCustomAttributes<HttpMethodAttribute>().FirstOrDefault()
?.HttpMethods.FirstOrDefault() ?? "GET";
var route = $"/{controllerName}/{actionName}".Replace("//", "/");
bool isExist = sysUrlServer.IsExist(route, httpMethod);
if (isExist)
{
continue;
}
var sysUrl = new SysUrl()
{
Url = route,
IsDeleted = false,
Description = "",
RequestMethod = httpMethod,
CreateUserId = "b47637e2-603f-4df0-abe9-88d70fa870ee"
};
sysUrls.Add(sysUrl);
}
}
}
}
//5. 将路径添加到数据库
sysUrlServer.Add(sysUrls);
}
}
}
这段代码用于自动初始化 Web API 的路径配置,并将其存储到数据库中,方便后续管理或用于权限控制等功能。它通过反射获取当前程序集中的控制器和其操作方法(Actions),生成每个 API 的完整路径,并在数据库中进行存储。
代码的入口是 Init
方法,它接受一个 IServiceProvider
参数,用于通过依赖注入获取服务和创建作用域。在作用域内,获取了 ISysUrlServer
服务实例,该服务负责操作路径数据。首先,通过反射获取当前程序集中的所有类型,并筛选出继承自 BaseController
的类型,代表这些类型是 API 控制器。接着,代码对每个控制器检查是否标记了 Route
特性,用来定义控制器的路由模板。如果找到该特性,则会提取其中的模板信息,并替换其中的占位符 [controller]
为控制器的名称(去除 Controller
后缀)。
在处理每个控制器后,代码进一步获取控制器中所有公开的操作方法,这些方法需要满足没有标记为 NonAction
特性的条件。对于每个操作方法,代码检查是否有 Route
特性来定义其路径。如果存在 [action]
占位符,会被替换为具体方法名。结合控制器路径和操作路径,生成每个 API 的完整路径。
在生成路径后,代码检查该路径是否已经存在于数据库中,利用 sysUrlServer.IsExist
方法。如果路径已经存在,则跳过该路径,避免重复存储;如果路径不存在,则创建一个 SysUrl
对象,包含路径、HTTP 请求方法、是否删除标记以及创建者 ID 等信息。所有新的路径信息会被添加到 sysUrls
列表中。
最后,调用 sysUrlServer.Add
方法,将收集到的路径信息批量写入数据库。通过这种方式,代码实现了自动化的路径管理,无需手动配置所有 API 的路由信息,显著提高了开发效率。与此同时,系统可以动态地获取和管理路径信息,为后续功能(如权限验证或日志记录)提供支持。这种设计方式特别适合复杂项目中的路径初始化需求。
完成AddPath
类后,我们还需要在Program
类中使用它,使其可以项目启动时帮我们吧接口路径写入到数据库中,这里需要注意的是AddPath
类是在开发中用的,生产环境中我们捕获这么初始化数据库,因此需要将这个类的调用限制在开发环境下,代码如下:
if (app.Environment.IsDevelopment())
{
//more code....
AddPath.Init(app.Services);
AddRolePath.Init(app.Services);
}
二、总结
在开发过程中,针对每次新增接口需要手动添加到接口表的问题,设计并实现了一个自动化解决方案:利用反射动态生成 API 路径并自动存储到数据库中。具体实现通过新增一个名为 AddPath
的静态类完成。此类通过反射扫描当前程序集的控制器及其操作方法(Action),提取路由信息,动态生成完整的 API 路径,并检查路径是否已存在于数据库中。如果不存在,则将路径及相关元数据保存到数据库中,从而实现接口的自动注册。实现过程中,AddPath
类的核心逻辑包括反射获取控制器及其 Route
特性,替换路由模板中的占位符(如 [controller]
和 [action]
),并结合 HTTP 方法信息生成唯一标识的完整路径。为了确保数据的一致性和避免重复存储,还使用 ISysUrlServer
服务检查路径是否已存在。最终,通过批量操作将新路径存储到数据库中。此外,将该功能集成到项目启动流程中,仅在开发环境下调用此功能以避免在生产环境中执行不必要的初始化操作。这种自动化路径管理机制显著提高了开发效率,避免了手动维护接口表的繁琐过程,同时为后续权限控制和日志管理提供了便利支持。