ASP.NET MVC中实现属性和属性值的组合,即笛卡尔乘积02, 在界面实现
在"ASP.NET MVC中实现属性和属性值的组合,即笛卡尔乘积01, 在控制台实现"中,在控制台应用程序中实现了属性值的笛卡尔乘积。本篇在界面中实现。需要实现的大致如下:
在界面中勾选CheckBoxList中属性值选项:
把勾选的属性值进行笛卡尔乘积,每行需要填写价格:
我们暂且不考虑这些CheckBoxList是如何显示出来的(在后续有关商品模块的文章中会实现),还需考虑的方面包括:
1、从CheckBoxList中获取到什么再往控制器传?
对于每行的CheckBoxList来说,可以从选项中拿到属性值的编号,还可以拿到属性的编号,最后我们拿到类似{ propId: 1, propOptionId: 1 },{ propId: 1, propOptionId: 2 }
的数组或集合。
2、前台数组和集合如何传递才能确保被控制器接收?
通过jQuery.ajax方法可以实现,待会实现。
3、在呈现属性值和价格组合的界面是如何呈现的?
其实是通过部分视图来实现的,而且在一个主部分视图中嵌套了子部分视图。
4、如何对呈现的价格验证呢?
由于价格是被异步、动态加载到页面的,所以,这里还涉及到如何对异步加载的动态内容进行验证的问题。
关于属性的Model:
public class Prop { public int Id { get; set; } public string Name { get; set; } }
关于属性值的Model:
public class PropOption { public int Id { get; set; } public string RealValue { get; set; } public int PropId { get; set; } }
把从前台获取的属性和属性值编号封装到PropAndOption
类中,前台向控制器传的就是这个类的集合。
public class PropAndOption { public int PropId { get; set; } public int propOptionId { get; set; } }
模拟一个数据库存储层,无非就是获取数据等,可忽略。
- 展开 public static class Database
- {
- public static List<Prop> GetProps()
- {
- return new List<Prop>()
- {
- new Prop(){Id = 1, Name = "颜色"},
- new Prop(){Id = 2, Name = "尺寸"},
- };
- }
- public static List<PropOption> GetPropOptions()
- {
- return new List<PropOption>()
- {
- new PropOption(){Id = 1, PropId = 1, RealValue = "红色"},
- new PropOption(){Id = 2, PropId = 1, RealValue = "蓝色"},
- new PropOption(){Id = 3, PropId = 1, RealValue = "黑色"},
- new PropOption(){Id = 4, PropId = 2, RealValue = "5.5英寸"},
- new PropOption(){Id = 5, PropId = 2, RealValue = "8.5英寸"},
- };
- }
- //根据属性项的Id获取属性值
- public static string GetOptionValueById(int optionId)
- {
- return (GetPropOptions().Where(p => p.Id == optionId).FirstOrDefault()).RealValue;
- }
- public static List<PropOption> GEtPropOptionsByPropId(int propId)
- {
- return GetPropOptions().Where(p => p.PropId == propId).ToList();
- }
- }
- Home控制器的Index方法渲染Home/Index.cshtml视图,在这个页面中暂时属性值及价格的笛卡尔乘积。
- public class HomeController : Controller
- {
- public ActionResult Index()
- {
- return View();
- }
- ......
- }
在Home/Index.cshtml视图,把通过$.ajax
异步动态加载的、有关属性值及价格笛卡尔乘积的部分视图,追加到页面上的一块区域。大致如下:
1、发出异步请求,把类似{ propId: 1, propOptionId: 1 }
的数组传给控制器
2、控制器根据接收到的{ propId: 1, propOptionId: 1 }
的数组,得到类似"红色 5英寸"
的一个IEnumerable<string>类型的集合,通过ViewData传给_DisplaySKUs.cshtml
部分视图
3、在_DisplaySKUs.cshtml
,遍历类似"红色 5英寸"
的一个IEnumerable<string>类型的集合,每遍历一次,再加载有关价格的一个强类型部分视图_SKUDetail.cshtml
4、最后把_DisplaySKUs.cshtml
部分视图动态加载到Home/Index.cshtml视图的某块区域中
Home/Index.cshtml视图。
@{
ViewBag.Title = "Index"; Layout = "~/Views/Shared/_Layout.cshtml";}
<form id="fm"> <ul id="skus"> </ul> <input type="submit" value="提交"/> </form> @section scripts{
<script src="~/Scripts/jquery.validate.min.js"></script> <script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script> <script src="~/Scripts/dynamicvalidation.js"></script> <script type="text/javascript"> $(function () { var propAndOptions = []; propAndOptions.push({ propId: 1, propOptionId: 1 }); propAndOptions.push({ propId: 1, propOptionId: 2 }); propAndOptions.push({ propId: 1, propOptionId: 3 }); propAndOptions.push({ propId: 2, propOptionId: 4 }); propAndOptions.push({ propId: 2, propOptionId: 5 }); $.ajax({ cache: false, url: '@Url.Action("DisplaySKUs", "Home")', contentType: 'application/json; charset=utf-8', dataType: "html", type: "POST", data: JSON.stringify({ 'propAndOptions': propAndOptions }), success: function (data) { $('#skus').html(data); }, error: function (jqXhr, textStatus, errorThrown) { alert("出错了 '" + jqXhr.status + "' (状态: '" + textStatus + "', 错误为: '" + errorThrown + "')"); } }); }); </script>}
以上,
○ { propId: 1, propOptionId: 1 }
这个匿名对象的propId
和propOptionId
键必须和PropAndOption
类的属性对应
○ $.ajax
方法中,contentType
表示传递给控制器的数据类型
○ $.ajax
方法中,dataType
表示返回的数据类型,由于返回的是部分视图,所以这里的类型是html
○ 通过JSON.stringify
方法把{ propId: 1, propOptionId: 1 }
类型数组转换成json格式
○ $('#skus').html(data)
把_DisplaySKUs.cshtml部分视图动态加载到id为skus的区域
HomeController
public class HomeController : Controller { public ActionResult Index() { return View(); } [HttpPost] public ActionResult DisplaySKUs(List<PropAndOption> propAndOptions) { try { //属性值分组 var groupValues = (from v in propAndOptions group v by v.PropId into grp select grp.Select(t => Database.GetOptionValueById(t.propOptionId))).ToList(); //属性值Id分组 var groupIds = (from i in propAndOptions group i by i.PropId into grep select grep.Select(t => t.propOptionId.ToString())).ToList(); //属性值分组后进行笛卡尔乘积 IEnumerable<string> values; values = groupValues.First(); groupValues.RemoveAt(0); groupValues.ForEach(delegate(IEnumerable<string> ele) { values = (from v in values from e in ele select v + " " + e).ToList(); }); //属性值Id分组后进行笛卡尔乘积 IEnumerable<string> ids; ids = groupIds.First(); groupIds.RemoveAt(0); groupIds.ForEach(delegate(IEnumerable<string> ele) { ids = (from i in ids from e in ele select i + "," + e).ToList(); }); //把笛卡尔积后的集合传递给前台 ViewData["v"] = values; ViewData["i"] = ids; } catch (Exception) { throw; } return PartialView("_DisplaySKUs"); } }
以上,部分视图_DisplaySKUs
将会收到2种类型为IEnumerable<string>的集合,一种有关类似"红色 5英寸"
属性值集合,一种是类似"1, 2"
属性值ID集合,前者用来显示,后者需要被传递到有关价格的Model中,以便随同价格保存到数据库。和价格有关的Model是:
public class SKUVm { [Display(Name = "价格")] [Required(ErrorMessage = "必填")] [Range(typeof(Decimal), "0", "9999", ErrorMessage = "{0} 必须是数字介于 {1} 和 {2}之间.")] public decimal Price { get; set; } public string OptionIds { get; set; } }
其中,OptionIds
属性用来保存类似"1, 2"
属性值ID,随同价格被保存到数据库。具体如何保存,这里略去,将在后续有关商品模块的文章中实现。
_DisplaySKUs.cshtml部分视图
@{
string[] values = (ViewData["v"] as IEnumerable<string>).ToArray(); string[] ids = (ViewData["i"] as IEnumerable<string>).ToArray();}
@for (int i = 0; i <values.Count(); i++){
<li> <span> @values[@i] </span> <span class="s"> @{ SKUVm skuVm = new SKUVm(); skuVm.OptionIds = ids[@i]; Html.RenderPartial("_SKUDetail", skuVm); } </span> </li>}
以上,遍历所有类似"红色 5英寸"
属性值集合,用于显示,由于类似"1, 2"
属性值ID集合与类似"红色 5英寸"
属性值集合采用同样的算法、逻辑获取到的,所以两者有一一对应关系。在遍历类似"红色 5英寸"
属性值集合的同时,把每一个类似"1, 2"
属性值传给有关价格的强类型部分视图。
_SKUDetail.cshtml强类型部分视图。
@model MvcApplication3.Models.SKUVm @Html.TextBoxFor(m => m.Price) @Html.ValidationMessageFor(m => m.Price) @Html.HiddenFor(m => m.OptionIds)
大功告成!试着运行一下。wow......真如所愿!
再试下异步验证功能,居然没有?!
why?要知道,所有的异步验证与表单元素中以data-*
开头的属性及其值有关。看来,还是有必要查看当前的表单元素。
可是,表单元素中明明已经有了以data-*
开头的属性及其值啊?难道jquery.validate.unobtrusive
在调用jquery.validate
的validate
方法的时候,写法有问题?
validate: function( options ) {......
// check if a validator for this form was already created
var validator = $.data(this[0], 'validator'); if ( validator ) { return validator;}
......
发现问题了:当调用validate
方法的时候,jquery.validate.unobtrusive
发现存在data-val="true"
的表单元素,就会返回当前的validator对象
而不做其它任何事。而实际上,对于动态加载的部分视图,它还没有自己的validator对象
。所以,有必要专门针对动态加载的内容写一个$.validator.unobtrusive
的扩展。
创建dynamicvalidation.js文件。
//对动态生成内容客户端验证
(function ($) { $.validator.unobtrusive.parseDynamicContent = function (selector, formSelector) { $.validator.unobtrusive.parse(selector); var form = $(formSelector); var unobtrusiveValidation = form.data('unobtrusiveValidation'); var validator = form.validate(); $.each(unobtrusiveValidation.options.rules, function (elname, elrules) { if (validator.settings.rules[elname] == undefined) { var args = {}; $.extend(args, elrules); args.messages = unobtrusiveValidation.options.messages[elname]; //edit:use quoted strings for the name selector $("[name='" + elname + "']").rules("add", args); } else { $.each(elrules, function (rulename, data) { if (validator.settings.rules[elname][rulename] == undefined) { var args = {}; args[rulename] = data; args.messages = unobtrusiveValidation.options.messages[elname][rulename]; //edit:use quoted strings for the name selector $("[name='" + elname + "']").rules("add", args); } }); } }); }; })(jQuery);
把dynamicvalidation.js
文件引入Home/Index.cshtml视图页,修改如下:
……
@section scripts{
<script src="~/Scripts/jquery.validate.min.js"></script> <script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script> <script src="~/Scripts/dynamicvalidation.js"></script> <script type="text/javascript"> $(function () { var propAndOptions = []; propAndOptions.push({ propId: 1, propOptionId: 1 }); propAndOptions.push({ propId: 1, propOptionId: 2 }); propAndOptions.push({ propId: 1, propOptionId: 3 }); propAndOptions.push({ propId: 2, propOptionId: 4 }); propAndOptions.push({ propId: 2, propOptionId: 5 }); $.ajax({ cache: false, url: '@Url.Action("DisplaySKUs", "Home")', contentType: 'application/json; charset=utf-8', dataType: "html", type: "POST", data: JSON.stringify({ 'propAndOptions': propAndOptions }), success: function (data) { $('#skus').html(data); $.each($('.s'), function(index) { $.validator.unobtrusive.parseDynamicContent(this, "#fm"); }); }, error: function (jqXhr, textStatus, errorThrown) { alert("出错了 '" + jqXhr.status + "' (状态: '" + textStatus + "', 错误为: '" + errorThrown + "')"); } }); }); </script>}
以上,当异步加载成功,遍历的类名为s的span,即有关价格强类型视图生成的地方,运用扩展方法。
再次运行,并测试异步验证。
亦喜亦忧,喜的是有了异步验证,忧的是虽然只有一个价格验证不通过,所有的价格都验证不通过!
再次查看表单元素:
我们发现:所有有关价格的强类型视图页中,name属性都是price,而以上的$.validator.unobtrusive.parseDynamicContent
扩展方法是以name属性为依据的。所以,有必要为每一个有关价格的强类型视图生成不一样的name值。
写一个针对HtmlHelper
的扩展方法,目标是生成如下格式:
//目标生成如下格式 <input autocomplete="off" name="SomePropertion.Index" type="hidden" value="6d85a95b-1dee-4175-bfae-73fad6a3763b" /> <label>Title</label> <input class="text-box single-line" name="SomePropertion[6d85a95b-1dee-4175-bfae-73fad6a3763b].Title" type="text" value="Movie 1" /> <span class="field-validation-valid"></span>
以上的目的是让每一个有关价格的input元素的name属性值都不一样。
public static class CollectionEditingHtmlExtensions { //目标生成如下格式 //<input autocomplete="off" name="FavouriteMovies.Index" type="hidden" value="6d85a95b-1dee-4175-bfae-73fad6a3763b" /> //<label>Title</label> //<input class="text-box single-line" name="FavouriteMovies[6d85a95b-1dee-4175-bfae-73fad6a3763b].Title" type="text" value="Movie 1" /> //<span class="field-validation-valid"></span> public static IDisposable BeginCollectionItem<TModel>(this HtmlHelper<TModel> html, string collectionName) { //构建name="FavouriteMovies.Index" string collectionIndexFieldName = string.Format("{0}.Index", collectionName); //构建Guid字符串 string itemIndex = GetCollectionItemIndex(collectionIndexFieldName); //构建带上集合属性+Guid字符串的前缀 string collectionItemName = string.Format("{0}[{1}]", collectionName, itemIndex); TagBuilder indexField = new TagBuilder("input"); indexField.MergeAttributes(new Dictionary<string, string>() { {"name", string.Format("{0}.Index", collectionName)}, {"value", itemIndex}, {"type", "hidden"}, {"autocomplete", "off"} }); html.ViewContext.Writer.WriteLine(indexField.ToString(TagRenderMode.SelfClosing)); return new CollectionItemNamePrefixScope(html.ViewData.TemplateInfo, collectionItemName); } private class CollectionItemNamePrefixScope : IDisposable { private readonly TemplateInfo _templateInfo; private readonly string _previousPrfix; //通过构造函数,先把TemplateInfo以及TemplateInfo.HtmlFieldPrefix赋值给私有字段变量,并把集合属性名称赋值给TemplateInfo.HtmlFieldPrefix public CollectionItemNamePrefixScope(TemplateInfo templateInfo, string collectionItemName) { this._templateInfo = templateInfo; this._previousPrfix = templateInfo.HtmlFieldPrefix; templateInfo.HtmlFieldPrefix = collectionItemName; } public void Dispose() { _templateInfo.HtmlFieldPrefix = _previousPrfix; } } /// <summary> /// /// </summary> /// <param name="collectionIndexFieldName">比如,FavouriteMovies.Index</param> /// <returns>Guid字符串</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(); } }
关于,CollectionEditingHtmlExtensions类,在"MVC批量更新,可验证并解决集合元素不连续控制器接收不完全的问题"中有详细说明。
最后,再次修改_SKUDetail.cshtml
这个强类型部分视图。
@using MvcApplication3.Extension @model MvcApplication3.Models.SKUVm @using (Html.BeginCollectionItem("ProductSkus")){
@Html.TextBoxFor(m => m.Price) @Html.ValidationMessageFor(m => m.Price) @Html.HiddenFor(m => m.OptionIds)}
再次运行并测试异步验证,一切正常!
且在每个有关价格的强类型视图部分,有了一个隐藏域,存放这属性值的Id,这些Id可以随价格一起被保存到数据库。
ASP.NET MVC中实现属性和属性值的组合,即笛卡尔乘积02, 在界面实现的更多相关文章
- ASP.NET MVC中实现属性和属性值的组合,即笛卡尔乘积01, 在控制台实现
在电商产品模块中必经的一个环节是:当选择某一个产品类别,动态生成该类别下的所有属性和属性项,这些属性项有些是以DropDownList的形式存在,有些是以CheckBoxList的形式存在.接着,把C ...
- 关于ASP.NET MVC 中JsonResult返回的日期值问题
最近开始用MVC做项目,在使用 JsonResult返回数据的时候,日期被反射成了/Date 1233455这种格式,遍查网上都是在客户端使用JS来处理这个问题的,这样的话,就需要在每一个涉及到日期的 ...
- ASP.Net MVC 中EF实体的属性取消映射数据库、自定义名称
例如:数据库中一个字段名称为CompanyId 自定义实体数据名称 [Column("CompanyId")] public int Id{ get; set; } 这样就可以使用 ...
- ASP.NET MVC中商品模块小样
在前面的几篇文章中,已经在控制台和界面实现了属性值的笛卡尔乘积,这是商品模块中的一个难点.本篇就来实现在ASP.NET MVC4下商品模块的一个小样.与本篇相关的文章包括: 1.ASP.NET MVC ...
- 在Asp.Net MVC中实现RequiredIf标签对Model中的属性进行验证
在Asp.Net MVC中可以用继承ValidationAttribute的方式,自定制实现RequiredIf标签对Model中的属性进行验证 具体场景为:某一属性是否允许为null的验证,要根据另 ...
- 在Asp.Net MVC中实现CompareValues标签对Model中的属性进行验证
在Asp.Net MVC中可以用继承ValidationAttribute的方式,自定制实现Model两个中两个属性值的比较验证 具体应用场景为:要对两个属性值的大小进行验证 代码如下所示: /// ...
- 如何在 ASP.NET MVC 中集成 AngularJS(3)
今天来为大家介绍如何在 ASP.NET MVC 中集成 AngularJS 的最后一部分内容. 调试路由表 - HTML 缓存清除 就在我以为示例应用程序完成之后,我意识到,我必须提供两个版本的路由表 ...
- 如何在 ASP.NET MVC 中集成 AngularJS(2)
在如何在 ASP.NET MVC 中集成 AngularJS(1)中,我们介绍了 ASP.NET MVC 捆绑和压缩.应用程序版本自动刷新和工程构建等内容. 下面介绍如何在 ASP.NET MVC 中 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (20) -----第四章 ASP.NET MVC中使用实体框架之在MVC中构建一个CRUD示例
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 第四章 ASP.NET MVC中使用实体框架 ASP.NET是一个免费的Web框架 ...
随机推荐
- linux修改文件打开最大数(ulimit命令)
解除 Linux 系统的最大进程数和最大文件打开数限制:vi /etc/security/limits.conf# 添加如下的行* soft noproc 65536 * hard noproc 65 ...
- vs中如何统计整个项目的代码行数
在一个大工程中有很多的源文件和头文件,如何快速统计总行数? ------解决方案--------------------b*[^:b#/]+.*$^b*[^:b#/]+.*$ ctrl + shift ...
- MVC layout 命名空间引用问题
虽然用MVC做了很多项目,但是都是在别人搭好的框架上实现 今天碰到一个很简单的命名空间引用问题 如图所示,Scripts和Styles 都没有引用命名空间 解决方法一: 直接使用 System.Web ...
- 两种常量类型-readonly和const
C#中有两种常量类型,分别为readonly(运行时常量)与const(编译时常量),本文将就这两种类型的不同特性进行比较并说明各自的适用场景. 工作原理 readonly 为运行时常量(动态常量), ...
- MongoDB存储基础教程
一.MongoDB简介 1. mangodb是一种基于分布式.文件存储的非关系型数据库 2. C++写的,性能高 3. 为web应用提供可扩展的高性能数据存储解决方案 4. 所支持的格式是json格式 ...
- springmvc接收jquery提交的数组数据
var selectedUsers = $('#users').tagbox('getValues'); if (selectedUsers.length > 0) { $.post(appPa ...
- ubuntu12.04安装maven
step: 1,确认已经安装jdk, java --version 2,下载apache-maven-3.3.9 下载地址:http://maven.apache.org/download.cgi 3 ...
- Pandas DataFrame数据的增、删、改、查
Pandas DataFrame数据的增.删.改.查 https://blog.csdn.net/zhangchuang601/article/details/79583551 #删除列 df_2 = ...
- 对象克隆及属性转换-JavaScript
在某些项目中,需要将一些返回信息进行其他语言的翻译,可以为不同语言用户提供不同的语言版本.下面是一个实现: /** * @class Translate * @description 查询字典,翻译成 ...
- Java编程的逻辑 (5) - 小数计算为什么会出错?
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...