#c# #asp.net-core #asp.net-core-mvc
#c# #asp.net-ядро #asp.net-core-mvc
Вопрос:
У меня есть следующая модель представления:
public class FormViewModel {
[Required, StringLength(100)]
public string Name { get; set; }
private object _parameters = null;
public object Parameters {
get {
if (_parameters == null)
_parameters = Activator.CreateInstance(Type.GetType("CustomParameters"));
return _parameters;
}
set {
_parameters = value;
}
}
}
Где CustomParameters выглядит так:
public class CustomParameters {
[Required]
public string Text { get; set; }
}
Теперь, если я опубликую следующие данные формы:
"Name" => "Foo"
"Parameters.Text" => "Bar"
Свойство «Name» задано правильно, однако свойству «Parameters.Text» присвоено значение null.
Пожалуйста, обратите внимание, что приведенный выше сценарий был упрощен, и параметры должны поддерживать привязку к нескольким пользовательским типам.
Редактировать — я добавил следующий код, который я использовал в ASP.NET MVC , но ASP.NET Привязка модели ядра, похоже, была переписана, и я не вижу, что мне нужно делать:
public class IRuntimeBindableModelBinder : DefaultModelBinder {
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
var newBindingContext = new ModelBindingContext() {
// In the original method you have:
// ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => bindingContext.Model, typeof(TModel)),
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => bindingContext.Model, bindingContext.Model.GetType()),
ModelName = bindingContext.ModelName,
ModelState = bindingContext.ModelState,
PropertyFilter = bindingContext.PropertyFilter,
ValueProvider = bindingContext.ValueProvider
};
return base.BindModel(controllerContext, newBindingContext);
}
}
Я был бы признателен, если бы кто-нибудь мог помочь.
Спасибо
Комментарии:
1. Я не вижу необходимости в этом? Является
CustomParameters
общим, или вы заранее не знаете входящие поля?2. Я жестко запрограммировал тип в приведенном выше примере. Но тип параметров может измениться. Например, скажем, я отправляю обратно параметры для виджета, параметры которого варьируются в зависимости от типа виджета.
3. Есть ли у вас какие-либо метаданные во входящем запросе, которые сообщают вам, какая это модель? То есть, как вы это выясняете? Отражение?
4. На самом деле я публикую тип параметров вместе с именем и параметрами. Это отправляется, когда пользователь выбирает соответствующий тип виджета. Он также выполняет вызов ajax для заполнения представления соответствующим классом параметров. Также стоит отметить в ASP.NET MVC Я сделал класс parameters реализующим интерфейс IRuntimeBindable (это будет класс «CustomParameters» в приведенном выше примере, и у него нет членов). Однако я открыт для изменения этого, чтобы использовать атрибут против свойства или какой-либо другой идеи.
5. Я думаю, вы можете сделать это, внедрив пользовательскую привязку модели.. Я провожу несколько тестов.. давайте посмотрим 🙂
Ответ №1:
Это может быть сделано с помощью пользовательского ModelBinder. Проблема здесь в том, что .NET не знает, какой тип хранится в свойстве object, поэтому по умолчанию он равен null.
Вам нужно знать целевой тип (либо по имени, либо по дополнительному свойству типа), тогда вы можете создать ModelBinder следующим образом:
public class MyModelBinder : IModelBinder
{
private readonly IModelMetadataProvider _modelMetadataProvider;
private readonly IModelBinderFactory _modelBinderFactory;
public MyModelBinder(IModelMetadataProvider modelMetadataProvider, IModelBinderFactory modelBinderFactory)
{
_modelMetadataProvider = modelMetadataProvider;
_modelBinderFactory = modelBinderFactory;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var typeValue = bindingContext.ValueProvider.GetValue(nameof(ComplexModel.Type)).Values;
var nameValue = bindingContext.ValueProvider.GetValue(nameof(ComplexModel.Name)).Values;
var finalModel = new ComplexModel
{
Name = nameValue,
Type = typeValue
};
var innerType = LookupType(typeValue);
if (innerType != null)
{
finalModel.Parameters = Activator.CreateInstance(innerType);
var modelMetadata = _modelMetadataProvider.GetMetadataForType(innerType);
var modelBinder = _modelBinderFactory.CreateBinder(new ModelBinderFactoryContext
{
Metadata = modelMetadata,
CacheToken = modelMetadata
});
var modelName = bindingContext.BinderModelName == null ? "Parameters" : $"{bindingContext.BinderModelName}.Parameters";
using (var scope = bindingContext.EnterNestedScope(modelMetadata, modelName, modelName, finalModel.Parameters))
{
await modelBinder.BindModelAsync(bindingContext);
}
}
bindingContext.Result = ModelBindingResult.Success(finalModel);
return;
}
//NOTE: this maps a type string to a Type.
//DO NOT transmit a type FullName and use reflection to activate, this could cause a RCE vulnerability.
private Type LookupType(string type)
{
switch (type)
{
case "text":
return typeof(TextParam);
case "int":
return typeof(IntParam);
}
return null;
}
}
//Sample of ComplexModel classes
[ModelBinder(typeof(MyModelBinder))]
public class ComplexModel
{
public string Name { get; set; }
public string Type { get; set; }
public object Parameters { get; set; }
}
public class TextParam
{
public string Text { get; set; }
}
public class IntParam
{
public int Number { get; set; }
}
ПРИМЕЧАНИЕ: При выполнении пользовательской десериализации с типом важно ограничить список разрешенных типов для десериализации. Если вы принимаете полное имя типа и используете отражение для активации, это может привести к уязвимости RCE, поскольку в .NET есть некоторые типы, которые выполняют код при установке свойства.
Комментарии:
1. Спасибо, я проверю это первым делом утром. Пожалуйста, обратите внимание, что безопасность не является проблемой, поскольку он размещен на выделенном сервере, и доступ к нему будут иметь только администраторы. Пожалуйста, не могли бы вы предложить альтернативный вариант, если безопасность не является проблемой. Спасибо
2. Опять же, я не рекомендую это. если возникает такая ошибка, то у каждого есть потенциальный доступ к этому серверу, который имеет доступ к вашему контроллеру с этим ModelBinder. — Однако вот пример активации типа с отражением: gist.github.com/hiiru/f106fd974564b8d74f9ed3253d3ec632 В этом примере существует ограничение, заключающееся в том, что он находит только типы, основанные на базовом типе, но вы можете легко изменить его, если вам это действительно нужно. (примечание: работает только с классами, которые имеют конструктор без параметров)
3. У меня только что была возможность протестировать это, и это работает отлично, спасибо. Я просматривал исходный код для некоторых других связующих, и у меня возникло ощущение привязки Context. В игру вступит EnterNestedScope. Еще раз спасибо.