提交由部分(异步)视图组成的表单

时间:2014-12-07 17:49:37

标签: c# jquery asp.net-mvc

我试图使用异步Tabs使JQuery-Tabcontrol工作。

tabcontrol是表单的一部分,问题是,提交并不真正适用于视图模型的异步加载部分。

我的viewmodel类似于windows-forms。它由带孩子的控件组成。 每个控件都有一个editortemplate。我试图让我的tab-control异步工作。 (它同步运作良好)但这部分让我从星期五开始忙碌。

使用这些编辑器模板我需要编写自定义模型绑定器。当我在那里放置一个断点时,它不会受到按需加载的视图部分的影响。但是,当我深入了解控制器的值提供者/表单集合时,我会找到这些值。所以它肯定会收到所有数据,但它不想为它调用Binders。

如何让它调用绑定器,以便输入其中一个选项卡的数据绑定到viewmodel?

这是制表符控件的视图

@model {..}.TabControlViewModel

<div class="tabs">
  <ul>
    @for (int i = 0; i < Model.Pages.Count; i++)
    {
      <li>
          @Html.ActionLink(Model.Pages[i].Title, "ShowPartial", new  { formId = Model.FormId, controlId = Model.Pages[i].Id})
      </li>
    }
  </ul>
</div>

这是控制器方法:

    public async Task<ActionResult> ShowPartial(int formId, int controlId)
    {
        TabPageViewModel tabPage= await FormControlManager.GetTabPage(this, formId, controlId);
        await Task.Delay(1000); // just for testing
        return PartialView(tabPage);
    }

ShowPartial的视图

@model {...}.ControlViewModel

@Html.EditorFor(x => x)

更详细的信息

整个Control-Nesting-Idea基于本教程:

http://blogs.microsoft.co.il/gilf/2012/04/23/generating-aspnet-mvc-view-controls-according-to-xml-configurations/(虽然它不平坦但递归(如窗体))

所以基本上所有东西都是FormControl的成员。这也是控制器的初始动作“Show”

返回的

当用户选择一个选项卡时,它通过传递formid和controlId来获取表单的引用,以获取所需的TabPage控件并返回它。因此,从数据角度来看,异步部分已经是ViewModel的一部分,但最初在渲染时并未使用。我需要更改以使其同步工作的唯一事情是TabControl的编辑器模板。

1 个答案:

答案 0 :(得分:0)

我终于修改了一些东西:

首先,我扩展了我的ModelBinder,以便它处理List,我通过覆盖DefaultModelBinder

的源代码读取自己后,通过覆盖BindProperty实现了这一点。
public class ControlViewModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        ValueProviderResult formIdResult = bindingContext.ValueProvider.GetValue("FormId");
        if (formIdResult == null)
            return null;
        long formId = long.Parse(formIdResult.AttemptedValue);

        ValueProviderResult controlIdResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".Id");
        if (controlIdResult == null)
            return null;

        int controlId = int.Parse(controlIdResult.AttemptedValue);
        FormViewModel form;
        ControlViewModel model = FormControlManager.GetControl(formId, controlId, out form);

        if (model == null)
            return null;

        bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType());
        return base.BindModel(controllerContext, bindingContext);
    }


    protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
    {
        // need to skip properties that aren't part of the request, else we might hit a StackOverflowException
        string fullPropertyKey = _buildPropName(bindingContext.ModelName, propertyDescriptor.Name);
        if (!bindingContext.ValueProvider.ContainsPrefix(fullPropertyKey))
        {
            return;
        }
        // call into the property's model binder
        object originalPropertyValue = propertyDescriptor.GetValue(bindingContext.Model);
        ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name];

        // If it is not an IEnumerable we can make the default Function do its magic
        if (!(originalPropertyValue is IEnumerable<ControlViewModel>))
            base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
        else
        {
            // Else we need to call the BindModel method for each Member of the Sequence.
            int index = 0;
            foreach (ControlViewModel value in (IEnumerable<ControlViewModel>)originalPropertyValue)
            {
                propertyMetadata.Model = value;
                ModelBindingContext innerBindingContext = new ModelBindingContext()
                {
                    ModelMetadata = propertyMetadata,
                    ModelName = fullPropertyKey + String.Format("[{0}]", index++),
                    ModelState = bindingContext.ModelState,
                    ValueProvider = bindingContext.ValueProvider
                };

                BindModel(controllerContext, innerBindingContext);
            }
        }

    }

    static string _buildPropName(string prefix, string propertyName)
    {
        if (String.IsNullOrEmpty(prefix))
        {
            return propertyName;
        }
        else if (String.IsNullOrEmpty(propertyName))
        {
            return prefix;
        }
        else
        {
            return prefix + "." + propertyName;
        }
    }

第二部分是,我必须修改HtmlHelper以获取要异步渲染的部分。

public static MvcHtmlString EditorForDynamic<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
        where TModel : IAsyncControlWithPrefix
{
    IAsyncControlWithPrefix model = htmlHelper.ViewData.Model;
    htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix = model.PrefixForPartialRender + htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix;
    return htmlHelper.EditorFor(expression);
}

public static string GetFullText<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
    return htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(ExpressionHelper.GetExpressionText(expression));
}

这确保了那些异步呈现的部分仍然创建了看起来很简洁的完整嵌套字符串:

儿童[0] .PAGES [0] .Rows [0] .Cells [0]。文本 而不仅仅是行[0] .Cells [0] .Text

这是界面

public interface IAsyncControlWithPrefix
{
    string PrefixForPartialRender { get; set; }
}

我的情况是每个TabPage实现此接口,因此我可以异步加载每个标签页。

Tabpages需要获取PrefixAssigned,这在TabControlView

中发生

像这样:

@model {...}.TabControlViewModel

<div class="tabs">
<ul>
    @for (int i = 0; i < Model.Pages.Count; i++)
    {
      <li>
          @Html.ActionLink(Model.Pages[i].Title, "ShowPartial", new  { formId = Model.FormId, controlId = Model.Pages[i].Id})
          @{ Model.Pages[i].PrefixForPartialRender = Html.GetFullText(m => m.Pages[i]);}
      </li>
    }
</ul>
</div>

嗯,现在它就像一个魅力。它可以立即从不同的异步加载Tabs提交数据。非常好。