在Foreach或For循环中使用EditorFor(ASP.NET MVC + RAZOR)

时间:2016-04-02 15:41:57

标签: c# asp.net asp.net-mvc razor telerik

我目前正在ASP.NET MVC项目中实现 Family Tree 系统。为了设置家庭成员之间的关系,我需要每行显示两个ComboBox / DropDownList来定义从一个成员到另一个成员的关系。

首先,我将分享我的代码,然后我会解释到目前为止我尝试过的方法以及最后的结果。

视图模型

public class FamilyTreeRelationshipViewModel
{

    [ScaffoldColumn(false)]
    public string FromMemberId { get; set; }

    [ScaffoldColumn(false)]
    public string ToMemberId { get; set; }


    public Member FromMember { get; set; }
    public IEnumerable<Member> ToMembers { get; set; }


    [UIHint("FTComboBox")]
    [AdditionalMetadata("BindTo", "relationships")]
    [Required]
    [Display(Name = "From Relationship")]
    public string FromRelationship { get; set; }


    [UIHint("FTComboBox")]
    [AdditionalMetadata("BindTo", "relationships")]
    [Required]
    [Display(Name = "To Relationship")]
    public string ToRelationship { get; set; }
}

控制器

public class FamilyTreeController : Controller
{
    private AppMVC db = new AppMVC();


    public ActionResult Index(Guid? cid, Guid? mid)
    {

        if (cid == null && mid == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.NotFound);
        }


        var frommember = db.Member.FirstOrDefault(x => x.MemberId == mid && x.CaseId == cid && x.Deleted == false);
        var tomembers = db.Member.Where(x => x.CaseId == cid && x.MemberId != mid.Value && x.Deleted == false).ToList();


        ViewBag.cid = cid;
        ViewBag.mid = mid;

        PopulateRelationship();


        var familyTreeRelationshipViewModel = new FamilyTreeRelationshipViewModel
        {
            FromMember = frommember,
            ToMembers = tomembers,
        };

        return View(familyTreeRelationshipViewModel);
    }



    public void PopulateRelationship()
    {
        var relationship = db.RelationshipDD
        .Where(c => c.Deleted == false && c.Code != "PA")
        .OrderBy(c => c.OrderIndex)
        .Select(c => new RelationshipDDViewModel
        {
            Code = c.Code,
            Definition = c.Definition
        })
        .ToList();

        ViewData["relationships"] = relationship;

    }

}

ComboBox的编辑器模板

@model object

@{
    var bindto = ViewData.ModelMetadata.AdditionalValues["BindTo"].ToString();
    var fieldname = ViewData.ModelMetadata.PropertyName;
    var prefix = ViewData.TemplateInfo.HtmlFieldPrefix;

}

@(Html.Kendo().ComboBoxFor(m => m)
          .Filter("contains")
          .Placeholder(ViewData.ModelMetadata.DisplayName)
          .DataTextField("Definition")
          .DataValueField("Code")
          .HtmlAttributes(new { style = "width: 100%", Id = prefix})
          .BindTo((System.Collections.IEnumerable)ViewData[bindto])

          )

@Html.ValidationMessageFor(m => m, "", new { @class = "mdc-text-red-400" })

为了向每一行显示新结果,我在视图中使用了 foreach

@if (Model.ToMembers != null)
{
    if (Model.ToMembers.Any())
    {
        foreach (var othermembers in Model.ToMembers.OrderBy(x => x.MemberNumberSuffix))
        {
            @Html.EditorFor(m => m.ToRelationship)
            @Html.EditorFor(m => m.FromRelationship)
        }
    }
}

正如您在屏幕截图中看到的那样,只渲染了第一行中的ComboBox。我假设这是因为每个ComboBox的控制ID相同。我检查了浏览器开发人员工具(F12),他们都有相同的ID。 ForEach

后来我以为我应该使用For而不是Foreach,看看会发生什么:

@if (Model.ToMembers != null)
{
    if (Model.ToMembers.Any())
    {
        for (var i = 0; i < Model.ToMembers.Count(); i++)
        {
            var othermembers = Model.ToMembers.ToList()[i];

            @Html.EditorFor(m => m.ToRelationship[i])
            @Html.EditorFor(m => m.FromRelationship[i])
        }
    }
}

正如您在屏幕截图中看到的那样,所有ComboBox都已消失,所有内容都已呈现为 Char 。这里唯一的区别是每个控件/输入都有它自己的Id,这很好,但并不像我期望的那样好。 For

毕竟,我决定使用内置DropDownList(MVC)来达到这个目的,结果也是如此。因为我认为Telerik控件有问题。

我甚至尝试直接在View中使用ComboBox而不是 EditorFor ,结果不同。每一行都是单独成功渲染的,但它又是Char类型,甚至错误信息都说明了这一点。通常它应该说,“从关系字段是必需的”。

@if (Model.ToMembers != null)
{
    if (Model.ToMembers.Any())
    {
        for (var i = 0; i < Model.ToMembers.Count(); i++)
        {

            @(Html.Kendo().ComboBoxFor(m => m.ToRelationship[i])
            .Filter("contains")
            .Placeholder(ViewData.ModelMetadata.DisplayName)
            .DataTextField("Definition")
            .DataValueField("Code")
            .HtmlAttributes(new { style = "width: 100%" })
            .BindTo((System.Collections.IEnumerable)ViewData["relationships"])
            )

            @(Html.Kendo().ComboBoxFor(m => m.FromRelationship[i])
            .Filter("contains")
            .Placeholder(ViewData.ModelMetadata.DisplayName)
            .DataTextField("Definition")
            .DataValueField("Code")
            .HtmlAttributes(new { style = "width: 100%" })
            .BindTo((System.Collections.IEnumerable)ViewData["relationships"])
            )
        }
    }
}

截图: For Direct

问题

  1. 为什么我不能为此目的使用EditorFor?
  2. 为什么类型已更改为Char,我该如何解决?
  3. 任何替代方法来实现这一目标?
  4. 提前感谢您的帮助!

3 个答案:

答案 0 :(得分:2)

  1. 您可以但您需要将ViewData传递给子模板才能在子视图中访问它,因为我的理解是Viewdata不是全局可用的(对请求是全局的)对象,它仅对当前视图有效(除非传入,否则不会显示任何子视图)

  2. 我认为这里的问题是你的视图模型,你似乎试图将所有行的下拉列表绑定到根视图模型中的同一属性,但我相信你可能更需要做的是在根模型的ToMembers属性中的每个成员上设置关系值,例如...

  3. 作为一个只有seudocode的例子,感觉就像而不是......

    foreach(var child in model.members)
    {
        dropdownfor(m => m.FromRelationship, ViewData["relationships"]);
        dropdownfor(m => m.ToRelationship, ViewData["relationships"]);
    }
    

    ...你应该通过建立设置属性的下拉菜单来设置两个人之间的关系,比如......

    foreach(var child in model.members)
    {
        dropdownfor(m => child.FromRelationship, ViewData["relationships"]);
        dropdownfor(m => child.ToRelationship, ViewData["relationships"]);
    }
    

    ......以下是真实的......

    1. 第一个参数是您在下拉列表中设置为所选项目值的属性
    2. 第二个参数是您似乎存储在viewdata对象中的“关系类型”列表
    3. 这存在于根视图中,而不是某些模板/子视图
    4. ...

      1. 我们如何才能达到预期的效果呢?
      2. 我们可以确保我们正在设置正确的事情开头,我认为你真正想要的是一个看起来像这样的根模型......

        public class FamilyTreeRelationshipViewModel
        {
            public IEnumerable<MemberPair> FamilyMemberPairs{ get; set; }
            public IEnumerable<Relationship> Relationships { get;set; }
        }
        

        ...从那里为每个成员你可以像这样渲染你的行......

        @foreach(var pair in Model.MemberPairs)
        {
            @* Template out a member pair, pass through the set of relationships *@
            @Html.EditorFor(m => pair, Relationships)
        }
        

        ..好的,从这里你需要一个名为“MemberPair.cshtml”的EditorTemplates文件夹中的一个视图,它将模板化一个成员对象,该对象会像这样... ...

        class MemberPair
        {
           public Member From { get;set; }
           public Member To { get;set; }
           public int FromRelationshipId {get;set;}
           public int ToRelationshipId {get;set;}
        }
        

        ...这个viewmodel定义了一对家庭成员,你想要定义一个家庭成员之间的关系,我们将使用我们将在视图中生成的下拉列表来定义关系。这......

        @Html.DisplayFor(m => m.From) 
        
        @(Html.Kendo().ComboBoxFor(m => m.FromRelationshipId)
                    .DataTextField("Definition")
                    .DataValueField("Code")
                    .DataSource((System.Collections.IEnumerable)ViewData["Relationships"])
                    )
        
        @(Html.Kendo().ComboBoxFor(m => m.ToRelationshipId)
                    .DataTextField("Definition")
                    .DataValueField("Code")
                    .DataSource((System.Collections.IEnumerable)ViewData["Relationships"])
                    )
        
        @Html.DisplayFor(m => m.To)
        

        在此上下文中,关系集合可以从viewdata获得,因为我们在上一个视图中明确地传递了它。

        我们还可以在这里注意到,当我们绑定下拉列表时,我们以一种在单个关系对象对象上设置单个int关系id的方式进行绑定。

        我认为你上面错的地方是你试图将所有下拉列表绑定到父模型上的相同int值,我不知道MVC是否允许/适应,因为这会导致字段ID冲突在客户端。

        免责声明:

        这个答案描述了实现背后的理论,但不是您可能需要的确切代码,这也需要在控制器中进行一些逻辑更改,以管理这些视图的模型构造。

        花点时间考虑构建和构建具有较小可管理组件的解决方案所需的结构。

        这感觉就像是一个分层的问题,而不是你可以在一个视图中实现的东西,你也可以考虑使用一个kendo网格来进行绑定,因为它可以为你找出这些东西,但我不想要建议使用复杂的控件来替换复杂的绑定问题。

