我正在创建.NET Core 3 WebApi,并且在查询参数中存在一些与模型绑定有关的“麻烦”。我有一个Range
类,具有Min
,Max
结尾的Value
属性。此类的用途是使用范围或常量值进行过滤。
public class Range
{
public int? Min { get; set; }
public int? Max { get; set; }
public int? Value { get; set; }
public static implicit operator Range(int value) => new Range {Value = value};
}
当我未定义子属性时,我希望它绑定到Value
属性,例如api/contacts?age=40
。
Min
和Max
按计划使用默认绑定方式,例如api/contacts?age.min=18&age.max=30
。我以为添加一个隐式运算符可以,但是不行。
是否有一种方法(如属性)将Value
设置为默认属性?
答案 0 :(得分:1)
当您将'int'值分配给'Range'对象时,Implicit运算符会进行隐式转换。但是,它不能应用于ASP.NET Core的模型绑定方案中。模型绑定仅检查请求中的数据,然后调用模型绑定器为不同类型的类绑定数据。
asp.net核心框架还提供“ TypeConverter”,以确定是否应将字符串转换为对象。但是,它只能处理一个查询字符串,并且会干扰通用的模型绑定过程。 (例如age.max和age.min)。
对于您的问题,我建议的唯一方法是构造一个自定义模型绑定程序,以便它将按照自定义规则绑定数据:(以您的情况为例)
针对复杂模型的自定义模型绑定步骤如下:
创建一个自定义活页夹,例如RangeEntityBinder
,该活页夹应扩展IModelBinder
创建一个自定义的资料夹提供程序,例如RangeEntityBinderProvider
,该提供程序应扩展IModelBinderProvider
将绑定程序提供程序注册到启动文件的ConfigureServices的ModelBinderProviders
中
根据您的描述,我构建了一个您可以参考的演示。
控制器:
public IActionResult RangePage(Range age)
{
if(age == null)
{
age = new Range();
}
return View(age);
}
RangePage.cshtml:
<a asp-controller="Home" asp-action="RangePage" asp-route-age.min="18" asp-route-age.max="30">age.min=18&age.max=30</a>
<br />
<a asp-controller="Home" asp-action="RangePage" asp-route-age="40">age=40</a>
<div class="container">
<label asp-for="Max">Max: </label>@Model.Max
<br />
<label asp-for="Min">Min: </label>@Model.Min
<br />
<label asp-for="Value">Value: </label>@Model.Value
</div>
Range.cs
public class Range
{
public int? Min { get; set; }
public int? Max { get; set; }
public int? Value { get; set; }
}
RangeEntityBinder.cs
public class RangeEntityBinder : IModelBinder
{
private readonly ComplexTypeModelBinder worker;
public RangeEntityBinder(Dictionary<ModelMetadata, IModelBinder> propertyBinders, ILoggerFactory loggerFactory)
{
worker = new ComplexTypeModelBinder(propertyBinders, loggerFactory);
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
// Try get the "age" to populate the model
var modelName = bindingContext.ModelName;
// Try to fetch the value of the argument by name
var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
// If find 'age', return Range object with Value="age"
// If not, use ComplexTypeModelBinder do the common binding
if (valueProviderResult == ValueProviderResult.None)
{
await this.worker.BindModelAsync(bindingContext);
if (!bindingContext.Result.IsModelSet)
{
return;
}
var model= bindingContext.Result.Model as Range;
if (model== null)
{
throw new InvalidOperationException($"Expected {bindingContext.ModelName} to have been bound by ComplexTypeModelBinder");
}
}
else
{
var value = valueProviderResult.FirstValue;
// Check if the argument value is null or empty
if (string.IsNullOrEmpty(value))
{
await Task.CompletedTask;
}
if (!int.TryParse(value, out var ageValue))
{
// Non-integer arguments result in model state errors
bindingContext.ModelState.TryAddModelError(
modelName, "Age value must be an integer.");
await Task.CompletedTask;
return;
}
var model = new Range()
{
Value = ageValue
};
bindingContext.Result = ModelBindingResult.Success(model);
await Task.CompletedTask;
}
}
}
RangeEntityBinderProvider.cs
public class RangeEntityBinderProvider: IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// If type is Range, use RangeEntityBinder to bind model
if (context.Metadata.ModelType == typeof(Range))
{
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
for (var i = 0; i < context.Metadata.Properties.Count; i++)
{
var property = context.Metadata.Properties[i];
propertyBinders.Add(property, context.CreateBinder(property));
}
var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
return new RangeEntityBinder(propertyBinders, loggerFactory);
}
return null;
}
}
ConfigureServices方法
public void ConfigureServices(IServiceCollection services)
{
/* Other services*/
services.AddMvc(options =>
{
options.ModelBinderProviders.Insert(0, new RangeEntityBinderProvider());
});
/* Other services*/
}
演示: