自定义模型绑定器 AspNet Core 2.2,用于复杂的嵌套属性



我有一个 Angular 客户端,并使用以下正文创建一个 POST 请求:

{"名称":"示例","货币":"EUR"}

我使用 Odata 协议,我的控制器是:

[HttpPost, ODataRoute("Templates")]
public IActionResult Insert([FromBody] Template value)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
value.Id = Guid.NewGuid();
_context.Templates.Add(value);
_context.SaveChanges();
return Created(value);
}

使用模板:

public class Template
{
public Guid Id { get; set; }
public string Name { get; set; }
public Currency Currency { get; set; }
}

和货币:

[Serializable]
public class Currency : StringEnumeration<Currency>
{
public static Currency EUR = new Currency("EUR", "EUR");
public static Currency USD = new Currency("USD", "USD");
Currency() { }
Currency(string code, string description) : base(code, description) { }
}

货币是一个特定的类,因为它有私有构造函数,因此我无法创建货币的新实例。我想使用现有实例之一(欧元或美元)。

(字符串枚举支持 Parse 和 TryParse 方法并返回正确的实例)

标准配置:

public void ConfigureServices(IServiceCollection services)
{
services.ConfigureCors();
services.AddOData();
services.ConfigureIISIntegration();
services.AddMvc()                
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddDbContext<GpContext>(option => option
.UseSqlServer(Configuration.GetConnectionString(GpConnection)));
}

我的问题是当客户端在 http://localhost:4200/template 上调用 POST 时 与正文: {"名称":"示例","货币":"EUR"}

模型绑定器无法在 Currency.EUR 实例中转换"EUR",因此我想提供一些东西来帮助模型绑定器使用实例 Currency.EUR 创建具有货币属性的模板

这是生成的错误: 尝试读取属性"货币"的值时,发现具有非空值的"基元值"节点;但是,预期会出现具有空值的"StartArray"节点、"StartObject"节点或"PrimitiveValue"节点。

在我的项目中,我有很多带有货币属性的类。

我尝试在模板类上使用 IModelBinder,它可以工作,但我不想为任何货币属性编写模型绑定器。

我尝试使用 JsonConverter,但它对我不起作用(可能是错误)

我的预期结果是具有以下值的模板实例:

Id = defaluf(Guid)
Name = "example"
Currency = Currency.EUR

如果已经为Currency类型实现了工作模型绑定器,则可以实现一个IModelBinderProvider,该在 MVC 需要绑定到Currency类型时自动提供模型绑定器:

public class CurrencyModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(Currency))
return new BinderTypeModelBinder(typeof(CurrencyModelBinder));
return null;
}
}

然后,您需要在初创公司的ConfigureServices中注册它:

services.AddMvc(options =>
{
options.ModelBinderProviders.Insert(0, new CurrencyModelBinderProvider());
});

然后,所有Currency元素都将使用您的CurrencyModelBinder自动绑定,而无需您在任何地方使用[ModelBinder]属性。

文档的"自定义模型绑定程序示例"部分也对此进行了描述。


只是为了完整起见,CurrencyModelBinder的可能实现:

public class CurrencyModelBinder : IModelBinder
{
private static readonly Currency[] _currencies = new Currency[]
{
Currency.EUR,
Currency.USD,
};
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var modelName = bindingContext.ModelName;
var providerResult = bindingContext.ValueProvider.GetValue(modelName);
if (providerResult == ValueProviderResult.None)
{
return Task.CompletedTask;
}
var value = providerResult.FirstValue;
if (string.IsNullOrEmpty(value))
{
return Task.CompletedTask;
}
var currency = _currencies
.FirstOrDefault(c => c.Code.Equals(value, StringComparison.OrdinalIgnoreCase));
if (currency != null)
bindingContext.Result = ModelBindingResult.Success(currency);
else
bindingContext.ModelState.TryAddModelError(modelName, "Unknown currency");
return Task.CompletedTask;
}
}

我尝试这个实现,但我有同样的错误。

我在 CurrencyModelBinder 和 CurrencyModelBinderProvider 中设置断点

模型绑定程序提供程序上的断点

问题出在比较中: 上下文。Metadata.ModelType = "Template" 并且 CurrencyModelBinder 仅用于 Currency。

我用这个工作解决了:

  1. 从正文请求中获取原始值
  2. 使用 JsonConverter 反序列化

    [HttpPost, ODataRoute("Templates")]
    public IActionResult Insert([FromBody] object value)
    {
    if (!ModelState.IsValid)
    return BadRequest(ModelState);
    var template = JsonConvert.DeserializeObject<Template>(value.ToString());
    template.Id = Guid.NewGuid();
    _context.Templates.Add(template);
    _context.SaveChanges();
    return Created(value);
    }
    

货币类现在为

[Serializable]
[JsonConverter(typeof(CurrencyJsonConverter))]
public class Currency : StringEnumeration<Currency>
{
public static Currency CHF = new Currency("CHF", "CHF");
public static Currency EUR = new Currency("EUR", "EUR");
public static Currency USD = new Currency("USD", "USD");
Currency() { }
Currency(string code, string description) : base(code, description) { }
}

和 JsonConverter

public class CurrencyJsonConverter : JsonConverter
{
public override bool CanWrite => true;
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Currency);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null) return null;
var value = reader.Value as string;
return Currency.Parse(value);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is Currency currency)
serializer.Serialize(writer, currency.Code);
}
}

我不明白为什么 Defaul 模型绑定器不使用 Json 反序列化。

我仍在等待你的友好答复。

相关内容

  • 没有找到相关文章

最新更新