简体   繁体   English

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

[英]Using EditorFor in Foreach or For loop (ASP.NET MVC + RAZOR)

I'm currently implementing a Family Tree system in my ASP.NET MVC project. 我目前正在ASP.NET MVC项目中实现Family Tree系统。 In order to set the relationship between family members, I need to display two ComboBox/DropDownList per row to define relationships from one member to the other. 为了设置族成员之间的关系,我需要每行显示两个ComboBox / DropDownList来定义从一个成员到另一个成员的关系。

First I will share my codes and then I will explain what ways I've tried so far and what was the result at the end. 首先,我将分享我的代码,然后我将解释我到目前为止尝试过的方式以及最后的结果。

ViewModel 视图模型

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; }
}

Controller 调节器

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;

    }

}

Editor Template for ComboBox 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" })

In order to show each row a new result, I used foreach in View: 为了向每一行显示一个新结果,我在View中使用了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)
        }
    }
}

As you see below in the screenshot, only ComboBoxes in the first row were rendered. 正如您在屏幕截图中看到的那样,只渲染了第一行中的ComboBox。 I assume it's because of the same control Id for each ComboBox. 我假设这是因为每个ComboBox的控制ID相同。 I checked the browser developer tools (F12), all of them had the same Id. 我检查了浏览器开发人员工具(F12),他们都有相同的ID。 的ForEach

Later I thought I should use For instead of Foreach and see what happens: 后来我以为我应该用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])
        }
    }
}

As you see below in the screenshot, all of the ComboBoxes are gone and everything has been rendered as Char . 正如您在屏幕截图中看到的那样,所有ComboBox都已消失,所有内容都被渲染为Char The only difference here is that each control/input has it's own Id and that's good but not good as I was expecting. 这里唯一的区别是每个控件/输入都有它自己的Id,这很好,但并不像我期望的那样好。 对于

After all, I decided to use the Built-in DropDownList (MVC) for this purpose and it was the same result. 毕竟,我决定使用内置DropDownList(MVC)来达到这个目的,结果也是如此。 Because I thought something is wrong with Telerik controls. 因为我认为Telerik控件有问题。

I even tried to use the ComboBox directly inside the View instead of EditorFor , the result was different. 我甚至试图直接在View中使用ComboBox而不是EditorFor ,结果是不同的。 Each row was rendered separately and successfully but it was again Char type and even the error message says that. 每一行都是单独成功渲染的,但它又是Char类型,甚至错误信息都说明了这一点。 Normally it should say, "From relationship field is required". 通常它应该说,“从关系字段是必需的”。

@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"])
            )
        }
    }
}

Screenshot: 截图: 直接


Questions 问题

  1. Why can't I use EditorFor for this purpose? 为什么我不能为此目的使用EditorFor?
  2. Why the type has changed to Char and how can I fix this? 为什么类型已更改为Char,我该如何解决?
  3. Any alternative way to achieve this? 任何替代方法来实现这一目标?

Thanks in advance for your help! 在此先感谢您的帮助!

  1. You can but you will need to pass the ViewData in to the child template to have access to it in the child view as my understanding is that Viewdata is not a globally available (global to the request anyway) object, it's only valid for the current view (and not any child views unless passed in) 您可以但是您需要将ViewData传递给子模板才能在子视图中访问它,因为我的理解是Viewdata不是全局可用的(对请求是全局的)对象,它只对当前有效view(除非传入,否则不会显示任何子视图)

  2. I think the problem here is your view model, you appear to be trying to bind the drop down on all rows to the same property in the root view model, but what I believe you likely more need to do is set relationship values on each Member in the ToMembers property on your root model for example ... 我认为这里的问题是你的视图模型,你似乎试图将所有行的下拉绑定到根视图模型中的同一属性,但我相信你可能更需要做的是在每个成员上设置关系值在根模型的ToMembers属性中,例如...

As a seudocode only example it feels like instead of ... 作为一个只有seudocode的例子,感觉就像而不是......

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

... you should be setting the relationship between 2 people by building dropdowns that set their properties, something like ... ...你应该通过建立设置属性的下拉菜单来设置两个人之间的关系,比如......

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

... where the following is true ... ......以下是真实的......

  1. the first param is the property that you are setting to the value of the selected item in the dropdown list 第一个参数是您在下拉列表中设置所选项的值的属性
  2. the second param is the list of "relationship types" you appear to be storing in the viewdata object 第二个参数是您似乎存储在viewdata对象中的“关系类型”列表
  3. this exists in the root view not some template / child view 这存在于根视图中,而不是某些模板/子视图

.... ....

  1. How can we achieve the desired result? 我们如何才能达到预期的效果?

Well we can ensure we are setting the right bits of the right things to start off with, I think therefore that what you really want is a root model that looks something like this ... 好吧,我们可以确保我们正在设置正确的东西开始,我认为你真正想要的是一个看起来像这样的根模型......

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

... from there for each member you can render your rows like this ... ...从那里为每个成员你可以像这样呈现你的行......

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

.. ok from here you will need a view in the EditorTemplates folder called "MemberPair.cshtml" which will template a member pair object, that object will loook like this ... ..好的,从这里你需要一个名为“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;}
}

... this viewmodel defines a pair of family members that you want to define a relationship between, the from and to family members hould be set and the relationships we will define using the dropdowns we are going to generate in the view like this ... ...这个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)

