如何让自定义的ModelBinder自动应用于某一类型的所有属性
How to make a custom ModelBinder automatically apply to all properties of a certain type
编辑:事实证明我的 ModelBinder
实际上不是问题所在。问题出在我的 ValueConverter
上。把它留在这里,因为我认为它说明了如何做一个自定义模型活页夹。
本题代码较多。我正在努力做到尽可能全面和准确。
我有一个Measurement
class。我的许多模型都有一个或多个 Measurement
类型的属性。 class 使用 ValueConverter
作为字符串映射到数据库。我想做的是在提交表单时将表单数据(即字符串)转换为这种类型。所以,我创建了一个自定义 ModelBinder
.
public class MeasurementModelBinder : IModelBinder
{
public MeasurementModelBinder() { }
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if(bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); }
ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if(valueProviderResult == ValueProviderResult.None) { throw new Exception(); }
string valueString = valueProviderResult.FirstValue;
if (string.IsNullOrEmpty(valueString)) { throw new Exception(); }
Measurement result = new Measurement(valueString);
bindingContext.Result = ModelBindingResult.Success(result);
return Task.CompletedTask;
}
}
我希望此 MeasurementModelBinder
应用于每个模型中该类型的所有属性。所以我正在努力做到这一点。这是具有 Measurement
类型属性的模型示例。
public class Material
{
[Key]
public int Key { get; set; }
public string Name { get; set; }
public Measurement Density { get; set; }
public Measurement Conductivity { get; set; }
[Display(Name="Specific Heat")]
public Measurement SpecificHeat { get; set; }
public string Description { get; set; }
public DateTime Created { get; set; }
public DateTime Updated { get; set; }
public Material() { }
public Material(string name, Measurement density, Measurement conductivity, Measurement specificHeat, string description = "")
{
Name = name;
Density = density;
Description = description;
Conductivity = conductivity;
SpecificHeat = specificHeat;
}
public override string ToString() { return Name; }
}
至于控制器,我现在只是在使用一个测试控制器,它是 Visual Studio 自动生成的带有视图的 MVC Entity Framework 控制器之一的修改版本。这是控制器中的一项操作。
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Material material)
{
material.Created = DateTime.Now;
material.Updated = DateTime.Now;
if (ModelState.IsValid)
{
_context.Add(material);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(material);
}
表格没有提供Created
和Updated
字段,所以我自己填写。但是,其他所有内容都由表单填写为字符串。您可以在上面的自定义模型活页夹 class 中看到 Measurement
对象是如何构造的。
我创建了一个 ModelBinderProvider
class,如下所示。
public class MeasurementModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if(context == null) { throw new ArgumentException(nameof(context)); }
if(!context.Metadata.IsComplexType && context.Metadata.ModelType == typeof(Measurement))
{
return new MeasurementModelBinder();
}
return null;
}
}
并且我已经在Startup.ConfigureServices()
方法中注册了provider,像这样。
services.AddMvc(options =>
{
options.ModelBinderProviders.Insert(0, new MeasurementModelBinderProvider());
});
这是正在提交的表单。
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Description" class="control-label"></label>
<input asp-for="Description" class="form-control" />
<span asp-validation-for="Description" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Density" class="control-label"></label>
<input asp-for="Density" class="form-control" />
<span asp-validation-for="Density" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Conductivity" class="control-label"></label>
<input asp-for="Conductivity" class="form-control" />
<span asp-validation-for="Conductivity" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="SpecificHeat" class="control-label"></label>
<input asp-for="SpecificHeat" class="form-control" />
<span asp-validation-for="SpecificHeat" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
我希望通过提交此表单向 material table 添加一条新记录,并填写所有字段。它没有这样做。相反,它会添加一条新记录,其中所有 Measurement
属性均为空。
我尝试将 [ModelBinder(BinderType = typeof(MeasurementModelBinder))]
属性添加到 Measurement
class 中,但没有帮助。事实上,它导致了 "Unable to resolve service" 错误,当我通过将 services.AddScoped<IModelBinder, MeasurementModelBinder>();
添加到 ConfigureServices()
来消除该错误时,我的操作仍然得到相同的结果。
我也尝试过将 [BindProperty(BinderType typeof(MeasurementModelBinder))]
属性添加到模型本身的属性中,但没有帮助。此外,这是我宁愿不做的事情。有什么方法可以让它对所有 Measurement
类型的属性起作用吗?如果没有,是否有任何方法可以让它发挥作用?
谢谢。
我尝试了您的代码,当我将 [ModelBinder(BinderType = typeof(MeasurementModelBinder))]
属性添加到 Measurable
class 时,它对我有用。我认为您应该将 [ModelBinder(BinderType = typeof(MeasurementModelBinder))]
属性添加到 Measurement
class.
编辑:事实证明我的 ModelBinder
实际上不是问题所在。问题出在我的 ValueConverter
上。把它留在这里,因为我认为它说明了如何做一个自定义模型活页夹。
本题代码较多。我正在努力做到尽可能全面和准确。
我有一个Measurement
class。我的许多模型都有一个或多个 Measurement
类型的属性。 class 使用 ValueConverter
作为字符串映射到数据库。我想做的是在提交表单时将表单数据(即字符串)转换为这种类型。所以,我创建了一个自定义 ModelBinder
.
public class MeasurementModelBinder : IModelBinder
{
public MeasurementModelBinder() { }
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if(bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); }
ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if(valueProviderResult == ValueProviderResult.None) { throw new Exception(); }
string valueString = valueProviderResult.FirstValue;
if (string.IsNullOrEmpty(valueString)) { throw new Exception(); }
Measurement result = new Measurement(valueString);
bindingContext.Result = ModelBindingResult.Success(result);
return Task.CompletedTask;
}
}
我希望此 MeasurementModelBinder
应用于每个模型中该类型的所有属性。所以我正在努力做到这一点。这是具有 Measurement
类型属性的模型示例。
public class Material
{
[Key]
public int Key { get; set; }
public string Name { get; set; }
public Measurement Density { get; set; }
public Measurement Conductivity { get; set; }
[Display(Name="Specific Heat")]
public Measurement SpecificHeat { get; set; }
public string Description { get; set; }
public DateTime Created { get; set; }
public DateTime Updated { get; set; }
public Material() { }
public Material(string name, Measurement density, Measurement conductivity, Measurement specificHeat, string description = "")
{
Name = name;
Density = density;
Description = description;
Conductivity = conductivity;
SpecificHeat = specificHeat;
}
public override string ToString() { return Name; }
}
至于控制器,我现在只是在使用一个测试控制器,它是 Visual Studio 自动生成的带有视图的 MVC Entity Framework 控制器之一的修改版本。这是控制器中的一项操作。
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Material material)
{
material.Created = DateTime.Now;
material.Updated = DateTime.Now;
if (ModelState.IsValid)
{
_context.Add(material);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(material);
}
表格没有提供Created
和Updated
字段,所以我自己填写。但是,其他所有内容都由表单填写为字符串。您可以在上面的自定义模型活页夹 class 中看到 Measurement
对象是如何构造的。
我创建了一个 ModelBinderProvider
class,如下所示。
public class MeasurementModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if(context == null) { throw new ArgumentException(nameof(context)); }
if(!context.Metadata.IsComplexType && context.Metadata.ModelType == typeof(Measurement))
{
return new MeasurementModelBinder();
}
return null;
}
}
并且我已经在Startup.ConfigureServices()
方法中注册了provider,像这样。
services.AddMvc(options =>
{
options.ModelBinderProviders.Insert(0, new MeasurementModelBinderProvider());
});
这是正在提交的表单。
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Description" class="control-label"></label>
<input asp-for="Description" class="form-control" />
<span asp-validation-for="Description" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Density" class="control-label"></label>
<input asp-for="Density" class="form-control" />
<span asp-validation-for="Density" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Conductivity" class="control-label"></label>
<input asp-for="Conductivity" class="form-control" />
<span asp-validation-for="Conductivity" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="SpecificHeat" class="control-label"></label>
<input asp-for="SpecificHeat" class="form-control" />
<span asp-validation-for="SpecificHeat" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
我希望通过提交此表单向 material table 添加一条新记录,并填写所有字段。它没有这样做。相反,它会添加一条新记录,其中所有 Measurement
属性均为空。
我尝试将 [ModelBinder(BinderType = typeof(MeasurementModelBinder))]
属性添加到 Measurement
class 中,但没有帮助。事实上,它导致了 "Unable to resolve service" 错误,当我通过将 services.AddScoped<IModelBinder, MeasurementModelBinder>();
添加到 ConfigureServices()
来消除该错误时,我的操作仍然得到相同的结果。
我也尝试过将 [BindProperty(BinderType typeof(MeasurementModelBinder))]
属性添加到模型本身的属性中,但没有帮助。此外,这是我宁愿不做的事情。有什么方法可以让它对所有 Measurement
类型的属性起作用吗?如果没有,是否有任何方法可以让它发挥作用?
谢谢。
我尝试了您的代码,当我将 [ModelBinder(BinderType = typeof(MeasurementModelBinder))]
属性添加到 Measurable
class 时,它对我有用。我认为您应该将 [ModelBinder(BinderType = typeof(MeasurementModelBinder))]
属性添加到 Measurement
class.