答案 1 :(得分:2)

首先,解释你的错误。您无法使用foreach循环为集合生成form控件。它生成重复的name属性,这些属性无法绑定回集合(以及无效的html的重复id属性)。您需要使用for循环,或者更好的自定义EditorTemplate。第二个错误(当你使用for循环时)是因为你绑定的属性是string,所以绑定到m => m.ToRelationship[i]绑定到一个字符(typeof Charstring

真正的问题是您的视图模型不正确,与您在视图中显示/编辑的内容没有任何关系,您需要将下拉列表绑定到集合中的属性。

此外,您对ToRelationshipFromRelationship使用2个组合框会导致很多潜在错误,您应该重新定义Relationship表以包含(说)以下字段

ID, Relationship, MaleReverseRelationship, FemaleReverseRelationship

所以典型的行可能包含

1, Son, Father, Mother
2, Wife, Husband, NULL
3, Niece, Uncle, Aunt

然后你需要一个下拉列表,如果杰克与罗杰的关系是Son,你知道反向关系是Father(假设你的Member模型有一个Gender字段{1}})。这将极大地简化您的视图模型,使UI更简单,并在用户选择错误的反向关系时防止用户错误。

您尚未提供有关其他表/型号的任何信息,但您需要一个带有字段的MemberRelationShip

ID, Member (FK to Members), OtherMember(FK to Members), RelationShip(FK to Relationships).

然后,您需要2个视图模型,一个用于父Member,另一个用于创建关系

public class ParentMemberVM
{
    public int ID { get; set; }
    public string Name { get; set; }
    .... other properties to display
    public IEnumerable<MemberVM> Members { get; set; }
    public IEnumerable<SelectListItem> RelationshipList { get; set; }
}
public class MemberVM
{
    public int ID { get; set; }
    public string Name { get; set; }
    public int Relationship { get; set; }
}

然后,EditorTemplate

需要MemberVM

/Views/Shared/EditorTemplates/MemberVM.cshtml

@model MemberVM
@Html.HiddenFor(m => m.ID)
@Html.DisplayFor(m => m.Name)
@Html.DropDownListFor(m => m.Relationship, (IEnumerable<SelectListItem>)ViewData["relationships"])

并在主视图中

@model ParentMemberVM
....
@using (Html.BeginForm())
{
    @Html.HiddenFor(m => m.ID)
    @Html.DislayFor(m => m.Name)
    @Html.EditorFor(m => m.Members, new { relationships = Model.RelationshipList })
    <input type="submit" value="Save" />
}

答案 2 :(得分:-5)

  1. 作为替代解决方案,您可能根本不使用HTML帮助程序并编写自己的HTML。使用HTML帮助程序会导致诸如您遇到的问题。你可以克服它们,但是如果不是帮助你,那么使用HTML助手会产生什么困难呢? (在我看来,MVC是关于控制你为浏览器提供的前端代码。)
  2. 使用Razor,直接编写所需的HTML可以比让HTML帮助者为您做到这一点更有效。

    当然,这是我的观点。我在一些类似的问题here上详细介绍了一下。