In this context the relationship collection is available from viewdata because we explicitly passed it through from the parent in the previous view. 在此上下文中,关系集合可以从viewdata获得,因为我们在前一个视图中显式地传递了它。

We can also note here that when we bind our dropdowns we are binding in a way that will set a single int relationship id on a single relationship pair object. 我们还可以在这里注意到,当我们绑定下拉列表时,我们以一种在单个关系对对象上设置单个int关系id的方式进行绑定。

Where I believe you went wrong above is that you were trying to bind all of the dropdowns to the same int value on the parent model which I don't know if MVC will allow / cater for as this can cause field id conflicts on the client. 我认为你上面出错的地方是你试图将所有下拉列表绑定到父模型上的相同int值,我不知道MVC是否允许/适应,因为这会导致客户端上的字段ID冲突。

Disclaimer: 免责声明:

This answer describes the theory behind your implementation but is not the exact code you may need, and this will also require some logic change in the controller to manage the construction of the models for these views. 这个答案描述了实现背后的理论,但不是您可能需要的确切代码,这也需要在控制器中进行一些逻辑更改来管理这些视图的模型构造。

Take your time to think about the structure you need to build and construct the solution with smaller manageable component parts. 花点时间考虑构建和构建具有较小可管理组件的解决方案所需的结构。

This feels like a layered problem rather than something you can achieve in a single view, you could also consider using a kendo grid to do your binding as it would be able to figure this stuff out for you but i didn't want to suggest using a complex control to replace a simple combo binding issue with a complex binding one. 这感觉就像一个分层的问题,而不是你可以在一个视图中实现的东西,你也可以考虑使用一个kendo网格来做你的绑定,因为它能够为你找出这些东西,但我不想建议使用一个复杂的控件,用复杂的绑定问题替换一个简单的组合绑定问题。

First, to explain your errors. 首先,解释你的错误。 You cannot use a foreach loop to generate form controls for a collection. 您不能使用foreach循环为集合生成form控件。 It generates duplicate name attributes which cannot bind back to a collection (and duplicate id attributes which is invalid html). 它生成重复的name属性,这些属性无法绑定回集合(以及无效的html的重复id属性)。 You need to use a for loop, or better a custom EditorTemplate . 您需要使用for循环,或者更好的自定义EditorTemplate You second error (when you do use a for loop) is because the property your binding to is string , so binding to m => m.ToRelationship[i] is binding to a character (typeof Char ) in the string . 第二个错误(当你使用for循环时)是因为你绑定的属性是string ,所以绑定到m => m.ToRelationship[i]绑定到string的字符(typeof Char )。

The real issue is that your view model is incorrect and has no relationship to what your displaying/editing in the view, and you need to bind the dropdownlists to a property in a collection. 真正的问题是您的视图模型不正确,与您在视图中显示/编辑的内容没有关系,您需要将下拉列表绑定到集合中的属性。

In addition, your use of 2 comboboxes for the ToRelationship and FromRelationship is going to lead to a lot of potential errors, and you should be redefining your Relationship table to include (say) the following fields 此外,您对ToRelationshipFromRelationship使用2个组合框将导致许多潜在错误,您应该重新定义您的Relationship表以包括(比方说)以下字段

ID, Relationship, MaleReverseRelationship, FemaleReverseRelationship

so a typical rows might contain 所以典型的行可能包含

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

then you need a single dropdownlist and if the relationship of Jack to Roger is Son , you know that the reverse relationship is Father (assuming your Member model has a field for Gender ). 然后你需要一个下拉列表,如果杰克与罗杰的关系是Son ,你知道反向关系是Father (假设你的Member模型有一个Gender字段)。 This will greatly simplify your view model, make the UI simpler and prevent user errors if the user selects the wrong reverse relationship. 这将极大地简化您的视图模型,使UI更简单,并在用户选择错误的反向关系时防止用户错误。

You have not provided any information on your other tables/models, but you would need a MemberRelationShip table with fields 您没有提供有关其他表/模型的任何信息,但是您需要一个包含字段的MemberRelationShip

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

You then needs 2 view models, one for the parent Member and one to create the 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; }
}

Then you need an EditorTemplate for MemberVM 然后,你需要一个EditorTemplateMemberVM

/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"])

and in the main view 并在主视图中

@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" />
}
  1. As an alternate solution you may simply not use HTML helpers and write your own HTML. 作为替代解决方案,您可能根本不使用HTML帮助程序并编写自己的HTML。 Using HTML helpers leads to issues such as those you are encountering. 使用HTML帮助程序会导致诸如您遇到的问题。 You may overcome them, but what is the point using HTML helpers if instead of helping you, they create difficulties? 你可以克服它们,但是如果不是帮助你,那么使用HTML助手会产生什么困难呢? (In my point of view, MVC is about having control over the front code you serve to browsers.) (在我看来,MVC是关于控制你为浏览器提供的前端代码。)

With Razor, writing directly the required HTML can prove more effective than letting HTML helpers do that for you. 使用Razor,直接编写所需的HTML可以比让HTML帮助程序为您执行此操作更有效。

Granted, this is my point of view. 当然,这是我的观点。 I have detailed it a bit more on some similar question here . 我在这里对一些类似的问题进行详细介绍。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM