2011-01-28 3 views
2

Я работаю над проектом ASP.NET MVC, который позволит пользователям выполнять пакетные изменения атрибутов объектов. Реализация в своего рода «мастера» как форма с четырьмя фазами в процессе следующим образом:TryUpdateModel работает не так, как ожидалось

  1. «Выберите атрибуты, которые нужно изменить» - первая страница будет представлять пользователю список флажков, представляющих каждый из атрибутов, которые они хотят редактировать. Пользователь должен проверить атрибуты, которые они должны отредактировать, и выбрать «Продолжить».
  2. «Редактировать выбранные атрибуты» - вторая страница представит пользователю список отдельных «редакторов», который будет уникален для каждого из атрибутов, выбранных на первой странице.
  3. «Просмотрите свои изменения» - эта страница позволит пользователю просмотреть изменения, которые они сделали, с выбранными атрибутами.
  4. «Отправить свои изменения» - эта страница будет фактически отправлять информацию об изменениях, которые пользователь хочет сделать для выбранных атрибутов для выбранной коллекции объектов.

Довольно прямолинейный.

Как я уже упоминал, «редактор» будет уникальным для каждого атрибута и может содержать любую комбинацию различных элементов управления. После того, как пользователь внесла свои исправления и сообщения приложения, информация на странице «Обзор», где у меня сейчас возникают проблемы.

Мы разработали концепцию класса EditorWorker, которая уникальна для каждого атрибута, который отвечает за создание ViewModel, необходимого для каждого редактора, но также несет ответственность за создание/возвращение (на странице «Обзор») действие контроллера) объект, который является «модельным» объектом для редактора, к которому могут быть привязаны почтовые данные, которые затем могут использоваться для отображения отредактированных данных для проверки. Этот объект должен иметь свойства, которые соответствуют идентификаторам элементов управления в редакторе, так что может возникнуть привязка модели.

У меня есть «EditorWorker», создающий и возвращающий необходимый класс, но по какой-то причине, когда я вызываю TryUpdateModel и передаю в этом классе, его свойства не заполняются в результате вызова этого метода, поскольку я ожидал бы их. Я проверил, что значения находятся в опубликованном FormCollection. Ниже приведен код моего действия с контроллером, где я пытаюсь это сделать. Если кто-то может помочь мне понять, почему TryUpdateModel не работает в этом сценарии, я был бы очень благодарен.

[HttpPost] 
public virtual ActionResult Review(ReviewBatchViewModel model) 
{ 
    var selectedAttributes = GetSelectedAttributes(model.SelectedAttributeIds.Split(',').Select(i => Int64.Parse(i)).ToArray()); 
    var workers = new List<IEditorWorker>(); 
    var reviewData = new Dictionary<ViewAttribute, IEditData>(); 
    foreach (var attribute in selectedAttributes) 
    { 
     if (!string.IsNullOrEmpty(attribute.EditorWorker)) // If there is no EditorWorker defined for this object, move on... 
     { 
      var worker = ServiceLocator.Current.GetInstance(Type.GetType(string.Format("{0}.{1}", EditorWorkerNamespace, attribute.EditorWorker))); 
      var attributeEditData = ((IEditorWorker)worker).LoadEditData(); 
      if (TryUpdateModel(attributeEditData)) 
       model.EditData.Add(attributeEditData); // model.EditData is a List<IEditData> that will be iterated on the Review page 
      reviewData.Add(attribute, attributeEditData); 
     } 
    } 

    return View(model); 
} 

// ReviewBatchViewModel.cs 
public class ReviewBatchViewModel : BaseViewModel 
{ 
    public ReviewBatchViewModel() { EditData = new List<IEditData>(); } 

    public string SelectedAttributeIds { get; set; } 
    public List<ViewAttribute> SelectedAttributes { get; set; } 
    public List<IEditData> EditData { get; set; } 
} 

// IEditData.cs 
public interface IEditData 
{ 
} 

// BroadcastStatusEditData.cs 
public class BroadcastStatusEditData : IEditData 
{ 
    public int BroadcastStatus { get; set; } 
} 

Я полностью понимаю, что это действие контроллера неполно в его текущем состоянии. В настоящее время я работаю над попыткой получить эти объекты EditData, заполненные правильно, прежде чем двигаться дальше. Как уже упоминалось, любые мысли будут высоко оценены. Благодарю.

