Asp.Net中如何基于BindingSource手动实现请求模型绑定?
核心思路
直接复用Asp.Net内置的模型绑定机制,避免重复实现类型转换、格式校验等逻辑,同时结合你已定义的ITargetEntityContract和TargetEntityLocation属性,完成请求数据解析与权限校验的联动。
步骤实现
1. 完善TargetEntityLocation属性定义
确保属性能存储目标模型类型(需实现ITargetEntityContract)和对应的绑定源:
[AttributeUsage(AttributeTargets.Method)] public class TargetEntityLocationAttribute : Attribute { public Type ModelType { get; } public BindingSource BindingSource { get; } public TargetEntityLocationAttribute(Type modelType, BindingSource bindingSource) { ModelType = modelType; BindingSource = bindingSource; } } // 示例ITargetEntityContract接口 public interface ITargetEntityContract { int GetTargetEntityId(); }
2. 实现自定义AuthorizationHandler
通过注入Asp.Net模型绑定核心组件,完成请求数据的解析与权限校验:
public class TargetEntityAuthorizationHandler : AuthorizationHandler<TargetEntityRequirement> { private readonly IModelBinderFactory _modelBinderFactory; private readonly ICompositeModelBinderProvider _modelBinderProvider; public TargetEntityAuthorizationHandler(IModelBinderFactory modelBinderFactory, ICompositeModelBinderProvider modelBinderProvider) { _modelBinderFactory = modelBinderFactory; _modelBinderProvider = modelBinderProvider; } protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, TargetEntityRequirement requirement) { // 获取当前端点元数据 if (context.Resource is not Endpoint endpoint) { context.Fail(); return; } var locationAttr = endpoint.Metadata.OfType<TargetEntityLocationAttribute>().FirstOrDefault(); if (locationAttr == null || !typeof(ITargetEntityContract).IsAssignableFrom(locationAttr.ModelType)) { context.Fail(); return; } // 构建模型绑定上下文 var modelBindingContext = CreateModelBindingContext(context.HttpContext, locationAttr); if (modelBindingContext == null) { context.Fail(); return; } // 执行模型绑定 var binder = _modelBinderFactory.CreateBinder(modelBindingContext); await binder.BindModelAsync(modelBindingContext); if (modelBindingContext.Result.IsModelSet && modelBindingContext.Result.Model is ITargetEntityContract entityContract) { var targetEntityId = entityContract.GetTargetEntityId(); // 结合权限要求完成校验逻辑 if (ValidateUserAccess(context.User, requirement, targetEntityId)) { context.Succeed(requirement); return; } } context.Fail(); } private ModelBindingContext? CreateModelBindingContext(HttpContext httpContext, TargetEntityLocationAttribute locationAttr) { var metadataProvider = httpContext.RequestServices.GetRequiredService<IModelMetadataProvider>(); var modelMetadata = metadataProvider.GetMetadataForType(locationAttr.ModelType); return new DefaultModelBindingContext { ActionContext = new ActionContext(httpContext, httpContext.GetRouteData(), new ControllerActionDescriptor()), ModelMetadata = modelMetadata, ModelName = string.Empty, ValueProvider = GetValueProvider(httpContext, locationAttr.BindingSource, locationAttr.ModelType) }; } private IValueProvider? GetValueProvider(HttpContext httpContext, BindingSource bindingSource, Type modelType) { if (bindingSource == BindingSource.Path) // 对应FromRoute { return new RouteValueProvider(BindingSource.Path, httpContext.GetRouteData().Values); } else if (bindingSource == BindingSource.Query) // 对应FromQuery { return new QueryStringValueProvider(BindingSource.Query, httpContext.Request.Query, CultureInfo.CurrentCulture); } else if (bindingSource == BindingSource.Body) // 对应FromBody { // 启用请求体重读,避免流读取一次后无法访问 httpContext.Request.EnableBuffering(); using var reader = new StreamReader(httpContext.Request.Body, Encoding.UTF8, leaveOpen: true); var bodyContent = await reader.ReadToEndAsync(); httpContext.Request.Body.Position = 0; // 解析请求体为目标模型,再构造ValueProvider var model = JsonSerializer.Deserialize(bodyContent, modelType); return new ObjectValueProvider(new EmptyModelMetadataProvider().GetMetadataForType(modelType), model); } return null; } private bool ValidateUserAccess(ClaimsPrincipal user, TargetEntityRequirement requirement, int targetEntityId) { // 替换为你的实际权限校验逻辑,比如检查用户是否拥有该实体的操作权限 return user.HasClaim(c => c.Type == "EntityPermission" && c.Value == $"{targetEntityId}:{requirement.Permission}"); } } // 示例权限要求 public class TargetEntityRequirement : IAuthorizationRequirement { public string Permission { get; set; } = "Read"; }
3. 注册服务与应用属性
在Program.cs中注册授权服务:
builder.Services.AddAuthorization(options => { options.AddPolicy("TargetEntityAccess", policy => policy.Requirements.Add(new TargetEntityRequirement { Permission = "Read" })); }); builder.Services.AddSingleton<IAuthorizationHandler, TargetEntityAuthorizationHandler>();
在控制器端点上应用属性:
[HttpGet("/api/entities/{id}")] [TargetEntityLocation(typeof(EntityQueryRequest), BindingSource.Path)] [Authorize(Policy = "TargetEntityAccess")] public IActionResult GetEntity(int id) { // 业务逻辑 return Ok(); } // 示例请求模型 public class EntityQueryRequest : ITargetEntityContract { public int Id { get; set; } public int GetTargetEntityId() => Id; }
关键注意点
- 请求体重读:处理
FromBody时必须调用EnableBuffering(),并重置流位置,否则后续中间件或控制器无法读取请求体。 - 类型安全校验:获取
TargetEntityLocationAttribute后,必须校验ModelType是否实现ITargetEntityContract,避免类型转换错误。 - 模型绑定上下文:确保
ActionContext、ModelMetadata和ValueProvider配置正确,否则模型绑定会失败。
内容的提问来源于stack exchange,提问作者Marcel Lorenz
相关产品推荐
相关产品推荐

