如何自动审计Entity Framework生成的ASP.NET Web API所有CRUD操作
实现ASP.NET Web API控制器CRUD操作的自动审计方案
要实现新增控制器自动具备审计能力的需求,最优雅的方式是结合ASP.NET Web API动作过滤器(Action Filter)和泛型基类控制器,这样既能统一拦截CRUD操作,又能让新增控制器只需继承基类就能自动拥有审计功能。以下是完整的落地方案:
整体思路
- 用泛型基类控制器封装通用CRUD逻辑,所有需要审计的业务控制器都继承这个基类。
- 编写审计动作过滤器,拦截基类控制器的所有请求,自动收集操作信息(用户ID、操作类型、表名、操作描述等)并写入
Audit表。 - 通过依赖注入确保过滤器能访问你的DbContext,完成审计记录的持久化。
步骤1:实现审计动作过滤器
这个过滤器会在控制器动作执行完成后,自动收集审计信息并保存到数据库。
using System; using System.Linq; using System.Web.Http.Controllers; using System.Web.Http.Filters; using System.Data.Entity; using System.ComponentModel.DataAnnotations.Schema; public class AuditActionFilter : ActionFilterAttribute { private readonly YourDbContext _dbContext; // 通过依赖注入获取DbContext public AuditActionFilter(YourDbContext dbContext) { _dbContext = dbContext; } public override void OnActionExecuted(HttpActionExecutedContext actionContext) { // 跳过未处理的异常请求(可根据需求改为记录失败操作) if (actionContext.Exception != null && !actionContext.ExceptionHandled) { return; } // 1. 获取当前操作的用户ID(根据你的身份验证方式调整,这里假设从Claims中获取) short userId = 0; var userIdClaim = actionContext.ActionContext.RequestContext.Principal? .Claims.FirstOrDefault(c => c.Type == "UserId"); if (userIdClaim != null) { short.TryParse(userIdClaim.Value, out userId); } // 2. 获取当前控制器对应的实体类型和表名 var controller = actionContext.ActionContext.ControllerContext.Controller; var baseControllerType = controller.GetType().BaseType; if (!baseControllerType.IsGenericType || baseControllerType.GetGenericTypeDefinition() != typeof(BaseAuditableApiController<>)) { return; // 非审计控制器,跳过 } var entityType = baseControllerType.GetGenericArguments()[0]; var tableAttr = entityType.GetCustomAttribute<TableAttribute>(); var tableName = tableAttr?.Name ?? entityType.Name; // 3. 根据HTTP方法和动作生成审计信息 var httpMethod = actionContext.ActionContext.Request.Method.Method; var actionName = actionContext.ActionContext.ActionDescriptor.ActionName; string auditAction, shortDesc, fullDesc; switch (httpMethod) { case "GET": auditAction = "GET"; if (actionName.Equals("Get", StringComparison.OrdinalIgnoreCase) && actionContext.ActionContext.ActionDescriptor.GetParameters().Any(p => p.ParameterType == typeof(int))) { var recordId = actionContext.ActionContext.ActionArguments["id"]; shortDesc = $"读取{tableName}单条记录"; fullDesc = $"用户[{userId}]读取了{tableName}表中ID为{recordId}的记录"; } else { shortDesc = $"读取{tableName}列表"; fullDesc = $"用户[{userId}]读取了{tableName}表的全部/分页记录"; } break; case "POST": auditAction = "POST"; shortDesc = $"创建{tableName}记录"; fullDesc = $"用户[{userId}]在{tableName}表中新增了一条记录"; // 可选:从ActionArguments中获取新增实体,记录更多字段信息 break; case "PUT": auditAction = "PUT"; var updateId = actionContext.ActionContext.ActionArguments["id"]; shortDesc = $"更新{tableName}记录"; fullDesc = $"用户[{userId}]更新了{tableName}表中ID为{updateId}的记录"; break; case "DELETE": auditAction = "DELETE"; var deleteId = actionContext.ActionContext.ActionArguments["id"]; shortDesc = $"删除{tableName}记录"; fullDesc = $"用户[{userId}]删除了{tableName}表中ID为{deleteId}的记录"; break; default: return; // 忽略非CRUD的HTTP方法 } // 4. 保存审计记录到数据库 var auditRecord = new Audit { AuditDate = DateTime.Now, AuditTableName = tableName, AuditDescription = fullDesc, AuditShortDescription = shortDesc, AuditAction = auditAction, UserId = userId }; _dbContext.Audit.Add(auditRecord); _dbContext.SaveChanges(); base.OnActionExecuted(actionContext); } }
步骤2:创建泛型基类控制器
所有需要审计的业务控制器只需继承这个基类,就能自动获得CRUD逻辑和审计功能:
using System.Web.Http; using System.Data.Entity; using System.Linq; [AuditActionFilter] // 给基类添加审计过滤器,所有子类自动继承 public class BaseAuditableApiController<T> : ApiController where T : class { protected readonly YourDbContext _dbContext; public BaseAuditableApiController(YourDbContext dbContext) { _dbContext = dbContext; } // 通用Get单条记录 public virtual IHttpActionResult Get(int id) { var entity = _dbContext.Set<T>().Find(id); return entity == null ? NotFound() : Ok(entity); } // 通用Get列表 public virtual IHttpActionResult Get() { var entities = _dbContext.Set<T>().ToList(); return Ok(entities); } // 通用Post新增 public virtual IHttpActionResult Post(T entity) { if (!ModelState.IsValid) return BadRequest(ModelState); _dbContext.Set<T>().Add(entity); _dbContext.SaveChanges(); // 自动获取实体主键(兼容不同命名的主键字段) var keyProperty = _dbContext.Entry(entity).EntityType.FindPrimaryKey().Properties.First(); var entityId = keyProperty.GetValue(entity); return CreatedAtRoute("DefaultApi", new { id = entityId }, entity); } // 通用Put更新 public virtual IHttpActionResult Put(int id, T entity) { if (!ModelState.IsValid) return BadRequest(ModelState); var existingEntity = _dbContext.Set<T>().Find(id); if (existingEntity == null) return NotFound(); _dbContext.Entry(existingEntity).CurrentValues.SetValues(entity); _dbContext.SaveChanges(); return Ok(existingEntity); } // 通用Delete删除 public virtual IHttpActionResult Delete(int id) { var entity = _dbContext.Set<T>().Find(id); if (entity == null) return NotFound(); _dbContext.Set<T>().Remove(entity); _dbContext.SaveChanges(); return Ok(); } }
步骤3:注册依赖注入
Web API需要配置依赖注入容器,确保过滤器能正确获取YourDbContext实例。这里以Unity容器为例:
using System.Web.Http; using Unity; using Unity.AspNet.WebApi; public static class WebApiConfig { public static void Register(HttpConfiguration config) { // 1. 配置Unity容器 var container = new UnityContainer(); container.RegisterType<YourDbContext>(new HierarchicalLifetimeManager()); container.RegisterType<AuditActionFilter>(); // 2. 设置Web API的依赖解析器 config.DependencyResolver = new UnityDependencyResolver(container); // 3. 路由配置(保持你的原有配置) config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } }
步骤4:创建业务控制器
新增控制器只需继承基类即可,无需额外编写审计逻辑:
public class ExampleController : BaseAuditableApiController<Example> { public ExampleController(YourDbContext dbContext) : base(dbContext) { } // 可选:添加自定义业务方法,过滤器依然会自动审计 public IHttpActionResult GetActiveExamples() { var activeExamples = _dbContext.Set<Example>().Where(e => e.IsActive).ToList(); return Ok(activeExamples); } }
可选增强:记录字段级变更
如果需要记录Update操作的具体字段变更,可以在基类的Put方法中对比实体前后值,并将信息传递给过滤器:
public virtual IHttpActionResult Put(int id, T entity) { if (!ModelState.IsValid) return BadRequest(ModelState); var existingEntity = _dbContext.Set<T>().Find(id); if (existingEntity == null) return NotFound(); // 保存原始值 var originalValues = _dbContext.Entry(existingEntity).CurrentValues.Clone(); // 更新实体 _dbContext.Entry(existingEntity).CurrentValues.SetValues(entity); _dbContext.SaveChanges(); // 将变更信息存入请求上下文,供过滤器读取 actionContext.ActionContext.Request.Properties["OriginalValues"] = originalValues; actionContext.ActionContext.Request.Properties["UpdatedValues"] = _dbContext.Entry(existingEntity).CurrentValues; return Ok(existingEntity); }
然后在过滤器的PUT分支中,读取这些值生成更详细的审计描述:
case "PUT": var updateId = actionContext.ActionContext.ActionArguments["id"]; shortDesc = $"更新{tableName}记录"; // 读取变更信息 if (actionContext.ActionContext.Request.Properties.TryGetValue("OriginalValues", out object origValues) && actionContext.ActionContext.Request.Properties.TryGetValue("UpdatedValues", out object newValues)) { var origDict = ((DbPropertyValues)origValues).ToObject(); var newDict = ((DbPropertyValues)newValues).ToObject(); fullDesc = $"用户[{userId}]更新了{tableName}表中ID为{updateId}的记录:{Newtonsoft.Json.JsonConvert.SerializeObject(origDict)} -> {Newtonsoft.Json.JsonConvert.SerializeObject(newDict)}"; } else { fullDesc = $"用户[{userId}]更新了{tableName}表中ID为{updateId}的记录"; } break;
内容的提问来源于stack exchange,提问作者Pablito0951
相关产品推荐
相关产品推荐

