如何消除ASP.NET Core REST API中的重复样板代码?
解决ASP.NET Core API中重复权限校验样板代码的方案
针对你遇到的重复校验逻辑问题,以下几种轻量方案可以有效消除样板代码,同时避免过度设计:
方案一:自定义Action过滤器(推荐)
将Workspace加载、权限校验逻辑封装成自定义Action过滤器,只需在需要校验的接口上添加特性即可完成前置校验,校验通过后还能将合法的Workspace实例传递给控制器使用。
实现自定义过滤器
public class WorkspaceAuthorizeAttribute : AsyncActionFilterAttribute { public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { // 从路由参数解析workspaceId if (!context.RouteData.Values.TryGetValue("workspaceId", out var workspaceIdObj) || !Guid.TryParse(workspaceIdObj.ToString(), out var workspaceId)) { context.Result = new BadRequestResult(); return; } // 获取DbContext和当前用户邮箱 var db = context.HttpContext.RequestServices.GetRequiredService<YourDbContext>(); var userEmail = context.HttpContext.User.Identity?.Name; if (string.IsNullOrEmpty(userEmail)) { context.Result = new ForbidResult(); return; } // 加载Workspace并校验权限 var workspace = await db.Workspaces .Include(w => w.Members) .ThenInclude(m => m.User) .FirstOrDefaultAsync(w => w.Id == workspaceId); if (workspace == null) { context.Result = new NotFoundResult(); return; } if (!workspace.Members.Any(m => m.User.Email == userEmail)) { context.Result = new ForbidResult(); return; } // 将校验通过的Workspace存入HttpContext,供控制器后续使用 context.HttpContext.Items["AuthorizedWorkspace"] = workspace; // 继续执行接口业务逻辑 await next(); } }
控制器中使用
[HttpPost("{workspaceId}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [WorkspaceAuthorize] // 只需添加该特性完成前置校验 public async Task<IActionResult> Create(Guid workspaceId, [FromBody] AppointmentCreateModel model) { // 直接获取已校验的Workspace实例 var workspace = (Workspace)HttpContext.Items["AuthorizedWorkspace"]!; var appointment = new Appointment { WorkspaceId = workspaceId, ContactName = model.ContactName, OriginalName = model.ContactName, AppointmentTime = model.AppointmentTime, Source = AppointmentSource.Dashboard, CreatedAt = DateTime.UtcNow, }; db.Appointments.Add(appointment); await db.SaveChangesAsync(); return Ok(new AppointmentDto(appointment)); }
方案二:封装扩展方法
如果不想使用过滤器,可以将校验逻辑封装成DbContext的扩展方法,在控制器中直接调用即可。
扩展方法实现
public static class WorkspaceAuthorizationExtensions { public static async Task<IActionResult?> ValidateWorkspaceAccessAsync(this YourDbContext db, Guid workspaceId, ClaimsPrincipal user, ControllerBase controller) { var userEmail = user.Identity?.Name; if (string.IsNullOrEmpty(userEmail)) { return controller.Forbid(); } var workspace = await db.Workspaces .Include(w => w.Members) .ThenInclude(m => m.User) .FirstOrDefaultAsync(w => w.Id == workspaceId); if (workspace == null) { return controller.NotFound(); } if (!workspace.Members.Any(m => m.User.Email == userEmail)) { return controller.Forbid(); } // 校验通过返回null,控制器继续执行业务逻辑 return null; } }
控制器中调用
[HttpPost("{workspaceId}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task<IActionResult> Create(Guid workspaceId, [FromBody] AppointmentCreateModel model) { var validationResult = await db.ValidateWorkspaceAccessAsync(workspaceId, User, this); if (validationResult != null) { return validationResult; } // 后续业务逻辑... }
方案三:基类控制器
如果多个控制器都需要Workspace校验逻辑,可以创建一个基类控制器,将校验方法封装在基类中,业务控制器继承该基类即可。
基类控制器实现
public class WorkspaceBaseController : ControllerBase { protected readonly YourDbContext db; public WorkspaceBaseController(YourDbContext db) { this.db = db; } protected async Task<IActionResult?> ValidateWorkspaceAccessAsync(Guid workspaceId) { var userEmail = User.Identity?.Name; if (string.IsNullOrEmpty(userEmail)) { return Forbid(); } var workspace = await db.Workspaces .Include(w => w.Members) .ThenInclude(m => m.User) .FirstOrDefaultAsync(w => w.Id == workspaceId); if (workspace == null) { return NotFound(); } if (!workspace.Members.Any(m => m.User.Email == userEmail)) { return Forbid(); } return null; } }
业务控制器继承基类
public class AppointmentsController : WorkspaceBaseController { public AppointmentsController(YourDbContext db) : base(db) { } [HttpPost("{workspaceId}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task<IActionResult> Create(Guid workspaceId, [FromBody] AppointmentCreateModel model) { var validationResult = await ValidateWorkspaceAccessAsync(workspaceId); if (validationResult != null) { return validationResult; } // 后续业务逻辑... } }
扩展说明
如果后续其他实体也需要类似的权限校验,可以将上述方案扩展为通用型(比如通过泛型、接口定义校验规则),当前规模下先解决Workspace的重复问题即可。
内容的提问来源于stack exchange,提问作者dinikai
相关产品推荐
相关产品推荐

