ASP.NET中能否以Fluent方式在目标模型外定义绑定属性?
如何通过Fluent方式在模型外部定义ASP.NET Core绑定源属性
可以实现。我们可以通过自定义模型绑定器、绑定配置接口以及绑定提供器,实现类似FluentValidation的外部绑定源配置,避免在模型属性上直接标记[FromRoute]、[FromBody]等特性。
实现步骤
- 定义绑定配置接口与配置器
创建接口和配置类,用来以Fluent风格声明模型属性的绑定源:
public interface IModelBindingConfiguration<TModel> { void ConfigureBinding(BindingConfigurator<TModel> configurator); } public class BindingConfigurator<TModel> { private Dictionary<Expression<Func<TModel, object>>, BindingSource> _propertyBindings = new(); public void FromRoute(Expression<Func<TModel, object>> propertyExpression) { _propertyBindings[propertyExpression] = BindingSource.Path; } public void FromBody(Expression<Func<TModel, object>> propertyExpression) { _propertyBindings[propertyExpression] = BindingSource.Body; } public void FromQuery(Expression<Func<TModel, object>> propertyExpression) { _propertyBindings[propertyExpression] = BindingSource.Query; } internal Dictionary<Expression<Func<TModel, object>>, BindingSource> GetBindings() => _propertyBindings; }
- 自定义Fluent模型绑定器
编写模型绑定器,根据外部配置从对应源获取属性值,未配置的属性默认使用Body绑定:
public class FluentModelBinder : IModelBinder { private readonly IModelBinderFactory _modelBinderFactory; private readonly IServiceProvider _serviceProvider; public FluentModelBinder(IModelBinderFactory modelBinderFactory, IServiceProvider serviceProvider) { _modelBinderFactory = modelBinderFactory; _serviceProvider = serviceProvider; } public async Task BindModelAsync(ModelBindingContext bindingContext) { var modelType = bindingContext.ModelType; var configType = typeof(IModelBindingConfiguration<>).MakeGenericType(modelType); var config = _serviceProvider.GetService(configType) as dynamic; // 无配置则使用默认绑定逻辑 if (config == null) { var defaultBinder = _modelBinderFactory.CreateBinder(new ModelBinderFactoryContext { Metadata = bindingContext.ModelMetadata, BindingInfo = bindingContext.BindingInfo }); await defaultBinder.BindModelAsync(bindingContext); return; } var configurator = new BindingConfigurator<dynamic>(); config.ConfigureBinding(configurator); var propertyBindings = configurator.GetBindings(); var model = Activator.CreateInstance(modelType); // 处理配置了绑定源的属性 foreach (var (propertyExpr, bindingSource) in propertyBindings) { var propertyName = ExtractPropertyName(propertyExpr); var property = modelType.GetProperty(propertyName); if (property == null) continue; var valueProvider = bindingContext.ValueProviders.FirstOrDefault(vp => vp.CanProvideValue(propertyName) && vp.BindingSource == bindingSource); if (valueProvider != null) { var valueResult = valueProvider.GetValue(propertyName); if (valueResult != ValueProviderResult.None) { var parsedValue = Convert.ChangeType(valueResult.FirstValue, property.PropertyType); property.SetValue(model, parsedValue); } } } // 处理未配置的属性,默认从Body绑定 var bodyBinder = _modelBinderFactory.CreateBinder(new ModelBinderFactoryContext { Metadata = bindingContext.ModelMetadata, BindingInfo = new BindingInfo { BindingSource = BindingSource.Body } }); var tempContext = DefaultModelBindingContext.CreateBindingContext( bindingContext.ActionContext, bindingContext.ValueProviders, bindingContext.ModelMetadata, bindingContext.BindingInfo, model); await bodyBinder.BindModelAsync(tempContext); bindingContext.Result = tempContext.Result.IsModelSet ? ModelBindingResult.Success(tempContext.Result.Model) : ModelBindingResult.Failed(); } private string ExtractPropertyName(Expression<Func<dynamic, object>> propertyExpr) { if (propertyExpr.Body is MemberExpression memberExpr) return memberExpr.Member.Name; if (propertyExpr.Body is UnaryExpression unaryExpr && unaryExpr.Operand is MemberExpression innerMember) return innerMember.Member.Name; throw new ArgumentException("无效的属性表达式"); } }
- 注册绑定提供器
创建绑定提供器,让ASP.NET Core识别并使用我们的自定义绑定器:
public class FluentModelBinderProvider : IModelBinderProvider { public IModelBinder GetBinder(ModelBinderProviderContext context) { var modelType = context.Metadata.ModelType; var configType = typeof(IModelBindingConfiguration<>).MakeGenericType(modelType); // 只有注册了绑定配置的模型才使用自定义绑定器 if (context.Services.Any(s => s.ServiceType == configType)) { return new FluentModelBinder( context.Services.GetRequiredService<IModelBinderFactory>(), context.Services); } return null; } }
在Program.cs中注册服务:
builder.Services.AddControllers(options => { // 将自定义提供器插入到最前面,优先使用 options.ModelBinderProviders.Insert(0, new FluentModelBinderProvider()); });
- 为命令模型编写绑定配置
为UpdateTodoListCommand创建外部绑定配置:
public class UpdateTodoListCommandBindingConfig : IModelBindingConfiguration<UpdateTodoListCommand> { public void ConfigureBinding(BindingConfigurator<UpdateTodoListCommand> configurator) { // 指定Id从路由绑定,其余属性默认从Body绑定 configurator.FromRoute(c => c.Id); } }
注册该配置到服务容器:
builder.Services.AddSingleton<IModelBindingConfiguration<UpdateTodoListCommand>, UpdateTodoListCommandBindingConfig>();
- 控制器中使用模型
命令模型无需任何绑定特性:
public class UpdateTodoListCommand { public int Id { get; set; } public string Title { get; set; } public bool IsCompleted { get; set; } }
控制器动作直接使用模型:
[HttpPut("{id}")] public async Task<ActionResult> Update(UpdateTodoListCommand command) { // command.Id 自动从路由获取,Title和IsCompleted从请求体获取 await Mediator.Send(command); return Ok(); }
内容的提问来源于stack exchange,提问作者gius
相关产品推荐
相关产品推荐

