TryUpdateModel无法按预期工作

Bob*_*ley 2 asp.net-mvc asp.net-mvc-3

我正在开发一个ASP.NET MVC项目,允许用户对对象的属性进行批量编辑.该实现是一种类似"向导"的形式,其过程分为四个阶段,如下所示:

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

非常坦率的.

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

我们开发了"EditorWorker"类的概念,该类对每个属性都是唯一的,它负责生成每个编辑器所需的ViewModel,但也负责创建/返回(在"Review"页面控制器操作中)对象是可以绑定发布数据的编辑器的"模型"对象,然后可以用它来显示已编辑的数据以供查看.此对象应具有与编辑器中控件的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; }
}
Run Code Online (Sandbox Code Playgroud)

我完全理解这个控制器动作在当前状态下是不完整的.我正在努力尝试在继续之前正确填充这些EditData对象.如上所述,任何想法都将非常感激.谢谢.

更新:关于@ mare的评论,我应该更清楚地解释这部分,对不起.到TryUpdateModel呼叫实际上返回true,但传递的模型对象的字段到它实际上并没有被从已证实存在于提交的表单数据的值填充.传递给调用的模型对象不是List,它只是一个poco.然后,最终希望填充的模型对象被添加到模型对象的List集合中,然后将其用于在Review页面上显示已发布的数据以供查看.我根本没有从数据存储区加载任何东西.每个选定属性的唯一编辑器将呈现在"编辑"屏幕上,并且我尝试捕获编辑值,以便在将批量编辑提交到服务之前在"审阅"屏幕上显示.希望这更清楚.谢谢.

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

希望这些额外信息可以更清楚地解决问题.

Jos*_*osh 10

TryUpdateModel是一种通用方法,因此尝试根据通用类型参数推断所有类型信息.

根据我在上面的例子中的理解,你总是传递IEditData正确的?

实际上你说:

TryUpdateModel<IEditData>(attributeEditData)
Run Code Online (Sandbox Code Playgroud)

这很可能是没有看到任何属性被设置的原因,因为IEditData它没有任何属性;)

要做你想做的事,你可能需要创建一个自定义的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;
}
Run Code Online (Sandbox Code Playgroud)

请注意在typeof(TModel)任何地方使用...在你的情况下被翻译成typeof(IEditData),这不是很有用,因为它只是一个标记界面.您应该能够根据自己的需要调整此代码,确保使用GetType()以便在运行时获取实际类型.

我希望这会有所帮助!

PS我已经在上面的代码中添加了一些注释以帮助解决一些问题