Я просто потратил много времени, пытаясь заставить работать пользовательский ComplexTypeModelBinder
.Что бы я ни делал, это никогда не работало.Оказывается, это работает только тогда, когда данные помещаются в виде данных формы;когда вы публикуете объект JSON (в моем случае из формы Swagger "try"), ComplexTypeModelBinder
никогда не вызывает метод SetProperty
.
У меня много моделей, некоторые более сложные, чем другие,и я аннотировал некоторые свойства с пользовательским атрибутом.Всякий раз, когда это свойство связано, я хочу, чтобы оно было « normalized » (применить к нему некоторое «форматирование»), чтобы к моменту проверки модели валидатор мог видеть «нормализованные» данные вместо пользователя-неданные данные.
Я действительно, действительно, хочу сохранить текущее поведение привязки модели, потому что оно в настоящее время работает нормально, но с одним исключением, что аннотированные свойства обрабатываются некоторым кодом, реализованным мной.Все остальные свойства и поведение должны быть сохранены как есть.Вот почему я надеялся наследовать от ComplexTypeModelBinder
, но, как оказалось, это не сработает, если данные помещены в формате JSON.
Моя (пример) модель выглядит следующим образом:
public class MyComplexModel
{
public int Id { get; set; }
public string Name { get; set; }
[FormatNumber(NumberFormat.E164)]
public string PhoneNumber { get; set; }
}
Мой метод контроллера выглядит следующим образом:
[HttpPost]
public MyComplexModel Post(MyComplexModel model)
{
return model;
}
Мой (не работает) пользовательский ComplexTypeModelBinder
выглядит так:
public class MyModelBinder : ComplexTypeModelBinder
{
private readonly INumberFormatter _numberformatter;
private static readonly ConcurrentDictionary<Type, Dictionary<string, FormatNumberAttribute>> _formatproperties = new ConcurrentDictionary<Type, Dictionary<string, FormatNumberAttribute>>();
public MyModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders, INumberFormatter numberFormatter, ILoggerFactory loggerFactory)
: base(propertyBinders, loggerFactory)
{
_numberformatter = numberFormatter;
}
protected override object CreateModel(ModelBindingContext bindingContext)
{
// Index and cache all properties having the FormatNumber Attribute
_formatproperties.GetOrAdd(bindingContext.ModelType, (t) =>
{
return t.GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(FormatNumberAttribute))).ToDictionary(pi => pi.Name, pi => pi.GetCustomAttribute<FormatNumberAttribute>(), StringComparer.OrdinalIgnoreCase);
});
return base.CreateModel(bindingContext);
}
protected override Task BindProperty(ModelBindingContext bindingContext)
{
return base.BindProperty(bindingContext);
}
protected override void SetProperty(ModelBindingContext bindingContext, string modelName, ModelMetadata propertyMetadata, ModelBindingResult result)
{
if (_formatproperties.TryGetValue(bindingContext.ModelType, out var props) && props.TryGetValue(modelName, out var att))
{
// Do our formatting here
var formatted = _numberformatter.FormatNumber(result.Model as string, att.NumberFormat);
base.SetProperty(bindingContext, modelName, propertyMetadata, ModelBindingResult.Success(formatted));
} else
{
// Do nothing
base.SetProperty(bindingContext, modelName, propertyMetadata, result);
}
}
}
(фактические MyModelBinder
проверки дляFormatNumber
атрибут и обрабатывает свойство соответственно, но я оставил его для краткости, так как это на самом деле не имеет значения).
И мой ModelBinderProvider
:
public class MyModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
var modelType = context.Metadata.ModelType;
if (!typeof(MyComplexModel).IsAssignableFrom(modelType))
return null;
if (!context.Metadata.IsComplexType || context.Metadata.IsCollectionType)
return null;
var propertyBinders = context.Metadata.Properties
.ToDictionary(modelProperty => modelProperty, context.CreateBinder);
return new MyModelBinder(
propertyBinders,
(INumberFormatter)context.Services.GetService(typeof(INumberFormatter)),
(ILoggerFactory)context.Services.GetService(typeof(ILoggerFactory))
);
}
}
И, конечно,Я добавил провайдера в класс StartUp
:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(config =>
{
config.ModelBinderProviders.Insert(0, new MyModelBinderProvider());
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
Опять же, это прекрасно работает, когда данные публикуются в виде данных формы, а не в виде JSON.Каков будет правильный способ реализовать это?Я где-то читал, что я должен смотреть не в направлении ModelBinding
, а в направлении «преобразователей JSON», но я не нашел ничего, что действительно работало (пока).
Edit: Я создал git-репозиторий, чтобы продемонстрировать мою проблему здесь .Чтобы увидеть мою проблему, установите точку останова здесь в TestController
, где модель возвращается в методе Post
.Начать проект;простая веб-страница будет показана с двумя кнопками.Слева будут размещены данные формы в виде данных формы, и вы увидите, что модель возвращается с обратным номером телефона (в качестве примера).Нажмите правую кнопку, и данные будут опубликованы в виде модели JSON.Обратите внимание, что возвращаемая модель имеет значения 0 id и null
для обоих свойств.