功能需求描述

Q:在实际的开发中,经常会遇到一个模型中包含有多个条目的表单。如何将数据提交到后台?

A: 以数组的形式提交到后台就Ok了(真的那么简单么,如果再嵌套一层呢?)

A2:拆分多个模型,映射就没啥问题了。但......有点麻烦啊~~

接下来说说如何将下面的模型提交到后台

    /// <summary>
/// 计划模型
/// </summary>
public class PlanModel
{
public int Id{ get; set; }
/// <summary>
/// 计划名称
/// </summary>
public string PlanName { get; set; }
/// <summary>
/// 描述
/// </summary>
public string Remark { get; set; }
/// <summary>
/// 方案集合
/// </summary>
public List<CaseModel> Cases { get; set; }
}
/// <summary>
/// 方案模型
/// </summary>
public class CaseModel
{
public int Id{ get; set; }
/// <summary>
/// 标题
/// </summary>
public string Title { get; set; }
/// <summary>
/// 描述
/// </summary>
public string Description { get; set; }
/// <summary>
/// 作者
/// </summary>
public string Author { get; set; } }

根据此模型,编辑的页面会如下图所示,一些基本信息加上可增可减的条目

实现效果

如何实现这个功能(asp.net mvc)

  1. 新建视图页面(略)
  2. 条目的显示增加删除

控制器代码

    public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
var model = new PlanModel() {};
return View(model);
}
public ActionResult CaseRow()
{
return View("_CaseRow", new CaseModel());
}
[HttpPost]
public ActionResult Form(PlanModel model)
{
return Json(model);
} }

编辑页条目显示代码

 <div class="form-group">
<label class="col-sm-3 control-label">计划方案:</label>
<div class="col-sm-7 ">
<table class="table table-bordered table-condensed">
<thead>
<tr class="text-center">
<th class="text-center">方案名称</th>
<th class="text-center">方案作者</th>
<th class="text-left">方案描述</th>
<th class="text-center" width="100">
<span>操作</span>
<span title="添加方案" id="add_case" class="glyphicon glyphicon-plus"></span>
</th>
</tr>
</thead>
<tbody id="case_list">
@if (Model.Cases != null)
{
foreach (var item in Model.Cases)
{
Html.RenderPartial("_CaseRow", item);
}
}
</tbody>
</table>
</div>
</div>

页面增加/删按钮js代码 + 验证

<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
<script type="text/javascript">
$(function () {
$("#case_list").delegate(".del_tr", "click", function () {
$(this).closest("tr").remove();
});
$("#add_case").click(function () {
//ajax请求返回新增方案视图代码
$.get('@Url.Action("CaseRow")', function (data) {
$("#case_list").append(data);
//重置验证模型
$("form").removeData("validator").removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse($("form"));
});
}); });
</script>

_CaseRow.cshtml分部视图代码

若要以集合/数组的形式提交到后台,须以name[]的格式提交,所以我能想到的就是这样去写(这种方案不可取!!)

但是这样写的话且不说太麻烦,验证也不行,一不小心也就写错了。所以这种方案并不可取

@{
Layout = null;
KeyValuePair<string, string> keyValuePair = new KeyValuePair<string, string>("Cases", Guid.NewGuid().ToString("N"));
var prefix = keyValuePair.Key+"["+keyValuePair.Value+"].";
}
@model MvcDemo.Models.CaseModel
<tr>
<td>
<input type="hidden" name="@(keyValuePair.Key+".index")" value="@keyValuePair.Value"/>
<input type="hidden" class="form-control" name="@(prefix)Id" value="@Model.Id" />
<input type="text" class="form-control" name="@(prefix)Title" value="@Model.Title" />
</td>
<td>
@Html.TextBox(prefix+nameof(Model.Author),Model.Author, new { @class = "form-control" })
</td>
<td>
@Html.TextBox(prefix + nameof(Model.Description), Model.Description, new { @class = "form-control" })
</td>
<td class="text-center">
<span class="del_tr glyphicon glyphicon-remove-circle"></span>
</td>
</tr>

而后发现大神写的一个HtmlPrefixScopeExtensions扩展类,可自动生成的表单前缀标识,使用方便,也能够使用验证

只需将表单包裹在@using (Html.BeginCollectionItem("子集合的属性名称")){}中即可,文末分享

@{
Layout = null;
}
@model MvcDemo.Models.CaseModel
@using MvcDemo.Extensions
<tr>
@using (Html.BeginCollectionItem("Cases"))
{
<td>
@Html.HiddenFor(e => e.Id)
@Html.TextBoxFor(e => e.Title, new { @class = "form-control" })
@Html.ValidationMessageFor(e => e.Title)
</td>
<td>
@Html.TextBoxFor(e => e.Author, new { @class = "form-control" })
</td>
<td>
@Html.TextBoxFor(e => e.Description, new { @class = "form-control" })
</td>
<td class="text-center">
<span class="del_tr glyphicon glyphicon-remove-circle"></span>
</td>
}
</tr>

然后提交表单可以发现格式如下,并能取到数据

MvcDemo.Extensions命名空间下的HtmlPrefixScopeExtensions扩展类

