mvc一对多模型表单的快速构建

功能需求描述

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

转载于:https://www.cnblogs.com/morang/p/7593215.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/467608.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c语言中 if(x) 、if(0) 、if(1)

解释if 语句里面包含真和非真&#xff0c;但是如果我们没有写清楚真和非真的话&#xff0c;会如何呢&#xff1f;if(x)相当于if(x ! 0)如果是指针的话&#xff0c;相当于if(x ! NULL)而if(1)相当于if(1 ! 0)还有if(0)相当于if(0 ! 0)举个例子#include<stdio.h> int main(…

看Linus骂人,真解气

感受下Linus骂人的感觉吧&#xff0c; 这样你会觉得工作中遇到的那些不愉快就算个鸟事背景一个Linux主线的内核维护者提交了一份patch&#xff0c;并说明问题产生的原因是因为应用传的音频有问题。Linus回复如下你他娘的给老子闭嘴&#xff01;这是一个内核bug好不好&#xff0…

不就是要个30K的薪资,他还问我Nginx调优

我是一个运维“老鸟”&#xff0c;目前在到处找工作阶段。周三刚面试完一家公司&#xff0c;还是非常中意的公司。结果是我中意公司&#xff0c;公司不中意我&#xff0c;妥妥的黄了。面试完我才知道&#xff0c;Linux云计算工程师必须能精通20多个企业级服务器优化。我之前不是…

android导出apk文件_Android测试工具入门介绍(三)

介绍一款牛逼的测试框架Drozer&#xff0c;一款可以检测Android一些公共漏洞的工具&#xff08;可能远不止这些、还可以继续挖掘&#xff09;&#xff0c;还可以生成shellcode&#xff0c;进行安卓设备的远程exploit。附下载地址&#xff1a;https://github.com/mwrlabs/drozer…

bomb炸弹

今天看到的一个Linux shell命令&#xff0c;但是我先说下&#xff0c;这个命令是危险的&#xff0c;所以没事的时候不要随便执行&#xff0c;出现了各种危险不要怪我没有提前告诉你哈。DANGER!命令代码:(){ :|: & };:命令解析1:() 意思是定义了一个函数&#xff0c;这个函数…

kindle的xray怎么用_Xray简单使用教程

Xray简单使用教程0X00下载xray 为单文件二进制文件&#xff0c;无依赖&#xff0c;也无需安装&#xff0c;下载后直接使用。下载地址为&#xff1a;注意&#xff1a; 不要直接 clone 仓库&#xff0c;xray 并不开源&#xff0c;仓库内不含源代码&#xff0c;直接下载构建的二进…

文件方式实现完整的英文词频统计实例(9.27)

1.读入待分析的字符串 2.分解提取单词 3.计数字典 4.排除语法型词汇 5.排序 6.输出TOP(20) 文本代码如下&#xff1a; girlRemembering me, Discover and see All over the world, Shes known as a girl To those who a free, The mind shall be key Forgotten as the past Ca…

UNUSED参数,这个宏,很秀

前言你们有没有在写代码的时候&#xff0c;遇到有的参数&#xff0c;从函数体里面传进来&#xff0c;但是又用不上&#xff0c;所以就不引用&#xff0c;但是不引用&#xff0c;在编译的时候&#xff0c;就会提示错误。是不是很尴尬&#xff0c;我们不使用&#xff0c;并不是错…

利用Python对文件进行批量重命名——以图片文件为例

效果如下&#xff1a;0001号用户的第 i 张图片 代码&#xff1a; import osclass ImageRename():def __init__(self):self.path C:/Users/lbpeng/Desktop/test/chictopia2/images1/fashioninmysoul/fulldef rename(self):filelist os.listdir(self.path)totalnum len(fileli…

mysql双重分组没有值也要显示_mysql 统计数据,按照日期分组,把没有数据的日期也展示出来...

因为业务需求&#xff0c;要统计每天的新增用户并且要用折线图的方式展示。如果其中有一天没有新增用户的话&#xff0c;这一天就是空缺的&#xff0c;在绘制折线图的时候是不允许的&#xff0c;所有要求把没有数据的日期也要在图表显示。查询2019-01-10------2019-01-20日的新…