ОБНОВЛЕНИЕ: Что касается комментария @ mare, я должен был объяснить эту часть более четко, извините. Вызов TryUpdateModel фактически - это, возвращающий значение true, но поля объекта модели, передаваемые в него, фактически не заполняются из значений, которые были подтверждены в опубликованных данных формы. Объект модели, передаваемый в вызов, не является списком, его просто poco. Получаемый, в конечном счете, надежно заполненный модельный объект затем добавляется в коллекцию списка объектов модели, которая затем будет использоваться для отображения опубликованных данных для просмотра на странице обзора. Я вообще ничего не загружаю из хранилища данных. Уникальные редакторы для каждого выбранного атрибута визуализируются на экране «Редактирование», и я пытаюсь захватить значения редактирования для отображения на экране обзора перед отправкой партии изменений в службу. Надеюсь, это более понятно. Благодарю.

ОБНОВЛЕНИЕ 2: Я включил определение класса ReviewBatchViewModel в соответствии с запросом @mare в комментариях. Использование ключевого слова var в большинстве случаев в этом примере кода во многом связано с тем, что методы, которые заполняют эти переменные, будут возвращать объект другого типа для каждого выбранного атрибута, поэтому я никогда не знаю точно, что его (хотя он всегда будет реализовывать интерфейс, в этом случае либо IEditorWorker, и/или IEditData). В модели есть один класс «Атрибут». Представленный образец кода имеет три переменные относительно этого класса: 1) SelectedAttributeIds представляет собой список идентификаторов атрибутов, которые пользователь выбрал для редактирования, разделенный запятыми, который передается с страницы редактирования на страницу обзора через скрытое поле, 2) selectedAttributes - это коллекция фактических объектов атрибута, которые соответствуют тем идентификаторам, с которыми я могу работать, и 3) attributeEditData - это экземпляр класса IEditData, специфичный для каждого данного атрибута, который я пытаюсь связать опубликованные данные из Edit страница.

Надеюсь, эта дополнительная информация очищает даже больше.

+0

TryUpdateModel() возвращает true, если выполнено успешно, и false, если это не так. Что вы имеете в виду, не работая? Означает ли это, что он возвращает ложь? Если это так, вы можете попробовать UpdateModel(), и поскольку это не возвращает true или false, скорее всего, это произойдет с исключением, и вы можете использовать данные этого исключения, чтобы выяснить, что не так. Я не думаю, что UpdateModel() и TryUpdateModel() будут работать со списком. Кроме того, я не понимаю, почему вы загружаете EditData из хранилища данных, а затем обновляете его из модели, а затем сохраняете эти обновленные данные обратно в модель. ? – mare

+0

@mare - просмотрите мои обновления в ответ на ваш вопрос. Благодарю. –

+0

Вы могли бы разместить соответствующие части класса ReviewBatchViewModel и attributeEditData (что бы это ни было, что, кстати, напоминает мне, что было бы легче понять, могли бы вы также явно объявить свои переменные вместо использования var)? Я думаю, что TryUpdateModel не работает, потому что ему не удается создать сопоставления между ReviewBatchViewModel и типом атрибутаEditData. – mare

ответ

0

@ Josh, вы очень помогли мне понять, почему TryUpdateModel не работает для меня, и я ценю это. К сожалению, я думаю, что более серьезной проблемой здесь был тот факт, что я (не совсем уверен, какой) был либо неспособен, либо не желал бы документировать все детали требований к проблеме, которую я пытаюсь решить здесь, что, я думаю, сделал для кого-либо трудно обеспечить значительный смысл. Самая большая проблема для нас в том, что, поскольку мы понятия не имеем до времени выполнения, которое атрибуты пользователь выбрал для редактирования, мы не знаем, с какими объектами мы будем работать в контексте этих действий контроллера, или то, что их типы будут быть. Одно место, в котором мы безопасно можем работать с известными данными и типами, находится в контексте каждого из уникальных объектов EditorWorker, где я решил сделать тяжелый подъем здесь.

Я надеялся и попытался воспользоваться всем тяжелым грузом, который MSFT сделал для нас в рамках MVC для обработки привязки к модели, но я пришел к выводу на этом этапе, что я не думаю это будет работать для нас. Решение, которое я придумал на этом этапе, заключается в том, чтобы позволить методу LoadEditData классов классов EditorWorker загружать классы EditData для меня. Поскольку каждый класс EditorWorker уникален и обладает знанием атрибута, с которым он связан. Первоначально проблема заключалась в том, что я давал возможность методу EditorWorker.LoadEditData просто возвращать пустой экземпляр определенного типа класса EditData, который мне нужен для атрибута, с которым я работал в данный момент, и пусть среда MVC обрабатывает модель привязки к этому объекту для меня. Это не работает, потому что этот метод предназначен для возврата объекта типа IEditData, и я никогда не знал точно, с каким типом он работал, поэтому у меня не было способа указать тип вызова в любой из типичные методы: TryUpdateModel<T> или UpdateModel<T>.

Итак, решение, которое я придумал, и по крайней мере на данный момент (перевоспитание и/или реорганизация могут очень хорошо изменить это в будущем, кто знает) - просто передать объект Request.Form в вызов EditorWorker.LoadEditData и пусть этот метод обрабатывает фактическую загрузку объекта EditData, который, как он знает, ему нужно вернуть для атрибута, за который он отвечает, что он может сделать, поскольку он знает, какая информация должна быть в собранной форме для загрузки ее EditData объект.

Так вот, где я сейчас. Спасибо за помощь.

10

TryUpdateModel является общим методом и поэтому пытается сделать вывод о всех типах информации на основе Generic Type Parameter.

Из чего я понимаю в вашем примере выше, вы всегда проходите в IEditData правильно?

В действительности вы говорите:

TryUpdateModel<IEditData>(attributeEditData) 

Это, скорее всего, причина не видит каких-либо свойств быть установлен, так как IEditData не имеет каких-либо свойств;)

Чтобы сделать то, что вы хотите вам, вероятно, придется создать custom ModelBinder.

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

Основываясь на ваших комментариях Я изменил код из System.Object на ваш интерфейс IEditData, но все еще сохраняется. Я заметил в предыдущем комментарии, который вы упомянули, используя var, потому что вы не знали тип до времени выполнения. Тем не менее, нет ничего волшебного в ключевом слове var. Единственное, что он делает, это дать вам неявное типирование, но оно все еще статически типизировано.

Приятная вещь о MVC заключается в том, что вы можете просто перейти к Codeplex и посмотреть на источник для TryUpdateModel, если хотите. Рытье вниз несколько слоев, которые в конечном итоге найти вызов этого метода внутреннего:

protected internal bool TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties, IDictionary<string, ValueProviderResult> valueProvider) where TModel : class { 
    if (model == null) { 
     throw new ArgumentNullException("model"); 
    } 

    //valueProvider is passed into this internal method by 
    // referencing the public ControlerBase.ValueProvider property 
    if (valueProvider == null) { 
     throw new ArgumentNullException("valueProvider"); 
    } 

    Predicate<string> propertyFilter = propertyName => BindAttribute.IsPropertyAllowed(propertyName, includeProperties, excludeProperties); 

    //Binders is an internal property that can be replaced by 
    // referencing the static class ModelBinders.Binders 
    IModelBinder binder = Binders.GetBinder(typeof(TModel)); 

    ModelBindingContext bindingContext = new ModelBindingContext() { 
     Model = model, 
     ModelName = prefix, 
     ModelState = ModelState, 
     ModelType = typeof(TModel), 
     PropertyFilter = propertyFilter, 
     ValueProvider = valueProvider 
    }; 
    binder.BindModel(ControllerContext, bindingContext); 
    return ModelState.IsValid; 
} 

Обратите внимание на использование typeof(TModel) везде ... в вашем случае, становится переведенный в typeof(IEditData), что не очень полезно, поскольку является только интерфейсом маркера. Вы должны уметь адаптировать этот код для собственного использования, убедившись, что используете GetType(), чтобы получить фактический тип во время выполнения.

Надеюсь, это поможет!

P.S.Я добавил несколько комментариев к приведенному выше коду, чтобы помочь немного.

+0

Хмммм ... Я понимаю, что вы говорите, и видите, что это больше всего скорее всего * есть * на самом деле моя проблема здесь. Я не намеренно передаю объект типа 'System.Object', но на основании того, что вы указали, я вижу, что это, вероятно, то, что видит инфраструктура.Моя большая проблема заключается в том, что во время выполнения я не знаю, какой тип я передаю. 'EditorWorker.LoadEditData' возвращает« IEditData »намеренно, поскольку это может быть что угодно, в зависимости от выбранного атрибута. Я открыт для предложений относительно того, как упростить решение, но это сложная проблема. –

Смежные вопросы