我目前正在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;
}
}
@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。
后来我以为我应该使用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,这很好,但并不像我期望的那样好。
毕竟,我决定使用内置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"])
)
}
}
}
提前感谢您的帮助!
答案 0 :(得分:2)
您可以但您需要将ViewData传递给子模板才能在子视图中访问它,因为我的理解是Viewdata不是全局可用的(对请求是全局的)对象,它仅对当前视图有效(除非传入,否则不会显示任何子视图)
我认为这里的问题是你的视图模型,你似乎试图将所有行的下拉列表绑定到根视图模型中的同一属性,但我相信你可能更需要做的是在根模型的ToMembers属性中的每个成员上设置关系值,例如...
作为一个只有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"]);
}
......以下是真实的......
...
我们可以确保我们正在设置正确的事情开头,我认为你真正想要的是一个看起来像这样的根模型......
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 Char
)string
。
真正的问题是您的视图模型不正确,与您在视图中显示/编辑的内容没有任何关系,您需要将下拉列表绑定到集合中的属性。
此外,您对ToRelationship
和FromRelationship
使用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)
使用Razor,直接编写所需的HTML可以比让HTML帮助者为您做到这一点更有效。
当然,这是我的观点。我在一些类似的问题here上详细介绍了一下。