【问题标题】:Add related entities with ASP.NET MVC and Razor?使用 ASP.NET MVC 和 Razor 添加相关实体?
【发布时间】:2015-03-29 02:03:33
【问题描述】:

我有一个Person 类,它有一个导航属性ICollection<Address> AddressesAddress 实体有一个属性 City

我想为用户提供创建/更新 Person 及其地址的选项,包括添加和删除地址。

因此,为了显示地址,我只需调用@Html.EditorFor(m => m.Addresses),剃须刀引擎会使用我位于 EditorTemplates 文件夹中的自定义模板来处理集合,为每个字段生成一个匹配的签名,如 @ 987654330@.
到目前为止一切顺利。

问题是添加新的Addresses。
我创建了一个按钮,当单击该按钮时,一个 jQuery 函数会调用一个动作(通过自定义 EditorTemplate 呈现),但它的字段没有上述签名,而只有id="City" name="City",因此, 在 post 操作中未被识别为 Person 实体的一部分。

如何使用正确的签名生成 idname 字段?

我已阅读 this 文章和许多其他文章,但没有找到解决 ids 和 names 问题的文章。

【问题讨论】:

  • 显示herehere 的几个用于动态添加新对象的选项。使用BeginCollectionItem 意味着每次调用服务器,但更易于维护。使用模板意味着它的所有客户端都完成了,但是如果你的类发生变化,你需要更新模板,如果你也有验证属性,可能会变得混乱。
  • 我不介意为此使用服务器,但我不想涉及额外的 NuGet 包。我有一种简单的方法可以通过服务器或 jQ(不查询数据库)来维护它吗?
  • 所以您想只是通过该 jquery 点击添加/更新Address 部分,而不是整个Person 实体?
  • 这是整个Person 实体。我的问题是nameid属性应该赋予新添加的行,以及是否有一种简单的方法来生成它。
  • 如果没有 jquery 或使用 BeginCollectionItem 帮助器,除了将模型回发到一个方法,然后将一个新项目添加到您的集合然后返回视图之外,真的别无选择,所以现有的和新添加的项目在名称属性中使用索引器在循环中正确呈现。

标签: c# asp.net-mvc razor asp.net-mvc-5 one-to-many


【解决方案1】:

根据关于 cmets 的建议,我最终使用了以下内容。

无论如何,我必须将新项目包装为集合,并且隐藏字段只是附加在集合项目之后,而不是被注入(因为在移除时它会保留在那里),这让我很困扰。

所以我最终添加了以下扩展,以用于 Razor cshtml 文件以及向集合添加新项目时调用的操作:

这里是扩展(还有一些重载,请看完整代码here):

private static string EditorForManyInternal<TModel, TValue>(HtmlHelper<TModel> html, Expression<Func<TModel, IEnumerable<TValue>>> expression, IEnumerable<TValue> collection, string templateName)
{
  var sb = new StringBuilder();

  var prefix = html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix;
  var htmlFieldName = (prefix.Length > 0 ? (prefix + ".") : String.Empty) + ExpressionHelper.GetExpressionText(expression);

  var items = collection ?? expression.Compile()(html.ViewData.Model);
  foreach (var item in items)
  {
    var guid = Guid.NewGuid().ToString();

    var dummy = new { Item = item };
    var memberExp = Expression.MakeMemberAccess(Expression.Constant(dummy), dummy.GetType().GetProperty("Item"));
    var singleItemExp = Expression.Lambda<Func<TModel, TValue>>(memberExp, expression.Parameters);

    var editor = html.EditorFor(singleItemExp, templateName, string.Format("{0}[{1}]", htmlFieldName, guid));
    var hidden = String.Format(@"<input type='hidden' name='{0}.Index' value='{1}' />", htmlFieldName, guid);

    var eNode = HtmlNode.CreateNode(editor.ToHtmlString().Trim());
    if (eNode is HtmlTextNode)
      throw new InvalidOperationException("Unsuported element.");

    if (eNode.GetAttributeValue("id", "") == "")
      eNode.SetAttributeValue("id", guid);

    var hNode = HtmlNode.CreateNode(hidden);
    eNode.AppendChild(hNode);
    sb.Append(eNode.OuterHtml);
  }

  return sb.ToString();
}

public static MvcHtmlString EditorForMany<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, IEnumerable<TValue>>> expression, string templateName)
{
  var value = EditorForManyInternal(html, expression, null, templateName);
  return new MvcHtmlString(value);
}

视图中的用法:

<div>
  <h4>@Resources.Person.Children</h4>
  <ul id="patientChildren" class="list-group ajax-collection">
    @Html.EditorForMany(m => m.Children)
  </ul>

  @Ajax.ActionLink("Create Child", "CreateChild", new { patientId = Model.Id, lastName = Model.LastName }, new AjaxOptions { UpdateTargetId = "patientChildren", InsertionMode = InsertionMode.InsertAfter, OnSuccess = CommonData.AjaxOnSuccessJsFuncName }, new { @class = "button btn-default" })
</div>

这里是被调用的 ajax 函数(重要的是将生成的项目归类为 ajax-collection-item 并有一个删除按钮归类为 btn remove):

//#region Ajax add and remove
var ajaxCollectionItemSelector = '.ajax-collection-item';
function attachAjaxRemoveHandlers(id) {
  var context = $(id ? '#' + id : ajaxCollectionItemSelector);

  var removeButton = context.find('.btn.remove');

  removeButton.click(function () {
    var button = $(this);
    var collectionItem = button.closest(ajaxCollectionItemSelector);
    collectionItem.remove();
  });
};

function ajaxOnSuccess(ajaxContext) {
  var collectionItem = $(ajaxContext);
  var id = collectionItem.prop('id');
  attachAjaxRemoveHandlers(id);
  //TODO: following line doesn't work
  collectionItem.find(':text:first-of-type').focus();
};

function runCommonScripts() {
  attachAjaxRemoveHandlers();
};
//#endregion Ajax add and remove

新的项目操作 (CreateChild) 如下所示(EditorForSingle 扩展位于 the same place

public ContentResult CreateChild(int patientId, string lastName)
{
  return this.EditorForSingle((Patient p) => p.Children, 
    new PatientChild
    { 
      PatientId = patientId, 
      LastName = lastName 
    });
}

【讨论】:

  • 我刚刚偶然发现了同样的问题,但我需要在实现中具有更大的灵活性,因为我正在处理必须包含多个不同项目列表且布局略有不同的向导。因此,我为现在在 NuGet 上可用的列表创建了一个简单的模板引擎:dynamic-vml.github.io
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多