zoukankan      html  css  js  c++  java
  • ASP.NET MVC中商品模块小样

    在前面的几篇文章中,已经在控制台和界面实现了属性值的笛卡尔乘积,这是商品模块中的一个难点。本篇就来实现在ASP.NET MVC4下商品模块的一个小样。与本篇相关的文章包括:


    1、ASP.NET MVC中实现属性和属性值的组合,即笛卡尔乘积01, 在控制台实现 
    2、
    ASP.NET MVC中实现属性和属性值的组合,即笛卡尔乘积02, 在界面实现  
    3、
    再议ASP.NET MVC中CheckBoxList的验证  
    4、
    ASP.NET MVC在服务端把异步上传的图片裁剪成不同尺寸分别保存,并设置上传目录的尺寸限制  
    5、
    ASP.NET MVC异步验证是如何工作的01,jQuery的验证方式、错误信息提示、validate方法的背后  
    6、
    ASP.NET MVC异步验证是如何工作的02,异步验证表单元素的创建  
    7、
    ASP.NET MVC异步验证是如何工作的03,jquery.validate.unobtrusive.js是如何工作的 
    8、
    MVC批量更新,可验证并解决集合元素不连续控制器接收不完全的问题 
    9、
    MVC扩展生成CheckBoxList并水平排列  

     

    本篇主要包括:

    商品模块小样简介
    领域模型和视图模型
    控制器和视图实现

     

    商品模块小样简介

     

    ※ 界面

    1

    ○ 类别区域,用来显示产品类别,点击选择某个类别,在"产品属性"区域出现该类别下的所有属性,以及属性值,对于单选的属性值用Select显示,对于多选的属性值用CheckBoxList显示。
    ○ 产品描述,表示数据库中产品表中的字段,当然实际情况中,这里的字段更多,比如上传时间,是否通过,产品卖点,等等。
    ○ 产品属性,只有点击选择产品类别,这里才会显示
    ○ 定价按钮,点击这个按钮,如果"产品属性"区域中有CheckBoxList项,"产品SKU与定价"区域会出现关于属性值、产品价格的SKU组合项;如果"产品属性"区域中没有CheckBoxList项,"产品SKU与定价"区域只出现一个有关价格的input元素。另外,每次点击定价按钮,出现提交按钮,定价按钮隐藏。
    ○ 产品SKU与定价:这里要么呈现属性值、价格的SKU项,要么只出现一个有关价格的input元素

     

    ※ 点击类别项,在"产品属性"区域包括CheckBoxList

    2

    ○ 点击类名中的"家电"选项,在"产品属性"区域中出现属性及其值,有些属性值以Select呈现,有些属性值以CheckBoxList呈现
    ○ 点击属性行后面的"删除行"直接删除属性行

     

    ※ 点击类别项,在"产品属性"区域包括CheckBoxList,点击"定价"按钮

    3

    点击"定价"按钮,如果每组的CheckBoxList中没有一项被选中,会在属性行后面出现错误提示。在"产品SKU与定价"区域不会出现内容。

     

    ※ 点击类别项,在"产品属性"区域包括CheckBoxList,点击"定价"按钮,再点击CheckBoxList选项,某些错误提示消失

    4

    点击CheckBoxList中的某项,该属性行后面的错误提示消失。在"产品SKU与定价"区域还是不会出现内容。

     

    ※ 点击类别项,在"产品属性"区域包括CheckBoxList,如果所有的CheckBoxList至少有一项被选中,点击"定价"按钮

    5

     

    ○ 会把所有的选中属性值进行笛卡尔乘积显示到"产品SKU与定价"区域
    ○ 出现"提交"按钮
    ○ 如果有关价格的input验证不通过会出现异步验证错误信息
    ○ 与有关价格的input一起渲染的还有一个隐藏域,用来存放该SKU项的属性值Id,以便和价格一起被保存到数据库

     

    6

     

    ※ 点击类别项,在"产品属性"区域不包括CheckBoxList

    7

    当选择类别中的"家具"项,在"产品属性"区域中的属性值只是以Select来呈现。

     

    ※ 点击类别项,在"产品属性"区域不包括CheckBoxList,点击"定价"按钮

    8

    如果"产品属性"区域中只有Select元素,点击"定价"按钮,在"产品SKU与定价"区域只出现有关价格的input,并且带异步验证,同时还出现提交按钮。

     

    ※ 在控制器提交产品的方法中打断点,点击"提交"按钮

    在界面提交的包括:

    9

     

    在控制器方法中收到了所有的提交:

    10

     

    领域模型和视图模型

     

    有关产品类别的领域模型:

        public class Category
    
        {
    
            public int Id { get; set; }
    
            public string Name { get; set; }
    
        }

     

    有关属性的领域模型:

        public class Prop
    
        {
    
            public int Id { get; set; }
    
            public string Name { get; set; }
    
            public int CategoryId { get; set; }
    
            public short InputType { get; set; }
    
            public Category Category { get; set; }
    
        }
    

     

    以上,InputType属性对应InputTypeEnum的枚举项,会依据此属性加载不同的视图(Select或CheckBoxList)。

     

        public enum InputTypeEnum
    
        {
    
            //下拉选框
    
            PropDropDownList = 0,
    
            //复选框
    
            PropCheckBoxList = 1
    
        }  
    

     

    有关属性值的领域模型:

        public class PropOption
    
        {
    
            public int Id { get; set; }
    
            public string RealValue { get; set; }
    
            public int PropId { get; set; }
    
            public Prop Prop { get; set; }
    
        }    
    

     

    在产品提交页,和产品有关包括:产品类别、产品本身的描述、属性及属性值(属性值有些以Select显示,有些以CheckBoxList显示)、属性值和价格的SKU组合项。提炼出有关产品的一个视图模型:

        public class ProductVm
    
        {
    
            public ProductVm()
    
            {
    
                this.PropOptionDs = new List<PropOptionVmD>();
    
                this.ProductSKUs = new List<ProductSKUVm>();
    
                this.PropOptionCs = new List<PropOptionVmC>();
    
            }
    
            public int Id { get; set; }
    
            [Required(ErrorMessage = "必填")]
    
            public int CategoryId { get; set; }
    
            [Required(ErrorMessage = "必填")]
    
            [Display(Name = "产品编号")]
    
            [MaxLength(10, ErrorMessage = "最大长度10")]
    
            public string Code { get; set; }
    
            [Required(ErrorMessage = "必填")]
    
            [Display(Name = "产品名称")]
    
            [MaxLength(10, ErrorMessage = "最大长度10")]
    
            public string Name { get; set; }
    
            public List<PropOptionVmD> PropOptionDs { get; set; }
    
            public List<PropOptionVmC> PropOptionCs { get; set; }
    
            public List<ProductSKUVm> ProductSKUs { get; set; }
    
        }
    

    以上,
    PropOptionDs表示以Select显示属性值的、有关属性和属性值的集合
    PropOptionCs表示以CheckBoxList显示属性值的、有关属性和属性值的集合
    ProductSKUs 表示SKU项的集合

     

    PropOptionVmD视图模型用来显示每一个属性名,该属性下的属性值是以Select呈现:

        public class PropOptionVmD
    
        {
    
            public int Id { get; set; }
    
            public int PropId { get; set; }
    
            public string PropName { get; set; }
    
            [Required(ErrorMessage = "必填")]
    
            public int PropOptionId { get; set; } 
    
        }
    

    以上,
    PropId用来表示属性Id,在界面中是以隐藏域存在的,会被传给服务端
    PropName 表示属性名,在界面中显示属性的名称
    PropOptionId 表示界面中被选中的属性值Id

     

    PropOptionVmC视图模型也用来显示每一个属性名,该属性下的属性值以CheckBoxList呈现:

        public class PropOptionVmC
    
        {
    
            public int Id { get; set; }
    
            public int PropId { get; set; }
    
            public string PropName { get; set; }
    
            public string PropOptionIds { get; set; }  
    
        }

     

    ProductSKUVm视图模型用来显示SKU项中的价格部分:

        public class ProductSKUVm
    
        {
    
            [Display(Name = "价格")]
    
            [Required(ErrorMessage = "必填")]
    
            [Range(typeof(Decimal), "0", "9999", ErrorMessage = "{0} 必须是数字介于 {1} 和 {2}之间.")]
    
            public decimal Price { get; set; } 
    
            public string  OptionIds { get; set; } 
    
        }
    

    以上,
    Price用来显示SKU项中的价格
    OptionIds用来存放SKU项中的所有属性值编号,以逗号隔开,在界面中以隐藏域存在

     

    控制器和视图实现

     

    □ HomeController

     

    当呈现Home/Index.cshtml视图的时候,HomeController应该提供一个方法,把所有的类别放在SelectListItem集合中传给前台,并返回一个有关产品视图模型强类型视图。

     

    当在界面上点击类别选项,HomeController应该有一个方法接收类别的Id,把该类别下所有的属性Id以Json格式返回给前台。

     

    当在界面上接收到一个属性Id集合,需要遍历属性Id集合,把每个属性Id传给控制器,HomeController应该有一个方法接收属性Id,在方法内部根据InputType来决定显示带Select的视图,还是带CheckBoxList的视图。

     

    当点击界面上的"定价"按钮,可能需要对属性值进行笛卡尔乘积,可能不需要,因此,HomeController应该提供2个方法,一个方法用来渲染出需要笛卡尔乘积的视图,另一个方法用来渲染不需要笛卡尔乘积的视图。

     

    当点击界面上的"提交"按钮,HomeController应该提供一个提交产品的方法,该方法接收的参数是有关产品的视图模型。

     

       public class HomeController : Controller
    
        {
    
            public ActionResult Index()
    
            {
    
                //把类别封装成SelectListItem集合传递到前台
    
                var categories = Database.GetCategories();
    
                var result = from c in categories
    
                    select new SelectListItem() {Text = c.Name, Value = c.Id.ToString()};
    
                ViewData["categories"] = result;
    
                return View(new ProductVm());
    
            }
    
            //添加产品
    
            [HttpPost]
    
            public ActionResult AddProduct(ProductVm productVm)
    
            {
    
                
    
                if (ModelState.IsValid)
    
                {
    
                    //TODO:各种保存
    
                    return Json(new { msg = true });
    
                }
    
                else
    
                {
    
                    //把类别封装成SelectListItem集合传递到前台
    
                    var categories = Database.GetCategories();
    
                    var result = from c in categories
    
                                 select new SelectListItem() { Text = c.Name, Value = c.Id.ToString() };
    
                    ViewData["categories"] = result;
    
                    return RedirectToAction("Index", productVm);
    
                }
    
            }
    
            //根据分类返回分类下的所有属性Id
    
            [HttpPost]
    
            public ActionResult GetPropIdsByCategoryId(int categoryId)
    
            {
    
                var props = Database.GetPropsByCategoryId(categoryId);
    
                List<int> propIds = props.Select(p => p.Id).ToList();
    
                return Json(propIds);
    
            }
    
            //显示属性和属性项的部分视图
    
            public ActionResult AddPropOption(int propId)
    
            {
    
                var prop = Database.GetProps().Where(p => p.Id == propId).FirstOrDefault();
    
                var propOptions = Database.GetPropOptionsByPropId(propId);
    
                if (prop.InputType == (short) InputTypeEnum.PropDropDownList)
    
                {
    
                    PropOptionVmD propOptionVmD = new PropOptionVmD();
    
                    propOptionVmD.PropId = propId;
    
                    propOptionVmD.PropName = prop.Name;
    
                    ViewData["propOptionsD"] = from p in propOptions
    
                                               select new SelectListItem() { Text = p.RealValue, Value = p.Id.ToString() };
    
                    return PartialView("_AddPropOptionD", propOptionVmD);
    
                } 
    
                else
    
                {
    
                    PropOptionVmC  propOptionVmC = new PropOptionVmC();
    
                    propOptionVmC.PropId = propId;
    
                    propOptionVmC.PropName = prop.Name;
    
                    ViewData["propOptionsC"] = from p in propOptions
    
                        select new SelectListItem() {Text = p.RealValue, Value = p.Id.ToString()};
    
                    return PartialView("_AddPropOptionC", propOptionVmC);
    
                }          
    
            }
    
            //当在前台界面上勾选CheckBoxList选项,点击"定价"按钮,就把PropAndOption集合传到这里
    
            [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("_ShowSKUs");
    
            }
    
            //不涉及属性值的笛卡尔乘积
    
            public ActionResult ShowSKUsWithoutCombination()
    
            {
    
                ViewData["v"] = null;
    
                ViewData["i"] = null;
    
                return PartialView("_ShowSKUs");
    
            }
    
        }
    

     

    □ Home/Index.cshtml视图

     

    当初次显示界面的时候,需要把"提交"按钮隐藏,把"定价"按钮显示。

     

    当点击类别下拉框的时候:
    1、清空属性区域
    2、清空SKU区域
    3、隐藏"定价"按钮,显示"提交"按钮
    4、把类别Id异步传给控制器
    5、遍历从控制器异步传回的属性Id的集合,把属性Id传给控制器,发送异步请求,返回有关产品属性和属性值的强类型部分视图,并追加到界面"产品属性"区域

     

    当点击"定价"按钮:
    1、可能"产品属性"区域有CheckBoxList
        1.1 判断每组CheckBoxList必须至少有一被勾选
        1.2 遍历每个属性行,遍历每个被勾选的项,组成类似{ propId: pId, propOptionId: oId }的数组
        1.3 把{ propId: pId, propOptionId: oId }的数组以json格式传给控制器
        1.4 异步返回的部分视图追加到界面的"产品SKU与定价"区域,并给动态加载内容实施异步验证

    2、可能"产品属性"区域没有CheckBoxList
        2.1 异步加载显示SKU组合的部分视图,只显示一个有关价格的input元素


    勾选"产品属性"区域的CheckBoxList:
    1、检查每组CheckBoxList是否满足条件,即至少有一项被选中
    2、隐藏"定价"按钮,显示"提交"按钮

     

    点击"产品属性"区域中,每行的"删除行"按钮,删除当前属性行。

     

    @model MvcApplication1.Models.ProductVm
    
    @{
    
        ViewBag.Title = "Index";
    
        Layout = "~/Views/Shared/_Layout.cshtml";
    
    }
    
    <div id="wrapper">
    
        @using (Html.BeginForm("AddProduct", "Home", FormMethod.Post, new { id = "addForm" }))
    
        {
    
            <fieldset>
    
                <legend>类别</legend>
    
                <div id="categories">
    
                    @Html.DropDownListFor(m => m.CategoryId, ViewData["categories"] as IEnumerable<SelectListItem>, "==选择类别==")
    
                    @Html.ValidationMessageFor(m => m.CategoryId)
    
                </div>
    
            </fieldset>
    
            <br />
    
            
    
            <fieldset>
    
                <legend>产品描述</legend>
    
                <div id="description">
    
                    @Html.LabelFor(m => m.Name)
    
                    @Html.TextBoxFor(m => m.Name)
    
                    @Html.ValidationMessageFor(m => m.Name)
    
                    <br />
    
                    <br />
    
                    @Html.LabelFor(m => m.Code)
    
                    @Html.TextBoxFor(m => m.Code)
    
                    @Html.ValidationMessageFor(m => m.Code)
    
                </div>
    
            </fieldset>
    
            <br />
    
            
    
            <fieldset>
    
                <legend>产品属性</legend>
    
                <ul id="props">
    
                </ul>
    
            </fieldset>
    
            <br />
    
            
    
            <input type="button" id="displaySKU" value="定价" />
    
            
    
            <br />
    
            <fieldset>
    
                <legend>产品SKU与定价</legend>
    
                <ul id="skus">
    
                </ul>
    
            </fieldset>
    
            
    
            <input type="button" id="up" value="提交" />
    
        }
    
    </div>
    
    @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 () {
    
                //提交按钮先隐藏直到点击定价按钮再显示
    
                showPriceHideUp();
    
                //点击类别下拉框
    
                $('#CategoryId').change(function () {
    
                    changeCategory();
    
                });
    
                //点击定价按钮显示SKU项,以表格显示,属性名称 属性名称 价格,
    
                //定价按钮消失,提交按钮出现
    
                //对每组CheckBoxList进行验证,保证至少有一个选项勾选
    
                $('#displaySKU').on("click", function () {
    
                    if ($('#props').find('.c').length) { //判断属性和属性值区域有没有包含CheckBoxList的li,存在
    
                        if (checkCblist()) { //如果所有CheckBoxList组都至少有一项被勾选
    
                            //遍历所有的CheckBoxList的选中项,一个属性Id带着1个或多个属性项Id
    
                            var propAndOptions = [];
    
                            //遍历所有包含CheckBoxList的li
    
                            $.each($('#props').find('.c'), function () {
    
                                //从隐藏域中获取属性Id <input type="hidden" value="" id='h_v' class='h_v'>
    
                                var pId = $(this).find('input[type=hidden]').val();
    
                                //遍历每个li中被选中的CheckBox
    
                                $.each($(this).find("input:checked"), function () {
    
                                    //获取选中值
    
                                    var oId = $(this).val();
    
                                    propAndOptions.push({ propId: pId, propOptionId: oId });
    
                                });
    
                            });
    
                            //异步提交PropAndOption集合
    
                            $.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, "#addForm");
    
                                    });
    
                                    hidePriceShowUp();
    
                                },
    
                                error: function (jqXhr, textStatus, errorThrown) {
    
                                    alert("出错了 '" + jqXhr.status + "' (状态: '" + textStatus + "', 错误为: '" + errorThrown + "')");
    
                                }
    
                            });
    
                        } else {
    
                            return;
    
                        }
    
                    } else {//判断属性和属性值区域有没有包含CheckBoxList的li,不存在
    
                        $.ajax({
    
                            cache: false,
    
                            url: '@Url.Action("ShowSKUsWithoutCombination", "Home")',
    
                            dataType: "html",
    
                            type: "GET",
    
                            success: function (data) {
    
                                $('#skus').html(data);
    
                                $.validator.unobtrusive.parseDynamicContent('.s', "#addForm");
    
                                hidePriceShowUp();
    
                            },
    
                            error: function (jqXhr, textStatus, errorThrown) {
    
                                alert("出错了 '" + jqXhr.status + "' (状态: '" + textStatus + "', 错误为: '" + errorThrown + "')");
    
                            }
    
                        });
    
                    }
    
                });
    
                //删除属性属性值行
    
                $('#props').on('click', '.delRow', function() {
    
                    $(this).parent().parent().remove();
    
                });
    
                //点击任意CheckBoxList中的选项,定价按钮出现,提交按钮隐藏
    
                $('#props').on("change", "input[type=checkbox]", function () {
    
                    //验证
    
                    checkCblist();
    
                    showPriceHideUp();
    
                });
    
                //点击提交
    
                $('#up').on("click", function () {
    
                    if (checkCblist) {
    
                        if ($('#addForm').valid()) {
    
                            $.ajax({
    
                                cache: false,
    
                                url: '@Url.Action("AddProduct", "Home")',
    
                                type: 'POST',
    
                                dataType: 'json',
    
                                data: $('#addForm').serialize(),
    
                                success: function (data) {
    
                                    if (data.msg) {
    
                                        alert('提交成功');
    
                                    }
    
                                },
    
                                error: function (xhr, status) {
    
                                    alert("添加失败,状态码:" + status);
    
                                }
    
                            });
    
                        }
    
                    } else {
    
                        alert("属性值必须勾选");
    
                    }
    
                });
    
            });
    
            //点击类别下拉框
    
            function changeCategory() {
    
                //获取选中的值
    
                var selectedValue = $('#CategoryId option:selected').val();
    
                //如果确实选中
    
                if ($.trim(selectedValue).length > 0) {
    
                    //清空属性和属性项区域
    
                    $('#props').empty();
    
                    //清空SKU区域
    
                    $('#skus').empty();
    
                    showPriceHideUp();
    
                    //异步请求属性和属性项
    
                    $.ajax({
    
                        url: '@Url.Action("GetPropIdsByCategoryId", "Home")',
    
                        data: { categoryId: selectedValue },
    
                        type: 'post',
    
                        cache: false,
    
                        async: false,
    
                        dataType: 'json',
    
                        success: function (data) {
    
                            if (data.length > 0) {
    
                                $.each(data, function (i, item) {
    
                                    $.get("@Url.Action("AddPropOption", "Home")", { propId: item }, function (result) {
    
                                        $('#props').append(result);
    
                                    });
    
                                });
    
                                }
    
                        }
    
                    });
    
                    }
    
                }
    
                //隐藏定价按钮  显示提交按钮
    
                function hidePriceShowUp() {
    
                    //隐藏定价按钮
    
                    $('#displaySKU').css("display", "none");
    
                    //显示提交按钮
    
                    $('#up').css("display", "block");
    
                }
    
                //显示定价按钮 隐藏提交按钮
    
                function showPriceHideUp(parameters) {
    
                    $('#displaySKU').css("display", "block");
    
                    $('#up').css("display", "none");
    
                }
    
                //检查每组CheckBoxList,如果没有一个选中,报错
    
                function checkCblist() {
    
                    var result = false;
    
                    //遍历每组li下的checkboxlist,如果没有一个选中,报错
    
                    $('#props li').each(function () {
    
                        if ($(this).find("input:checked").length == 0) {
    
                            $(this).find('.err').text("至少选择一项").css("color", "red");
    
                        } else {
    
                            $(this).find('.err').text("");
    
                            result = true;
    
                        }
    
                    });
    
                    return result;
    
                }
    
        </script>
    
    }
    

     

    以上,关于给为动态加载内容实施验证的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);

     

    以上,当点击产品类别,搜集"产品属性"区域中的勾选项,组成{ propId: pId, propOptionId: oId }数组的时候,这里的propIdpropOptionId键必须和PropAndOption中的属性吻合,因为在控制器方法中,接收的是List类型。

        public class PropAndOption
    
        {
    
            public int PropId { get; set; }
    
            public int PropOptionId { get; set; }
    
        }


    □  _AddPropOptionD.cshtml部分视图

     

    当点击界面上的类别选项,相应属性下的属性值以Select显示,即单选,就来加载这里的视图,并呈现到界面中的"产品属性"区域。

     

    @using MvcApplication1.Extensions
    
    @model MvcApplication1.Models.PropOptionVmD
    
    @using (Html.BeginCollectionItem("PropOptionDs"))
    
    {
    
        <li>
    
            <span>
    
                @Model.PropName:
    
            </span>
    
            <span>
    
                @Html.DropDownListFor(m => m.PropOptionId, ViewData["propOptionsD"] as IEnumerable<SelectListItem>)
    
            </span>
    
            <span>
    
                @Html.ValidationMessageFor(m => m.PropOptionId)
    
            </span>
    
            <span>
    
                @Html.HiddenFor(m => m.PropId)
    
            </span>
    
            <span>
    
                <a href="javascript:void(0)" class="delRow">删除行</a>
    
            </span>
    
        </li>
    
    }
    

    其中,Html.BeginCollectionItem("PropOptionDs")根据导航属性生成满足批量上传条件的表单元素,详细介绍在这里

     

        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();
    
            }
    
        }
    


    □ _AddPropOptionC.cshtml部分视图

     

    当点击界面上的类别选项,相应属性下的属性值以CheckBoxList显示,即多选,就来加载这里的视图,并呈现到界面中的"产品属性"区域。

    @using MvcApplication1.Extensions
    
    @model MvcApplication1.Models.PropOptionVmC
    
    @using (Html.BeginCollectionItem("PropOptionCs"))
    
    {
    
        <li class="c">
    
            <span>
    
                @Model.PropName:
    
            </span>
    
            <span>
    
                @Html.CheckBoxList("PropOptionIds",ViewData["propOptionsC"] as IEnumerable<SelectListItem>,null, 10)
    
                <span class="err"></span>
    
            </span>
    
            <span>
    
                @Html.HiddenFor(m => m.PropId)
    
            </span>
    
            <span>
    
                <a href="javascript:void(0)" class="delRow">删除行</a>
    
            </span>
    
        </li>
    
    }
    

     

    其中,CheckBoxList是基于HtmlHelper的扩展方法,用来呈现水平或垂直分布的CheckBoxList,详细介绍在这里

     

    using System.Collections.Generic;
    
    using System.Linq;
    
    using System.Text;
    
    namespace System.Web.Mvc
    
    {
    
        public static class InputExtensions
    
        {
    
            #region 水平方向CheckBoxList
    
            /// <summary>
    
            /// 生成水平方向的CheckBoxList
    
            /// </summary>
    
            /// <param name="htmlHelper"></param>
    
            /// <param name="name">name属性值</param>
    
            /// <param name="htmlAttributes">属性和属性值的键值对集合</param>
    
            /// <param name="number">每行显示的个数</param>
    
            /// <returns></returns>
    
            public static MvcHtmlString CheckBoxList(this HtmlHelper htmlHelper,
    
                string name,
    
                IEnumerable<SelectListItem> listInfo,
    
                IDictionary<string, object> htmlAttributes,
    
                int number)
    
            {
    
                //name属性值必须有
    
                if (string.IsNullOrEmpty(name))
    
                {
    
                    throw new ArgumentException("必须给CheckBoxList一个name值", "name");
    
                }
    
                //数据源SelectListItem的集合必须有
    
                if (listInfo == null)
    
                {
    
                    throw new ArgumentNullException("listInfo", "List<SelectListItem>类型的listInfo参数不能为null");
    
                }
    
                //数据源中必须有数据
    
                if (!listInfo.Any())
    
                {
    
                    throw new ArgumentException("List<SelectListItem>类型的listInfo参数必须有数据", "listInfo");
    
                }
    
                //准备拼接
    
                var sb = new StringBuilder();
    
                //每行CheckBox开始数数
    
                var lineNumber = 0;
    
                //遍历数据源
    
                foreach (var info in listInfo)
    
                {
    
                    lineNumber++;
    
                    //创建type=checkbox的input
    
                    var builder = new TagBuilder("input");
    
                    //tag设置属性
    
                    if (info.Selected)
    
                    {
    
                        builder.MergeAttribute("checked", "checked");
    
                    }
    
                    builder.MergeAttributes(htmlAttributes);
    
                    builder.MergeAttribute("type", "checkbox");
    
                    builder.MergeAttribute("value", info.Value);
    
                    builder.MergeAttribute("name", name);
    
                    builder.MergeAttribute("id", string.Format("{0}_{1}", name, info.Value));
    
                    sb.Append(builder.ToString(TagRenderMode.Normal));
    
                    //创建checkbox的显示值
    
                    var lableBuilder = new TagBuilder("label");
    
                    lableBuilder.MergeAttribute("for", string.Format("{0}_{1}", name, info.Value));
    
                    lableBuilder.InnerHtml = info.Text;
    
                    sb.Append(lableBuilder.ToString(TagRenderMode.Normal));
    
                    //如果设置的每行数量刚好被当前数量整除就换行
    
                    if (lineNumber == 0 || (lineNumber % number == 0))
    
                    {
    
                        sb.Append("<br />");
    
                    }
    
                }
    
                return MvcHtmlString.Create(sb.ToString());
    
            }
    
            /// <summary>
    
            /// 重载,不包含属性和属性值键值对的集合
    
            /// </summary>
    
            /// <param name="htmlHelper"></param>
    
            /// <param name="name">name的属性值</param>
    
            /// <param name="listInfor">SelectListItem集合类型的数据源</param>
    
            /// <returns></returns>
    
            public static MvcHtmlString CheckBoxList(this HtmlHelper htmlHelper,
    
                string name,
    
                IEnumerable<SelectListItem> listInfor)
    
            {
    
                return htmlHelper.CheckBoxList(name, listInfor, null, 5);
    
            } 
    
            #endregion
    
            #region 垂直方向CheckBoxList
    
            public static MvcHtmlString CheckBoxListVertical(this HtmlHelper htmlHelper,
    
                string name,
    
                IEnumerable<SelectListItem> listInfo,
    
                IDictionary<string, object> htmlAttributes,
    
                int columnNumber = 1)
    
            {
    
                //name属性值不能为null
    
                if (string.IsNullOrEmpty(name))
    
                {
    
                    throw new ArgumentException("必须给CheckBoxList的name属性赋值","name");
    
                }
    
                //数据源不能为null
    
                if (listInfo == null)
    
                {
    
                    throw new ArgumentNullException("listInfo","List<SelectListItem>类型的listInfo参数不能为null");
    
                }
    
                //数据源中必须有数据
    
                if (!listInfo.Any())
    
                {
    
                    throw new ArgumentException("List<SelectListItem>类型的参数listInfo必须有数据","listInfo");
    
                }
    
                //数据源数据项的数量
    
                var dataCount = listInfo.Count();
    
                //得到行数
    
                var rows = Convert.ToInt32(Math.Ceiling(Convert.ToDecimal(dataCount) / Convert.ToDecimal(columnNumber)));
    
                //创建div
    
                var wrapBuilder = new TagBuilder("div");
    
                wrapBuilder.MergeAttribute("style", "float:left; line-height:25px; padding-right:5px;");
    
                var wrapStart = wrapBuilder.ToString(TagRenderMode.StartTag);
    
                var wrapClose = string.Concat(wrapBuilder.ToString(TagRenderMode.EndTag),
    
                    " <div style="clear:both;"></div>");
    
                var wrapBreak = string.Concat("</div>", wrapBuilder.ToString(TagRenderMode.StartTag));
    
                var sb = new StringBuilder();
    
                sb.Append(wrapStart);
    
                var lineNumber = 0;
    
                //遍历数据源
    
                foreach (var info in listInfo)
    
                {
    
                    var builder = new TagBuilder("input");
    
                    if (info.Selected)
    
                    {
    
                        builder.MergeAttribute("checked", "checked");
    
                    }
    
                    builder.MergeAttributes(htmlAttributes);
    
                    builder.MergeAttribute("type", "checkbox");
    
                    builder.MergeAttribute("value", info.Value);
    
                    builder.MergeAttribute("name", name);
    
                    builder.MergeAttribute("id", string.Format("{0}_{1}", name, info.Value));
    
                    sb.Append(builder.ToString(TagRenderMode.Normal));
    
                    var labelBuilder = new TagBuilder("label");
    
                    labelBuilder.MergeAttribute("for", string.Format("{0}_{1}", name, info.Value));
    
                    labelBuilder.InnerHtml = info.Text;
    
                    sb.Append(labelBuilder.ToString(TagRenderMode.Normal));
    
                    lineNumber++;
    
                    if (lineNumber.Equals(rows))
    
                    {
    
                        sb.Append(wrapBreak);
    
                        lineNumber = 0;
    
                    }
    
                    else
    
                    {
    
                        sb.Append("<br />");
    
                    }
    
                }
    
                sb.Append(wrapClose);
    
                return MvcHtmlString.Create(sb.ToString());
    
            }
    
            #endregion
    
        }
    
    }
    

     

    □ _ShowSKUs.cshtml部分视图

     

    当点击界面上的"定价"按钮,就来加载这个视图,如果属性值笛卡尔乘积为null,那就只显示一个有关价格的input元素。如果确实存在属性值笛卡尔乘积,就遍历这些SKU项显示出来,并且,每遍历一次,就去加载有关产品价格的强类型部分视图。

    @if (ViewData["v"] == null)
    
    {
    
        <li>
    
            <span class="s">
    
                @{
    
                    ProductSKUVm productSkuVm = new ProductSKUVm();
    
                    productSkuVm.OptionIds = "";
    
                    Html.RenderPartial("_SKUDetail", productSkuVm);
    
                }
    
            </span>
    
        </li>
    
    }
    
    else
    
    {
    
        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">
    
                @{
    
                    ProductSKUVm productSkuVm = new ProductSKUVm();
    
                    productSkuVm.OptionIds = ids[@i];
    
                    Html.RenderPartial("_SKUDetail", productSkuVm);
    
                }
    
            </span>
    
        </li>
    
        }
    
    }

     

    □ _SKUDetail.cshtml强类型部分视图

     

    作为_ShowSKUs.cshtml部分视图的子部分视图,用来显示产品价格相关的强类型部分视图。

    @using MvcApplication1.Extensions
    
    @model MvcApplication1.Models.ProductSKUVm
    
    @using (Html.BeginCollectionItem("ProductSKUs"))
    
    {
    
         @Html.TextBoxFor(m => m.Price)
    
         @Html.ValidationMessageFor(m => m.Price)
    
         @Html.HiddenFor(m => m.OptionIds)
    
    }
    

     

    □ 模拟数据库存储的Database类

     

    展开

     

    结束!

     

     

     

     

     

     

     

     

     

     

     

  • 相关阅读:
    java加密算法-MD5
    java加密算法-DES
    java加密算法-AES
    java写入内容到本地文件 -读取文件内容
    java 图片base64互转
    java上传文件
    判断请求是否是同一个域名
    java计算两个经纬度之间的距离
    java请求url可以带参数
    Java编程基础篇第五章
  • 原文地址:https://www.cnblogs.com/darrenji/p/4110219.html
Copyright © 2011-2022 走看看