【问题标题】:MVC Dynamic View Data and Dynamic ViewsMVC 动态视图数据和动态视图
【发布时间】:2012-06-05 23:12:23
【问题描述】:

传统上,我使用具有数据注释属性的视图模型构建 MVC 应用程序,并使用编辑器模板动态呈现视图。一切都很好,它确实减少了我构建新视图的时间。我的要求最近发生了变化。现在,我无法在设计时定义视图模型。将在视图上呈现的属性是在运行时根据业务规则决定的。此外,这些属性的验证规则也可以在运行时决定。 (根据业务规则,我的域模型中不需要的字段可能在我看来是必需的)。此外,要渲染的属性集直到运行时才知道 - 用户 A 可以编辑模型中的 6 个属性,而用户 B 可以编辑 9 个属性。

我想知道是否有可能创建一个模型元数据提供程序,它将根据业务规则为无类型视图模型(如属性名称和值的集合)提供我自己的元数据。有人解决了这个问题吗?

【问题讨论】:

  • 你在使用 MVC 的编辑器模板吗?
  • 我正在尝试找出一种方法来将 MVC 编辑器模板与结构松散的视图数据一起使用(例如使用 ViewData 属性)。问题是结构松散的数据没有与模型元数据提供者集成,因此编辑器模板对我的影响不大。

标签: asp.net-mvc asp.net-mvc-3 dynamic views viewmodel


【解决方案1】:

我通过创建更复杂的模型解决了类似的问题,并使用自定义编辑器模板使模型呈现为看起来像典型的编辑器,但使用动态字段信息:

public class SingleRowFieldAnswerForm
{
    /// <summary>
    /// The fields answers to display.
    /// This is a collection because we ask the MVC to bind parameters to it,
    /// and it could cause issues if the underlying objects were being recreated
    /// each time it got iterated over.
    /// </summary>
    public ICollection<IFieldAnswerModel> FieldAnswers { get; set; } 
}

public interface IFieldAnswerModel
{
    int FieldId { get; set; }

    string FieldTitle { get; set; }

    bool DisplayAsInput { get; }

    bool IsRequired { get; }

    bool HideSurroundingHtml { get; }
}

// sample implementation of IFieldAnswerModel
public class TextAreaFieldAnswer : FieldAnswerModelBase<TextAreaDisplayerOptions>
{
    public string Answer { get; set; }
}

EditorTemplates/SingleRowFieldAnswerForm.cshtml:

@helper DisplayerOrEditor(IFieldAnswerModel answer)
{
    var templateName = "FieldAnswers/" + answer.GetType().Name;
    var htmlFieldName = string.Format("Answers[{0}]", answer.FieldId);

    if (answer.DisplayAsInput)
    {
        @Html.EditorFor(m => answer, templateName, htmlFieldName)
        // This will display validation messages that apply to the entire answer. 
        // This typically means that the input got past client-side validation and
        // was caught on the server instead.
        // Each answer's view must also produce a validation message for
        // its individual properties if you want client-side validation to be
        // enabled.
        @Html.ValidationMessage(htmlFieldName)
    }
    else
    {
        @Html.DisplayFor(m => answer, templateName, htmlFieldName)
    }
}
<div class="form-section">
    <table class="form-table">
        <tbody>
            @{
                foreach (var answer in Model.FieldAnswers)
                {
                    if (answer.HideSurroundingHtml)
                    {
                        @DisplayerOrEditor(answer)
                    }
                    else
                    {
                        var labelClass = answer.IsRequired ? "form-label required" : "form-label";
                        <tr>
                            <td class="@labelClass">
                                @answer.FieldTitle:
                            </td>
                            <td class="form-field">
                                <div>
                                    @DisplayerOrEditor(answer)
                                </div>
                            </td>
                        </tr>
                    }
                }
            }
        </tbody>
    </table>
</div>

所以我用一系列答案模型填充我的SingleRowFieldAnswerForm。每个答案模型类型都有自己的编辑器模板,允许我自定义不同类型的动态“属性”应如何显示。例如:

// EditorTemplates/FieldAnswers/TextAreaFieldAnswer.cshtml

@model TextAreaFieldAnswer

@{
    var htmlAttributes = Html.GetUnobtrusiveValidationAttributes("Answer", ViewData.ModelMetadata);
    // add custom classes that you want to apply to your inputs.
    htmlAttributes.Add("class", "multi-line input-field");
}
@Html.TextAreaFor(m => m.Answer, Model.Options.Rows, 0, htmlAttributes)
@Html.ValidationMessage("Answer")

下一个棘手的部分是,当您将此信息发送到服务器时,它本身并不知道要构造哪种类型的IFieldAnswerModel,因此您不能只在参数列表中绑定SingleRowAnswerForm。相反,您必须这样做:

public ActionResult SaveForm(int formId)
{
    SingleRowAnswerForm form = GetForm(formId);
    foreach (var fieldAnswerModel in form.FieldAnswers.Where(a => a.DisplayAsInput))
    {
        // Updating this as a dynamic makes sure all the properties are bound regardless
        // of the runtime type (since UpdateModel relies on the generic type normally).
        this.TryUpdateModel((dynamic) fieldAnswerModel, 
            string.Format("Answers[{1}]", fieldAnswerModel.FieldId));
        }
    ...

由于您为 MVC 提供了要绑定的每个动态“属性”值,因此它可以毫无困难地绑定每个答案类型的每个属性。

显然我省略了很多细节,比如如何首先生成答案模型,但希望这能让你走上正轨。

【讨论】:

  • 谢谢,这是一个非常好的方法。我希望能够利用模型元数据基础设施。您的 IFieldAnswerModel 中有类似的元数据,您可以在 HideSurroundingHtml 和 IsRequired 等模型元数据中找到(模型元数据有很多)。使用 modelmetadata 的优点是您可以依靠内置的编辑器模板自动为您完成一些工作 - 例如,如果正在呈现的属性指定了 modelmetadata.EditFormatString,则 MVC 模板引擎将自动应用该格式字符串而无需您写代码。
  • @MattMangold:是的。就我而言,我需要能够为每个答案字段提供特定的配置,并为每个字段高度自定义显示,因此我必须遵循这种模式。如果您真的只需要一个用于各种动态属性的标准模板,那么您可以使用自定义 ModelMetadataProvider 和自定义 ModelBinder。
  • 当我尝试创建自定义 ModelMetadataProvider 时,我遇到了困难。我创建了自己的提供程序,但在覆盖中,我实际上无法确定模型中的哪个属性正在请求元数据,我将其归因于我的视图模型是简单数据类型的集合而不是强类型视图模型这一事实。
【解决方案2】:

您可以在 ViewModel、View 和 Controller 中使用 ViewData 属性,它是动态的,因此可以在运行时解析。

【讨论】:

  • 我仍然想使用编辑器模板动态呈现我的视图。我正在寻找一种方法来使用像 ViewData 这样的松散类型结构来做到这一点
猜你喜欢
  • 2012-05-12
  • 2012-11-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多