MVC动态视图绑定到已知和未知属性的属性字典

时间:2018-08-15 22:08:15

标签: asp.net-mvc razor dynamic view binding

我一直在努力寻找解决方案,以解决我正在忙于设计的情况,但是我没有设法解决。

想象一下下面的模型

public enum InputType
{
  TextInput,
  LookupInput
}
public struct AdditionalProperty
{
  public string Key {get;set;}
  public string Value {get;set;}
  public InputType Type {get;set;}
}
public class Person
{
  public string FirstName {get;set;}

  public List<AdditionalProperty> AdditionalProperties {get;set;}
}

然后,具有以下控制器

public class HomeController
{
  public ActionResult Index()
  {
    var model = new Person { FirstName = "MyName" };
    model.AdditionalProperties = new List<AdditionalProperty>();

    var listItem = new AdditionalProperty
    {
      Key = "Surname",
      Value = "MySurname"
    };
    model.AdditionalProperties.Add(listItem);
    return View(model)
  }
}

我要寻找的是Razor视图代码,该代码如何“动态”创建具有正确输入类型的属性,并绑定到某些东西上,以便在表单回发后仍能够使用模型到控制器以获取保存功能。

因此已知的属性将如下所示:

<div class="form-group">
    <div class="form-row">
        <div class="col-md-6">
            @Html.LabelFor(model => model.FirstName, new { @class = "control-label" })
            <div>
                @Html.TextBoxFor(model => model.FirstName, new { @class = "form-control", placeholder = "Enter Group Name", type = "text" })
                @Html.ValidationMessageFor(model => model.FirstName, "", new { @class = "text-danger" })
            </div>
        </div>
    </div>
</div>

随后的想法是拥有以下内容。显然,下面的内容还不够,这是我需要帮助的地方。 我想显示其他属性,一个在另一个之下,每个基于属性在单独的行上(使用引导行)。InputType

@foreach (var property in Model.Properties)
{
  @Html.LabelFor(model => property.Key, new { @class = "control-label" })
  <div>
    @if (property.InputType == TextInput)
    {
      @Html.TextBoxFor(model => property.Value, new { @class = "form-control", placeholder = "Enter Group Name", type = "text" })
    }
    @Html.ValidationMessageFor(model => property.Key, "", new { @class = "text-danger" })
  </div>
}

因此,我希望将我的视图显示为:

                 | <label>     | <input>
Known Property   | FirstName   | MyFirstName
Unknown Property | Surname     | MySurname

1 个答案:

答案 0 :(得分:0)

关于完整性,我将发布以下答案。

我将发布模型,视图(索引和EditorTemplates)和控制器,以显示用于测试给出的答案的完整的工作解决方案。

此测试的“我的模型”类
Person.cs

public class Person
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public List<AdditionalProperty> AdditionalProperties { get; set; }
}

AdditionalProperty.cs

public struct AdditionalProperty
{
    public string Key { get; set; }

    public object Value { get; set; }

    public DateTime? DateValue
    {
        get
        {
            DateTime dateValue;
            if (DateTime.TryParse(Value?.ToString(), out dateValue))
            {
                return dateValue;
            }
            return null;
        }
        set => Value = value;
    }

    public InputType InputType { get; set; }

    public List<SelectListItem> ValueLookupItems { get; set; }
}

我在这里具有单独的DateValue属性的原因是,在进行DateTime绑定时可以帮助浏览器,否则DateTimePicker不会显示。

我使用一个枚举来确定此特定属性应使用的输入类型。
InputType.cs

public enum InputType
{
    TextBox,

    DropdownBox,

    TextArea,

    DateSelection,
}

为了使视图尽可能简单,Stephen为我提供了索引视图的示例以及AdditionalProperty对象的EditorTemplate。 EditorTemplate用于分离关注点,并确保使用哪种输入类型背后的所有逻辑都在一个地方。

我发现DateTime属性不能很好地工作,因此需要一个附加的EditorTemplate。我是从this post那里得到的。

DateTime.cshtml
注意:模板的位置-> / Views / Shared / EditorTemplates

@model DateTime
@{
    IDictionary<string, object> htmlAttributes;
    object objAttributes;
    if (ViewData.TryGetValue("htmlAttributes", out objAttributes))
    {
        htmlAttributes = objAttributes as IDictionary<string, object> ?? HtmlHelper.AnonymousObjectToHtmlAttributes(objAttributes);
    }
    else
    {
        htmlAttributes = new RouteValueDictionary();
    }
    htmlAttributes.Add("type", "date");
    String format = (Request.UserAgent != null && Request.UserAgent.Contains("Chrome")) ? "{0:yyyy-MM-dd}" : "{0:d}";
    @Html.TextBox("", Model, format, htmlAttributes)
}

AdditionalProperty.cshtml
注意:模板的位置-> / Views / Shared / EditorTemplates
注意:我的AdditionalProperty的位置是DynamicViewExample.Models命名空间的一部分

@model DynamicViewExample.Models.AdditionalProperty
<div>
    @Html.HiddenFor(m => m.Key)
    @Html.LabelFor(m => m.Key, Model.Key, new {@class = "control-label"})
    @if (Model.InputType == DynamicViewExample.Models.InputType.TextBox)
    {
        @Html.TextBoxFor(m => m.Value, new {@class = "form-control"})
    }
    else if (Model.InputType == DynamicViewExample.Models.InputType.TextArea) 
    {
        @Html.TextAreaFor(m => m.Value, new {@class = "form-control"})
    }
    else if (Model.InputType == DynamicViewExample.Models.InputType.DropdownBox)
    {
        @Html.DropDownListFor(m => m.Value, Model.ValueLookupItems, new {@class = "form-control"})
    }
    else if (Model.InputType == DynamicViewExample.Models.InputType.DateSelection)
    {
        @Html.EditorFor(m => m.DateValue, new {@class = "form-control"})
    }
    else
    {
        @Html.HiddenFor(m => m.Value) // we need this just in case
    }
</div

这就是 Index.cshtml 文件的外观

@model DynamicViewExample.Models.Person

@{
    ViewBag.Title = "Home Page";
}

@using (Html.BeginForm())
{
    <div class="row">
        @Html.LabelFor(model => model.FirstName, new { @class = "control-label" })
        @Html.TextBoxFor(model => model.FirstName, new { @class = "form-control", placeholder = "Enter Group Name", type = "text" })
        @Html.ValidationMessageFor(model => model.FirstName, "", new { @class = "text-danger" })
    </div>
    <div class="row">
        @Html.LabelFor(model => model.LastName, new { @class = "control-label" })
        @Html.TextBoxFor(model => model.LastName, new { @class = "form-control", placeholder = "Enter Group Name", type = "text" })
        @Html.ValidationMessageFor(model => model.LastName, "", new { @class = "text-danger" })
    </div>
    <div class="row">
        @Html.EditorFor(m => m.AdditionalProperties, new { htmlAttributes = new { @class = "form-control"}})
    </div>

    <input type="submit" class="btn btn-primary" />
}

最后, HomeController.cs 文件包含一个Get and Post(获取和发布),该文件可以根据需要操纵数据。这里缺少的是填充模型的“动态”方式,但是一旦将DB引入混合中,自然就会发生这种情况。

    [HttpGet]
    public ActionResult Index()
    {
        var model = new Person
        {
            FirstName = "Gawie",
            LastName = "Schneider",
            AdditionalProperties = new List<AdditionalProperty>
            {
                new AdditionalProperty {Key = "Identification Number", Value = "1234567890123456", InputType = InputType.TextBox},
                new AdditionalProperty {Key = "Date Of Birth", Value = DateTime.Today, InputType = InputType.DateSelection},
                new AdditionalProperty {Key = "Age", Value = "31", InputType = InputType.TextBox},
                new AdditionalProperty {Key = "Gender", Value = "Male", InputType = InputType.DropdownBox,
                    ValueLookupItems = new List<SelectListItem>
                    {
                        new SelectListItem{Text = "Male", Value = "Male"},
                        new SelectListItem{Text = "Female", Value = "Female"}
                    }},
            }
        };

        return View(model);
    }

    [HttpPost]
    public ActionResult Index(Person model)
    {
        //Do some stuff here with the model like writing it to a DB perhaps
        return RedirectToAction("Index");
    }

因此,如果我必须总结一下我在这里试图做的事情。
我想要实现的目标是能够将强类型/已知属性与动态/未知属性结合使用,以创建一个系统,该系统允许用户即时创建新输入而无需开发人员参与其中。

老实说,我希望有一天也能对别人有所帮助。

享受编码经验
高威