ASP.NET MVC中实现属性和属性值的组合,即笛卡尔乘积02, 在界面实现
2014-11-19 13:50
411 查看
在"ASP.NET MVC中实现属性和属性值的组合,即笛卡尔乘积01, 在控制台实现"中,在控制台应用程序中实现了属性值的笛卡尔乘积。本篇在界面中实现。需要实现的大致如下:在界面中勾选CheckBoxList中属性值选项:展开 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();}......}[/code]在Home/Index.cshtml视图,把通过
1、发出异步请求,把类似
再试下异步验证功能,居然没有?!
why?要知道,所有的异步验证与表单元素中以
可是,表单元素中明明已经有了以
亦喜亦忧,喜的是有了异步验证,忧的是虽然只有一个价格验证不通过,所有的价格都验证不通过!再次查看表单元素:
我们发现:所有有关价格的强类型视图页中,name属性都是price,而以上的
且在每个有关价格的强类型视图部分,有了一个隐藏域,存放这属性值的Id,这些Id可以随价格一起被保存到数据库。
$.ajax异步动态加载的、有关属性值及价格笛卡尔乘积的部分视图,追加到页面上的一块区域。大致如下:
![](http://images.cnitblog.com/blog/417212/201411/191400320468517.png)
{ propId: 1, propOptionId: 1 }的数组传给控制器2、控制器根据接收到的
{ propId: 1, propOptionId: 1 }的数组,得到类似
"红色 5英寸"的一个IEnumerable<string>类型的集合,通过ViewData传给
_DisplaySKUs.cshtml部分视图3、在
_DisplaySKUs.cshtml,遍历类似
"红色 5英寸"的一个IEnumerable<string>类型的集合,每遍历一次,再加载有关价格的一个强类型部分视图
_SKUDetail.cshtml4、最后把
_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......真如所愿!
![](http://images.cnitblog.com/blog/417212/201411/191400327506604.png)
![](http://images.cnitblog.com/blog/417212/201411/191400335004975.png)
data-*开头的属性及其值有关。看来,还是有必要查看当前的表单元素。
![](http://images.cnitblog.com/blog/417212/201411/191400343126859.png)
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,即有关价格强类型视图生成的地方,运用扩展方法。再次运行,并测试异步验证。
![](http://images.cnitblog.com/blog/417212/201411/191400351714729.png)
![](http://images.cnitblog.com/blog/417212/201411/191400363754730.png)
$.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)
}再次运行并测试异步验证,一切正常!
![](http://images.cnitblog.com/blog/417212/201411/191400371876614.png)
![](http://images.cnitblog.com/blog/417212/201411/191400385466398.png)
相关文章推荐
- ASP.NET MVC中实现属性和属性值的组合,即笛卡尔乘积01, 在控制台实现
- ASP.NET MVC 使用Remote特性实现远程属性验证
- 使用DIV属性,在ASP.NET 中实现,文本框和下拉框组合,实现文本框即能输入又能选择
- ASP.NET MVC 中使用Spring.NET实现简单的属性注入
- ASP.NET MVC利用ActionLink实现动态组合查询
- ASP.NET MVC 自定义过滤属性实现Enterprise的log功能
- 转---在ASP.NET MVC中实现登录后回到原先的界面
- 在Asp.Net MVC中实现CompareValues标签对Model中的属性进行验证
- 在ASP.NET MVC中实现登录后回到原先的界面
- 在Asp.Net MVC中实现RequiredIf标签对Model中的属性进行验证
- ASP.NET MVC 使用Remote特性实现远程属性验证
- ASP.NET MVC 中使用Spring.NET实现简单的属性注入
- 在ASP.Net中两种利用CSS实现多界面的方法
- ASP.Net中利用CSS实现多界面两法
- ASP.Net中利用CSS实现多界面两法
- 在ASP.Net中两种利用CSS实现多界面的方法
- 在ASP.Net中两种利用CSS实现多界面的方法.
- 在ASP.Net中两种利用CSS实现多界面的方法
- 在ASP.Net 2.0中实现多语言界面的方法
- MVC架构在Asp.net中的应用和实现