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

本文介绍了在ASP.NET MVC中如何处理并提交包含List或ICollection的模型数据,尤其是当数据项数量不固定的情况。通过Html.BeginCollectionItem方法,可以方便地解决模型绑定的难题,适用于需要动态添加步骤或条目的业务场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景说明

在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项目有所帮助,欢迎大家一起讨论和指正。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值