【问题标题】:.NET 6 Custom model binding - fall back to default binding?.NET 6 自定义模型绑定 - 回退到默认绑定?
【发布时间】:2022-01-01 08:19:42
【问题描述】:

我正在尝试更改我的规范模型的一个特定属性在 .NET 6 中的绑定方式。我有一堆从 SpecificationBase<> 继承的规范类。有一个int[]? Ids { get; set; } 属性是基类的一部分。我创建了一个自定义活页夹来获取 CSV 数字字符串并将它们转换为 int[]?大批。 (注意:我知道数组可以以?Ids=1&Ids3=&Ids=5 的形式传递给控制器​​操作,这将正确绑定到数组)。

在我在网上看到的旧示例中,自定义模型绑定器继承自 DefaultModelBinder。然后我们可以使用base.BindModelbase.BindProperty。对于 .Net 6,我找不到任何类似的东西。有一个 ComplexObjectModelBinder,但它是密封的。

只要规范属性是简单类型,下面的一切都可以正常工作。 TypeDescriptor 转换似乎对那些人很有效。我担心什么时候有复杂的类型。除了我的Ids 属性之外,有没有办法回退到默认绑定?

public class SpecificationModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        ArgumentNullException.ThrowIfNull(bindingContext);

        // If not a specification, skip
        if (bindingContext.ModelType.BaseType?.GetGenericTypeDefinition() != typeof(SpecificationBase<>))
            return Task.CompletedTask;

        // Type of specification
        var type = bindingContext.ModelType;

        // Create default instance of the specification
        var model = Activator.CreateInstance(type);

        // We are passing the specification parameters via the querystring, so loop over the querystring keys to set specification properties
        foreach (var name in bindingContext.HttpContext.Request.Query.Keys)
        {
            // Check to make sure there is a matching property. If not continue.
            var property = type.GetProperty(name);
            if (property is null)
                continue;

            if (property.Name == "Ids")
            {
                // Custom binding to convert int csv string into int[] array
                string? idsString = bindingContext.ValueProvider.GetValue("Ids").FirstValue;
                int[]? ids = null;
                if (!string.IsNullOrEmpty(idsString))
                    ids = idsString.Split(',', StringSplitOptions.RemoveEmptyEntries).Where(x => int.TryParse(x, out _)).Select(x => int.Parse(x)).ToArray();
                property.SetValue(model, ids);
            }
            else
            {
                // Is there a standard way to bind all of the other properties? This will only handle simple types
                var value = bindingContext.ValueProvider.GetValue(property.Name).FirstValue;
                var converter = TypeDescriptor.GetConverter(property.PropertyType);
                var convertedValue = converter.ConvertFrom(value!);
                property.SetValue(model, convertedValue);
            }
        }

        // Set the result to our populated model
        bindingContext.Result = ModelBindingResult.Success(model);

        return Task.CompletedTask;
    }
}

【问题讨论】:

  • 也许值得为该属性创建特殊的活页夹并用它标记所有Ids 属性?您也可以尝试创建IModelBinderProvider,它将检查属性类型和名称并返回您的自定义活页夹(它应该只处理属性)。
  • 大师,规范在一个单独的程序集中,不依赖于Microsoft.AspNetCore,所以我不确定我能否使您的第一个建议奏效。关于第二个建议,我只希望对派生自 SpecificationBase&lt;&gt; 的规范的 Ids 属性进行自定义处理。我正在尝试通过在我的控制器中执行public async Task&lt;IActionResult&gt; List([ModelBinder(typeof(SpecificationModelBinder))] CountrySpecification specification) 之类的操作来明确选择加入活页夹。

标签: c# asp.net-core asp.net-core-webapi model-binding .net-6.0


【解决方案1】:

如果您真的只是想为一个属性创建一个自定义模型绑定器。

您实际上可以在属性级别专门设置 Binder。 https://docs.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-6.0#modelbinder-attribute

从你的例子中我能做到:

class CountrySpecification : SpecificationBase<>
{
    //Country specific properties handled by the default Binders
}
class SpecificationBase<> 
{
    [ModelBinder(typeof(SpecificationModelBinder))]
    int[]? Ids { get; set; }
    //Extra properties handled by the default Binders
}

另一个额外的好处是现在您的自定义 Binder 也可以处理您的一个属性。

【讨论】:

    猜你喜欢
    • 2018-08-22
    • 1970-01-01
    • 2018-10-13
    • 2016-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-16
    • 2023-03-30
    相关资源
    最近更新 更多