如何让自定义的ModelBinder自动应用于某一类型的所有属性

How to make a custom ModelBinder automatically apply to all properties of a certain type

编辑:事实证明我的 ModelBinder 实际上不是问题所在。问题出在我的 ValueConverter 上。把它留在这里,因为我认为它说明了如何做一个自定义模型活页夹。


本题代码较多。我正在努力做到尽可能全面和准确。

我有一个Measurementclass。我的许多模型都有一个或多个 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);
        }

表格没有提供CreatedUpdated字段,所以我自己填写。但是,其他所有内容都由表单填写为字符串。您可以在上面的自定义模型活页夹 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.