博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ASP.NET MVC中实现属性和属性值的组合,即笛卡尔乘积02, 在界面实现
阅读量:6584 次
发布时间:2019-06-24

本文共 11375 字,大约阅读时间需要 37 分钟。

在""中,在控制台应用程序中实现了属性值的笛卡尔乘积。本篇在界面中实现。需要实现的大致如下:

在界面中勾选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
GetProps() { return new List
() { new Prop(){Id = 1, Name = "颜色"}, new Prop(){Id = 2, Name = "尺寸"}, }; } public static List
GetPropOptions() { return new List
() { 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
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";
}
     
     
     
     
    @section scripts
    {
    $(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 + "')");
    }
    });
    });
     
    }
     
     

    以上,

    { propId: 1, propOptionId: 1 }这个匿名对象的propIdpropOptionId键必须和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
    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
    values;
    values = groupValues.First();
    groupValues.RemoveAt(0);
    groupValues.ForEach(delegate(IEnumerable
    ele)
    {
    values = (from v in values
    from e in ele
    select v + " " + e).ToList();
    });
     
    //属性值Id分组后进行笛卡尔乘积
    IEnumerable
    ids;
    ids = groupIds.First();
    groupIds.RemoveAt(0);
    groupIds.ForEach(delegate(IEnumerable
    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
    ).ToArray();
    string[] ids = (ViewData["i"] as IEnumerable
    ).ToArray();
    }
     
    @for (int i = 0; i 
    {
  • @values[@i]
     
    @{
    SKUVm skuVm = new SKUVm();
    skuVm.OptionIds = ids[@i];
    Html.RenderPartial("_SKUDetail", skuVm);
    }
     
     
    }
     
     

    以上,遍历所有类似"红色 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.validatevalidate方法的时候,写法有问题?

     

     
    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
    {
    $(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 + "')");
    }
    });
    });
     
    }
     
     

    以上,当异步加载成功,遍历的类名为s的span,即有关价格强类型视图生成的地方,运用扩展方法。

     

    再次运行,并测试异步验证。

     

    亦喜亦忧,喜的是有了异步验证,忧的是虽然只有一个价格验证不通过,所有的价格都验证不通过!

     

    再次查看表单元素:

     

    我们发现:所有有关价格的强类型视图页中,name属性都是price,而以上的$.validator.unobtrusive.parseDynamicContent扩展方法是以name属性为依据的。所以,有必要为每一个有关价格的强类型视图生成不一样的name值。

     

    写一个针对HtmlHelper的扩展方法,目标是生成如下格式:

     
    //目标生成如下格式

     

    以上的目的是让每一个有关价格的input元素的name属性值都不一样。

     

     
    public static class CollectionEditingHtmlExtensions
    {
    //目标生成如下格式
    //
    //
    //
    //
     
    public static IDisposable BeginCollectionItem
    (this HtmlHelper
    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
    ()
    {
    {"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;
    }
    }
     
    /// 
    ///
    /// 
    /// 比如,FavouriteMovies.Index
    /// 
    Guid字符串
    private static string GetCollectionItemIndex(string collectionIndexFieldName)
    {
    Queue
    previousIndices = (Queue
    )HttpContext.Current.Items[collectionIndexFieldName];
    if (previousIndices == null)
    {
    HttpContext.Current.Items[collectionIndexFieldName] = previousIndices = new Queue
    ();
    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类,在""中有详细说明。

     

    最后,再次修改_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可以随价格一起被保存到数据库。

      

     

     

     

    转载地址:http://maxno.baihongyu.com/

    你可能感兴趣的文章
    Handler 原理分析和使用(一)
    查看>>
    给因特尔S2600CO服务器主板安装【SAS控制器】驱动
    查看>>
    MySQL之聚合
    查看>>
    异常处理
    查看>>
    CENTOS7 使用 Nginx + Uwsgi 部署 Django 项目
    查看>>
    快商通操作
    查看>>
    SpringMVC源码解析- HandlerAdapter - ModelFactory
    查看>>
    编程语言简史
    查看>>
    ios7状态栏属性的设置
    查看>>
    (六)springmvc+mybatis+dubbo+zookeeper分布式架构 整合 - maven构建config配置项目
    查看>>
    python重启程序 重新运行脚本
    查看>>
    HDU1253 胜利大逃亡 (BFS)
    查看>>
    编译带有PROJ4和GEOS模块的GDAL
    查看>>
    WPF——TargetNullValue(如何在绑定空值显示默认字符)
    查看>>
    杭电2001——计算两点间的距离
    查看>>
    实现简单点赞功能
    查看>>
    HTTP协议详解
    查看>>
    CSS
    查看>>
    静态库相关
    查看>>
    TDD: 解除依赖
    查看>>