您的位置:首页 > 编程语言 > ASP

如何在ASP.NET MVC中实现提交若干个某模型的数据(某Model的List或ICollection,大小不定)

2014-12-14 13:21 696 查看

背景说明

在ASP.NET MVC中,有一个我们经常使用且十分好用的功能——模型绑定。

即在页面中指定该页面将会使用到的数据模型Model,然后在“显示数据”或“提交数据”时,就可以很方便的获取数据内容。

通常情况下这两种场景都比较容易实现,特别是“显示数据”时,不管绑定的模型如何复杂都可以轻松的显示所有你想要的数据。

但是,“提交数据”时,就不一定那么简单了。特别是当模型中包含一个List或者ICollection数据(即实现集合类型接口的数据),且该数据中包含的item数量不确定时,情况就变的有些棘手了。

问题描述

下面举一个这种情况的例子

假如要给一个系统录入一个某业务的操作说明页面,页面包含如下内容:1). 标题 2). 业务介绍 3). 操作步骤(录入前不确定多少步)

解决方案

因此,这个页面对应着如下模型 BusinessHelp

public class BusinessHelp
{
public string BusinessTitle{ get; set; }

public string BusinessDescription{ get; set; }

public virtual ICollection<StepDetail> StepDetails { get; set; }

}


在录入页面AddBusinessHelp.cshtml中,主要内容如下:

@model HospitalProject.Models.AdminModel.BusinessHelp

@using (Html.BeginForm("ActionName","ControllerName", FormMethod.Post))
{
@Html.TextBoxFor(m => m.BusinessTitle)
@Html.TextAreaFor(m => m.BusinessDescription)
<div id="movieEditor">
@foreach (StepDetail step in Model.StepDetails)
{
Html.RenderPartial("StepDetailItem", step);
}
</div>
<button id="#addAnother">添加步骤</button>
}


其中StepDetailItem的PartialView如下:

@model HospitalProject.Models.StepDetail

@using (Html.BeginCollectionItem("StepDetails"))
{
@Html.TextBoxFor(m => m.Title)
@Html.TextAreaFor(m => m.Content)
}


对应这个视图的Action中的代码如下:

private static BusinessHelp _currentBusiness;

private static BusinessHelp CurrentBusiness
{
get
{
if (_currentBusiness == null)
_currentBusiness = GetBusiness();
return _currentBusiness;
}
set
{
_currentBusiness = value;
}
}

private static BusinessHelp GetBusiness()
{
return new BusinessHelp
{
BusinessTitle = "",
BusinessDescription = "",

StepDetails = new List<StepDetail>() {
new StepDetail() { Title = "", Content = "", PageName = ""},
}
};
}

public ActionResult AddBusinessHelp()
{
return View(CurrentBusiness);
}


每次给视图传递一个“空内容”的Model。(只是实际字符串等内容为空,数据模型对象并不为null)

这样每次进行业务帮助页面的录入时,都会初始化第一步。后续再想添加第二部、第三部 ......都可以通过js添加,并绑定到模型(稍后提到~)

在AddBusinessHelp.cshtml中 添加如下js代码:

$(function () {
$("#addAnother").click(function () {
$.get('/Help/StepEntryRow?count='+(++count), function (template) {
$("#movieEditor").append(template);
});
});
}


其中StepEntryRow Action代码如下:
public ActionResult StepEntryRow(int count = -1)
{
if (count != -1)
{
ViewBag.StepNumber = count;
StepDetail sd = new StepDetail() { Title = "", Content = "", Type = Models.EnumModel.StepType.Detail };
return PartialView("StepDetailItem", sd);
}
else
return View("Error");
}


这样就基本完成了该功能的实现了。

下面贴出上述代码中的核心部分—— Html.BeginCollectionItem("StepDetails")

public static class CollectionEditingHtmlExtensions
{
/// <summary>
/// Begins a collection item by inserting either a previously used .Index hidden field value for it or a new one.
/// </summary>
/// <typeparam name="TModel"></typeparam>
/// <param name="html"></param>
/// <param name="collectionName">The name of the collection property from the Model that owns this item.</param>
/// <returns></returns>
public static IDisposable BeginCollectionItem<TModel>(this HtmlHelper<TModel> html, string collectionName)
{
if (String.IsNullOrEmpty(collectionName))
throw new ArgumentException("collectionName is null or empty.", "collectionName");

string collectionIndexFieldName = String.Format("{0}.Index", collectionName);

string itemIndex = null;
if (html.ViewData.ContainsKey(JQueryTemplatingEnabledKey))
{
itemIndex = "${index}";
}
else
{
itemIndex = GetCollectionItemIndex(collectionIndexFieldName);
}

string collectionItemName = String.Format("{0}[{1}]", collectionName, itemIndex);

TagBuilder indexField = new TagBuilder("input");
indexField.MergeAttributes(new Dictionary<string, string>() {
{ "name", collectionIndexFieldName },
{ "value", itemIndex },
{ "type", "hidden" },
{ "autocomplete", "off" }
});

html.ViewContext.Writer.WriteLine(indexField.ToString(TagRenderMode.SelfClosing));
return new CollectionItemNamePrefixScope(html.ViewData.TemplateInfo, collectionItemName);
}

private const string JQueryTemplatingEnabledKey = "__BeginCollectionItem_jQuery";

public static MvcHtmlString CollectionItemJQueryTemplate<TModel, TCollectionItem>(this HtmlHelper<TModel> html,
string partialViewName,
TCollectionItem modelDefaultValues)
{
ViewDataDictionary<TCollectionItem> viewData = new ViewDataDictionary<TCollectionItem>(modelDefaultValues);
viewData.Add(JQueryTemplatingEnabledKey, true);
return html.Partial(partialViewName, modelDefaultValues, viewData);
}

/// <summary>
/// Tries to reuse old .Index values from the HttpRequest in order to keep the ModelState consistent
/// across requests. If none are left returns a new one.
/// </summary>
/// <param name="collectionIndexFieldName"></param>
/// <returns>a GUID string</returns>
private static string GetCollectionItemIndex(string collectionIndexFieldName)
{
Queue<string> previousIndices = (Queue<string>)HttpContext.Current.Items[collectionIndexFieldName];
if (previousIndices == null)
{
HttpContext.Current.Items[collectionIndexFieldName] = previousIndices = new Queue<string>();

string previousIndicesValues = HttpContext.Current.Request[collectionIndexFieldName];
if (!String.IsNullOrWhiteSpace(previousIndicesValues))
{
foreach (string index in previousIndicesValues.Split(','))
previousIndices.Enqueue(index);
}
}

return previousIndices.Count > 0 ? previousIndices.Dequeue() : Guid.NewGuid().ToString();
}

private class CollectionItemNamePrefixScope : IDisposable
{
private readonly TemplateInfo _templateInfo;
private readonly string _previousPrefix;

public CollectionItemNamePrefixScope(TemplateInfo templateInfo, string collectionItemName)
{
this._templateInfo = templateInfo;

_previousPrefix = templateInfo.HtmlFieldPrefix;
templateInfo.HtmlFieldPrefix = collectionItemName;
}

public void Dispose()
{
_templateInfo.HtmlFieldPrefix = _previousPrefix;
}
}
}


这样就能很方便的解决ASP.NET MVC提交包含集合类型且数量不定的数据时存在的问题。

希望这边博客对大家使用ASP.NET MVC编写web项目有所帮助,欢迎大家一起讨论和指正。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: