MVC UpdateModel и подклассы против базового класса

Я хочу использовать метод UpdateModel для подкласса, который извлекается во время выполнения, было бы здорово, если бы кто-нибудь мог пролить свет на то, делаю ли я его полный хэш и / или нет ли то, что я пытаюсь сделать возможно.

Я использую универсальное действие для управления проверкой нескольких частичных представлений; Я пытаюсь уйти от определенного действия для частичного просмотра.

Каждое частичное представление имеет уникальную модель, производную от базовой модели:

public class ModelA : ModelBase{
     [Required]
     public string SomeStringProperty{get;set;}
...
}
public class ModelB : ModelBase{
     [Required]
     public DateTime? SomeDateProperty{get;set;}
...
}
public class ModelBase{
     public Guid InstanceId{get;set;}
}

Я использую FormCollection в Action, чтобы получить отправленные элементы формы и их значения, включая тип модели, которую View должен использовать для проверки своего запроса. Не обращайте внимания на последствия этого для безопасности в этом примере, я знаю о них, и это только внутреннее подтверждение концепции

    [HttpPost]
    public ActionResult ChangeCaseState(int id, FormCollection formCollection)
    {
        Guid instanceId = new Guid(formCollection["instanceId"]);
        string modelType = formCollection["modelType"];
        //Return a specific Model class based on the event/modelType
        var args = GetStateModelClass(modelType, instanceId);

        try
        {
            UpdateModel(args);
            if(Model.IsValid){
             ...
        }
        catch (Exception)
        {
            return View("~/Views/Shared/StateForms/" + modelType + ".ascx", args);
        }...

А вот код, который я использую для возврата подкласса на основе modelType, переданного контроллеру.

private static ModelBase StateModelClassFactory(string stateModelTypeName, Guid instanceId)
        {
            switch (stateModelTypeName)
            {
                case "modelTypeA":
                    return new ModelA(workflowInstanceId);
                case "modelTypeB":
                    return new ModelB(workflowInstanceId);
    ...
    }

Поскольку тип возвращаемого значения метода StateModelClassFactory относится к базовому классу, хотя я фактически возвращаю подкласс, привязка модели, используемая методом UpdateModel, привязывается только к значениям в базовом классе.

Есть идеи, как я могу решить эту проблему?

ОБНОВЛЕНИЕ:

Я создал связыватель модели клиента:

public class CustomModelBinder : DefaultModelBinder
    {
      public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
            {

И назначил новую привязку модели правильному базовому классу, чтобы узнать, что происходит под капотом:

ModelBinders.Binders.Add(typeof(ModelBase), new CaseController.CustomModelBinder());

Когда я отлаживаю связыватель модели и проверяю контекст привязки, свойство Model представляет правильный подкласс, но свойство ModelType является свойством базового класса. Стоит ли искать изменение ModelType в методе BindModel? Если да, то какие-либо указатели на то, как это сделать, установщик в ModelType, похоже, был сделан избыточным. Я также заметил, что SomeDateProperty из подкласса фактически находится в свойстве PropertyMetadata .... Кажется, так близко к тому, чтобы вести себя так, как мне хотелось бы.


person Tr1stan    schedule 26.11.2010    source источник


Ответы (3)


Я только что столкнулся с этой конкретной проблемой и обнаружил, что лучшим общим подходом было бы просто преобразовать вашу модель в dynamic, передав ее в UpdateModel:

[HttpPost]
public ActionResult ChangeCaseState(int id, FormCollection formCollection)
{
    ...try
    {
        UpdateModel((dynamic)args);//!!notice cast to dynamic here
        if(Model.IsValid){
         ...
    }
    catch...

Кажется, это устанавливает все доступные свойства моего типа, независимо от того, связана ли моя переменная с базовым типом.

В CodePlex есть рабочий элемент для решения этой проблемы: http://aspnet.codeplex.com/workitem/8277?ProjectName=aspnet

person DMac the Destroyer    schedule 27.05.2011
comment
Спасибо. Я использовал .net 3.5 (тег добавлен к вопросу). Я новичок в использовании ключевого слова dynamic, поскольку я еще не работал ни с одним веб-сайтом .net 4.0, попробую, если это все еще проблема. - person Tr1stan; 20.07.2011
comment
Хорошая находка, я столкнулся с той же проблемой при использовании TryUpdateModel, а не UpdateModel, и приведение к динамическому преобразованию помогло. - person blins; 04.01.2012
comment
Выходи за меня! Спасибо, я был в аду до того, как наткнулся на этот пост. Икс - person ctrlplusb; 08.04.2013
comment
Спасибо, я потратил кучу времени на этот вопрос. - person Ian Newson; 19.04.2013

Думаю, я решил свою проблему. В основном из-за того, как я извлекаю класс модели перед вызовом UpdateModel, привязка модели связывает базовый класс, даже если модель принадлежит подклассу - это код, который я использовал для решения моей конкретной проблемы:

  public class SubClassModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var model = bindingContext.Model;
        var metaDataType = ModelMetadataProviders.Current.GetMetadataForType(null, model.GetType());
        bindingContext.ModelMetadata = metaDataType;
        bindingContext.ModelMetadata.Model = model;

        return base.BindModel(controllerContext, bindingContext);
    }
}

И в Global.asax:

ModelBinders.Binders.Add(typeof(ModelBase), new SubClassModelBinder ());

Спасибо Дарину за его начальный указатель.

person Tr1stan    schedule 29.11.2010

Чтобы решить эту проблему, вы можете написать настраиваемый связыватель модели для базового типа, который на основе значения строкового свойства будет возвращать правильный дочерний экземпляр.

person Darin Dimitrov    schedule 26.11.2010
comment
Спасибо, Дарин, я быстро поигрался с тем, что, по моему мнению, является началом вашего решения, и обновил вопрос. - person Tr1stan; 27.11.2010