我一个专科生,还有未来吗?

今天分享一个星球里面的讨论你好&#xff0c;我加入这个星球也算比较久了在此之前也一直都是在观望&#xff0c;我是一个19年因为高考失利而没有选择复读的专科生&#xff0c;我选择的专业是嵌入式技术与应用&#xff0c;最近不知道为什么特别迷茫&#xff0c;在选择读专科之前…

mysql查询数据库第一条记录_SQL获取第一条记录的方法(sqlserver、oracle、mysql数据库)...

Sqlserver 获取每组中的第一条记录在日常生活方面&#xff0c;我们经常需要记录一些操作&#xff0c;类似于日志的操作&#xff0c;最后的记录才是有效数据&#xff0c;而且可能它们属于不同的方面、功能下面&#xff0c;从数据库的术语来说&#xff0c;就是查找出每组中的一条…

Linux select/poll机制原理分析

转载一篇文章&#xff0c;讲解select和poll机制的&#xff0c;分享给大家。前言Read the fucking source code! --By 鲁迅A picture is worth a thousand words. --By 高尔基1. 概述Linux系统在访问设备的时候&#xff0c;存在以下几种IO模型&#xff1a;Blocking IO Model&am…

SSM框架的搭建学习(1)---MyBatis的环境搭建

SSM(SpringSpringMVCMyBatis)框架为当今最为流行的WEB开发框架之一,基本上涉及数据库的一些增删改查操作都可以借用此框架,本尊此前接的一个小公司关于楼宇空调监控指标的项目就是基于此框架,只不过当时框架已经被别人搭建好,只等我去添砖加瓦,并没有从头开始对此框架进行着手搭…

argv python 提示输入_Python解释器

一、Python解释器我们编写的Python代码都要放在Python解释器上运行&#xff0c;解释器是代码与计算机硬件之间的软件逻辑层。当我们在操作系统上安装好Python之后&#xff0c;它就包含了保证Python运行的最小化组件&#xff1a;解释器 和 标准库。根据选用的Python版本的不同&a…

高阶篇:8.1)开模前评审及提交资料;

本章目的&#xff1a;明确开模前需要提交的资料&#xff0c;确保开模一次性成功。 在现有机械行业制作技术的大环境下&#xff0c;除却必要的机加工零件之外&#xff0c;大部分的零件量产都需要开模制作。如塑胶、钣金、压铸、粉末冶金、锻造等。 但是模具是很贵的&#xff08;…

Android系统充电系统介绍-预防手机充电爆炸

1、锂电池介绍锂离子电池由日本索尼公司于1990年最先开发成功。它是把锂离子嵌入碳&#xff08;石油焦炭和石墨&#xff09;中形成负极&#xff08;传统锂电池用锂或锂合金作负极&#xff09;。正极材料常用LixCoO2 ,也用 LixNiO2 &#xff0c;和LixMnO4 &#xff0c;电解液用L…

Linux物理内存初始化

背景Read the fucking source code! --By 鲁迅A picture is worth a thousand words. --By 高尔基说明&#xff1a;Kernel版本&#xff1a;4.14ARM64处理器&#xff0c;Contex-A53&#xff0c;双核使用工具&#xff1a;Source Insight 3.5&#xff0c; Visio1. 介绍让我们思考…

mysql中%3c%3e和=_Grafana+Prometheus 监控 MySQL

架构图环境IP环境需装软件192.168.0.237mysql-5.7.20node_exporter-0.15.2.linux-amd64.tar.gzmysqld_exporter-0.10.0.linux-amd64.tar.gz192.168.0.248grafanaprometheusprometheus-2.1.0.linux-amd64.tar.gznode_exporter-0.15.2.linux-amd64.tar.gzgrafana-4.6.3.linux-x64…

Linux CentOS7.0 (01)在Vmvare Workstation上 安装配置

一、新建虚拟机 1、创建新的虚拟机 -》 默认典型 -》选择安装介质 2、指定虚拟机名称、安装目录、磁盘容量大小 点击 “完成”&#xff0c;创建虚拟机&#xff01; 随后虚拟机将自动启动安装过程。 二、安装linux 1、选择 English、English&#xff08;United States&#xff0…