ASP.NET MVC UpdateModel с интерфейсом - PullRequest
6 голосов
/ 09 сентября 2009

Я пытаюсь заставить UpdateModel заполнить модель, которая установлена ​​только как интерфейс во время компиляции. Например, у меня есть:

// View Model
public class AccountViewModel {
  public string Email { get; set; }
  public IProfile Profile { get; set; }
}

// Interface
public interface IProfile {
  // Empty
}

// Actual profile instance used
public class StandardProfile : IProfile {
  public string FavoriteFood { get; set; }
  public string FavoriteMusic { get; set; }
}

// Controller action
public ActionResult AddAccount(AccountViewModel viewModel) {
  // viewModel is populated already
  UpdateModel(viewModel.Profile, "Profile"); // This isn't working.
}

// Form
<form ... >
  <input name='Email' />
  <input name='Profile.FavoriteFood' />
  <input name='Profile.FavoriteMusic' />
  <button type='submit'></button>
</form>

Также обратите внимание, что у меня есть пользовательский механизм связывания, который наследуется от используемого DefaultModelBinder, который заполняет IProfile экземпляром StandardProfile в переопределенном методе CreateModel.

Проблема в том, что FavoriteFood и FavoriteMusic никогда не заполняются. Есть идеи? В идеале все это должно быть сделано в связывателе модели, но я не уверен, что это возможно без написания полностью пользовательской реализации.

Спасибо, Брайан

Ответы [ 2 ]

2 голосов
/ 09 сентября 2009

Я должен был бы проверить код ASP.NET MVC (DefaultModelBinder), но я предполагаю, что он отражает тип IProfile, а не экземпляр StandardProfile.

Таким образом, он ищет любые элементы IProfile, которые может пытаться связать, но это пустой интерфейс, поэтому он считает себя выполненным.

Вы можете попробовать что-то вроде обновления BindingContext и изменения ModelType на StandardProfile, а затем вызова

bindingContext.ModelType = typeof(StandardProfile);
IProfile profile = base.BindModel(controllerContext, bindingContext);

В любом случае, иметь пустой интерфейс странно ~


Редактировать: просто хотите добавить, что приведенный выше код является просто псевдокодом, вам нужно проверить DefaultModelBinder, чтобы увидеть, что именно вы хотите написать.


Изменить # 2:

Можете ли вы сделать:

public class ProfileModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
    {
        bindingContext.ModelType = typeof(StandardProfile);
        return base.BindModel(controllerContext, bindingContext);
    }
}

Нет необходимости связывать модель для AccountView, она отлично работает.


Редактировать # 3

Протестировано, вышеприведенное связующее работает, просто нужно добавить:

ModelBinders.Binders[typeof(IProfile)] = new ProfileModelBinder();

Ваше действие выглядит так:

public ActionResult AddAccount(AccountViewModel viewModel) {
    // viewModel is fully populated, including profile, don't call UpdateModel
}

Вы можете использовать IOC при настройке связывателя модели (например, с помощью инжектора конструктора типов).

0 голосов
/ 09 сентября 2009

Непроверенный фактический тип за интерфейсом обсуждался здесь: http://forums.asp.net/t/1348233.aspx

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

public class AccountViewModelModelBinder : DefaultModelBinder
{
    private readonly IProfileViewModel profileViewModel;
    private bool profileBound = false;

    public AccountViewModelModelBinder(IProfileViewModel profileViewModel)
    {
        this.profileViewModel = profileViewModel;
    }

    protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        // Bind the profile
        if (profileBound)
            return;

        profileBound = true;

        bindingContext.ModelType = profileViewModel.GetType();
        bindingContext.Model = profileViewModel;
        bindingContext.ModelName = "Profile";

        BindModel(controllerContext, bindingContext);
    }

    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, System.Type modelType)
    {
        var model = new AccountViewModel();
        model.Profile = profileViewModel;

        return model;
    }
}

По сути, когда связыватель модели «готов», связывающий основную AccountViewModel, я затем изменяю контекст связывания (как предлагает eyston) и снова вызываю BindModel. Это тогда связывает мой профиль. Обратите внимание, что я вызвал GetType для profileViewModel (который предоставляется контейнером IOC в конструкторе). Также обратите внимание, что я включаю флаг, чтобы указать, была ли уже привязана модель профиля. В противном случае был бы бесконечный цикл вызова OnModelUpdated.

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

...