命名空间自行引用

  1. asp.net mvc版本
    public static class HtmlPrefixScopeExtensions
{
private const string IdsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_"; /// <summary>
///
/// </summary>
/// <param name="html"></param>
/// <param name="collectionName"></param>
/// <param name="createDummyForm">是否使用虚拟表单,为了解决上下文中不存在表单,无法生成验证信息</param>
/// <returns></returns>
public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName,
bool createDummyForm = false, bool clientValidationEnabled = false)
{
if (clientValidationEnabled == true)
html.ViewContext.ClientValidationEnabled = true; if (createDummyForm == true)
{
if (html.ViewContext != null && html.ViewContext.FormContext == null)
{
var dummyFormContext = new FormContext();
html.ViewContext.FormContext = dummyFormContext;
}
} return BeginCollectionItem(html, collectionName, html.ViewContext.Writer);
} private static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName, TextWriter writer)
{
var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
var itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().GetHashCode().ToString("x"); writer.WriteLine(
"<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />",
collectionName, html.Encode(itemIndex)); return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
} private static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix)
{
return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
} private static Queue<string> GetIdsToReuse(HttpContextBase httpContext, string collectionName)
{
var key = IdsToReuseKey + collectionName;
var queue = (Queue<string>)httpContext.Items[key];
if (queue == null)
{
httpContext.Items[key] = queue = new Queue<string>();
var previouslyUsedIds = httpContext.Request[collectionName + ".index"];
if (!string.IsNullOrEmpty(previouslyUsedIds))
foreach (var previouslyUsedId in previouslyUsedIds.Split(','))
queue.Enqueue(previouslyUsedId);
}
return queue;
} internal class HtmlFieldPrefixScope : IDisposable
{
internal readonly TemplateInfo TemplateInfo;
internal readonly string PreviousHtmlFieldPrefix; public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix)
{
TemplateInfo = templateInfo; PreviousHtmlFieldPrefix = TemplateInfo.HtmlFieldPrefix;
TemplateInfo.HtmlFieldPrefix = htmlFieldPrefix; } public void Dispose()
{
TemplateInfo.HtmlFieldPrefix = PreviousHtmlFieldPrefix;
}
}
}
  1. asp.net core版本
    public static class HtmlPrefixScopeExtensions
{
private const string IdsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_"; public static IDisposable BeginCollectionItem(this IHtmlHelper html, string collectionName)
{
return BeginCollectionItem(html, collectionName, html.ViewContext.Writer);
} private static IDisposable BeginCollectionItem(this IHtmlHelper html, string collectionName, TextWriter writer)
{
if (html.ViewData["ContainerPrefix"] != null)
collectionName = string.Concat(html.ViewData["ContainerPrefix"], ".", collectionName); var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
var itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString(); string htmlFieldPrefix = $"{collectionName}[{itemIndex}]";
html.ViewData["ContainerPrefix"] = htmlFieldPrefix; /*
* html.Name(); has been removed
* because of incorrect naming of collection items
* e.g.
* let collectionName = "Collection"
* the first item's name was Collection[0].Collection[<GUID>]
* instead of Collection[<GUID>]
*/
string indexInputName = $"{collectionName}.index"; // autocomplete="off" is needed to work around a very annoying Chrome behaviour
// whereby it reuses old values after the user clicks "Back", which causes the
// xyz.index and xyz[...] values to get out of sync.
writer.WriteLine($@"<input type=""hidden"" name=""{indexInputName}"" autocomplete=""off"" value=""{html.Encode(itemIndex)}"" />"); return BeginHtmlFieldPrefixScope(html, htmlFieldPrefix);
} private static IDisposable BeginHtmlFieldPrefixScope(this IHtmlHelper html, string htmlFieldPrefix)
{
return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
} private static Queue<string> GetIdsToReuse(HttpContext httpContext, string collectionName)
{
// We need to use the same sequence of IDs following a server-side validation failure,
// otherwise the framework won't render the validation error messages next to each item.
var key = IdsToReuseKey + collectionName;
var queue = (Queue<string>)httpContext.Items[key];
if (queue == null)
{
httpContext.Items[key] = queue = new Queue<string>(); if (httpContext.Request.Method == "POST" && httpContext.Request.HasFormContentType)
{
StringValues previouslyUsedIds = httpContext.Request.Form[collectionName + ".index"];
if (!string.IsNullOrEmpty(previouslyUsedIds))
foreach (var previouslyUsedId in previouslyUsedIds)
queue.Enqueue(previouslyUsedId);
}
}
return queue;
} internal class HtmlFieldPrefixScope : IDisposable
{
internal readonly TemplateInfo TemplateInfo;
internal readonly string PreviousHtmlFieldPrefix; public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix)
{
TemplateInfo = templateInfo; PreviousHtmlFieldPrefix = TemplateInfo.HtmlFieldPrefix;
TemplateInfo.HtmlFieldPrefix = htmlFieldPrefix;
} public void Dispose()
{
TemplateInfo.HtmlFieldPrefix = PreviousHtmlFieldPrefix;
}
}
}

