TryUpdateModel无法按预期工作

时间:2011-01-28 22:08:17

标签: asp.net-mvc asp.net-mvc-3

我正在开发一个ASP.NET MVC项目,该项目允许用户对对象的属性执行批量编辑。该实现是一种“向导”形式,具有以下四个阶段:

  1. “选择要编辑的属性” - 第一页将向用户显示一个复选框列表,表示要编辑的每个属性。用户应检查他们想要编辑的属性,然后选择“继续”。
  2. “编辑所选属性” - 第二页将向用户显示不同“编辑器”的列表,这些编辑器对于他们在第一页上选择的每个属性都是唯一的。
  3. “查看您的更改” - 此页面允许用户查看他们对所选属性所做的更改。
  4. “提交您的更改” - 此页面实际上会针对所选对象集合提交有关用户希望对所选属性进行编辑的信息。
  5. 相当直截了当。

    正如我所提到的,“编辑器”对于每个属性都是唯一的,并且可以在其上具有不同控件的任意组合。一旦用户进行了编辑并且应用程序将该信息发布到“查看”页面,我就是我当前遇到问题的地方。

    我们开发了“EditorWorker”类的概念,该类对每个属性都是唯一的,它负责生成每个编辑器所需的ViewModel,但也负责创建/返回(在“Review”页面控制器内) action)一个对象,它是发布数据可以绑定到的编辑器的“模型”对象,然后可以用来显示已编辑的数据以供查看。此对象应具有与编辑器中控件的ID匹配的属性,以便可以进行模型绑定。

    我有“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,但传递给它的模型对象上的字段实际上并未从已发布的表单数据中已确认的值中填充。传递给调用的模型对象不是List,它只是一个poco。然后,最终希望填充的模型对象被添加到模型对象的List集合中,然后将其用于在Review页面上显示已发布的数据以供查看。我根本没有从数据存储区加载任何东西。每个选定属性的唯一编辑器将呈现在“编辑”屏幕上,并且我尝试捕获编辑值,以便在将批量编辑提交到服务之前在“审阅”屏幕上显示。希望这更清楚。感谢。

    更新2:我在评论中包含了@mare所要求的ReviewBatchViewModel类的定义。在大多数情况下,在此代码示例中使用var关键字主要是因为填充这些变量的方法将为所选的每个属性返回不同类型的对象,因此我从不确切地知道它在运行时会是什么(尽管它总是会实现一个接口,在这种情况下是IEditorWorker和/或IEditData)。模型中有一个名为“Attribute”的类。提供的代码示例有三个相对于该类的变量:1)SelectedAttributeIds是用户选择编辑的属性的Id的逗号分隔列表,它通过编辑页面传递到Review页面隐藏字段,2)selectedAttributes是实际属性对象的集合,对应于我可以使用的那些ID,3)attributeEditData是特定于IEditData类的实例每个给定的属性,我试图将编辑页面中的发布数据绑定到。

    希望这些额外信息能够更清晰地解决问题。

2 个答案:

答案 0 :(得分: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。我在上面的代码中添加了一些注释以帮助解决一些

答案 1 :(得分:0)

@Josh,你非常乐于帮助我理解为什么TryUpdateModel不能为我工作,我很感激。不幸的是,我认为这里更大的问题是我(不完全确定哪个)要么无法或不愿意记录我在这里要解决的问题的所有要求的详细信息,我认为这是任何人都难以提供有意义的输入。对我们来说最大的问题是,因为我们直到运行时才知道用户选择了哪些属性进行编辑,我们不知道我们将在这些控制器操作的上下文中使用哪些对象,或者它们的类型将是什么是。我们可以安全地处理已知数据和类型的一个地方是在每个独特的EditorWorker对象的上下文中,这是我选择在这里进行繁重的工作。

我希望并试图利用MSFT在MVC框架内为我们所做的所有繁重工作来处理模型绑定,但我现在得出的结论是,我认为不是为我们工作。我此时提出的解决方案是允许LoadEditData类的EditorWorker方法处理为我加载EditData类。由于每个EditorWorker类都是唯一的,并且知道与之关联的属性。我最初遇到的问题是我让EditorWorker.LoadEditData方法只返回我正在使用的属性所需的特定类型EditData类的空实例,然后让MVC框架句柄模型绑定到该对象为我。那是行不通的,因为该方法旨在返回类型为IEditData的对象,我从来没有真正确切地知道我当前使用的是什么类型,所以我无法指定类型调用任一类型的方法:TryUpdateModel<T>UpdateModel<T>

所以我提出的解决方案,至少现在一直在进行(重新教育和/或重构可能会在未来很好地改变这一点,谁知道)就是通过{{1}对象进入对Request.Form的调用,让该方法处理实际加载它知道需要为其负责的属性返回的EditorWorker.LoadEditData对象,它可以做,因为它知道应该知道什么信息在已发布的表单集合中加载其EditData对象。

这就是我现在所处的位置。谢谢你的帮助。