命名空间自行引用~~

End

完整源码:https://coding.net/u/yimocoding/p/WeDemo/git/tree/MvcFormExt/MvcFormExt/MvcDemo

mvc一对多模型表单的快速构建的更多相关文章

  1. django模型表单ModelForm

    如果你正在构建一个数据库驱动的应用,那么你可能会有与Django的模型紧密映射的表单.比如,你有个BlogComment模型,并且你还想创建一个表单让大家提交评论到这个模型中.在这种情况下,写一个fo ...

  2. MVC动态生成的表单:表单元素比较多 你就这样写

    MVC动态生成的表单:表单元素比较多 你就这样写: public ActionResult ShoudaanActionResult(FormCollection from,T_UserM user) ...

  3. 模型表单ModleForm

    官方文档网址   http://python.usyiyi.cn/documents/django_182/topics/forms/modelforms.html 模型表单的应用场景 如果你正在构建 ...

  4. 第四章:Django表单 - 5:模型表单ModelForm

    如果你正在构建一个数据库驱动的应用,那么你可能会有与Django的模型紧密映射的表单.比如,你有个BlogComment模型,并且你还想创建一个表单让大家提交评论到这个模型中.在这种情况下,写一个fo ...

  5. Asp.net Mvc Ajax.BeginForm提交表单

    之前Mvc中一直用Html.BeginForm提交表单,即如下: @using (Html.BeginForm("Add", "News", FormMetho ...

  6. 如何在.Net Core MVC中为动态表单开启客户端验证

    非Core中的请参照: MVC的验证 jquery.validate.unobtrusive mvc验证jquery.unobtrusive-ajax 参照向动态表单增加验证 页面引入相关JS: &l ...

  7. [Swift通天遁地]二、表格表单-(9)快速创建一个美观强大的表单

    ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...

  8. [Swift通天遁地]二、表格表单-(8)快速实现表单的输入验证

    ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...

  9. spring mvc 防止重复提交表单的两种方法,推荐第二种

    第一种方法:判断session中保存的token 比较麻烦,每次在提交表单时都必须传入上次的token.而且当一个页面使用ajax时,多个表单提交就会有问题. 注解Token代码: package c ...

随机推荐

  1. 从yum提示空间不足到根分区扩容

    记录一次安装软件的报错 --1261065212@qq.com         1.系统版本(VMware 虚拟机) [root@ansible-admin ~]# cat /etc/redhat-r ...

  2. 【前端】vue.js环境配置以及实例运行简明教程

    vue.js环境配置以及实例运行简明教程 声明:本文档编写参考如下两篇博客,是对它们的修改与补充,欢迎点击链接查看原文: 原文1:vue.js在windows本地下搭建环境和创建项目 原文2:Vue. ...

  3. LNMP1.4 PHP升级脚本

    升级PHP前,请确认你的网站程序是否支持升级到的PHP版本,防止升级到网站程序不兼容的PHP版本,具体可以去你使用的PHP程序的官网查询相关版本支持信息.v1.3及以后版本大部分情况下也可以进行降级操 ...

  4. RabbitMQ核心概念篇

    RabbitMQ介绍 一.RabbitMQ使用场景 RabbitMQ他是一个消息中间件,说道消息中间件[最主要的作用:信息的缓冲区]还是的从应用场景来看下: 1.系统集成与分布式系统的设计 各种子系统 ...

  5. 王爽汇编习题2.2(1):给定地址段为0001H,仅通过变化偏移地址寻址,CPU的寻址范围为____到____

    此题解题背景默认为8080型CPU,地址总线为16根.(8080-16,8086-20,8088-20,80286-24,80386-32) 16根地址总线寻址能力:(2 ** 16) / 1024 ...

  6. 最近见到的JS返回函数的一些题

    JS返回值题一直都是考察重点,面试和笔试之中也经常涉及到,说一说我最近遇到的一些有意思的JS返回函数问题. 之前见到过一道有意思的问题,说有一个sum函数,用户可以通过sum(2,3)来取到2+3 = ...

  7. setAttribute设置无效

    我发现ie浏览器中动态用setAttribute设置style属性值始终不能设置,经过一番查找发现了这篇文字 http://webcenter.hit.edu.cn/articles/2009/05- ...

  8. 计算理论:NFA转DFA的两种方法

    本文将以两种方法实现NFA转DFA,并利用C语言实现. 方法二已利用HNU OJ系统验证,方法一迷之WA,但思路应该是对的,自试方案,测试均通过. (主要是思路,AC均浮云,大概又有什么奇怪的Case ...

  9. string和double之间的相互转换(C++)

    很多人都写过这个标题的文章,但本文要解决的是确保负数的string和double也可以进行转换. 代码如下: string转double double stringToDouble(string nu ...

  10. 201521123112《Java程序设计》第6周学习总结

    1. 本周学习总结 1.1 面向对象学习暂告一段落,请使用思维导图,以封装.继承.多态为核心概念画一张思维导图,对面向对象思想进行一个总结. 注1:关键词与内容不求多,但概念之间的联系要清晰,内容覆盖 ...