在前面的几篇文章中,已经在控制台和界面实现了属性值的笛卡尔乘积,这是商品模块中的一个难点。本篇就来实现在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并水平排列 本篇主要包括: □ 商品模块小样简介 □ 领域模型和视图模型 □ 控制器和视图实现 商品模块小样简介
※ 界面 ○ 类别区域,用来显示产品类别,点击选择某个类别,在"产品属性"区域出现该类别下的所有属性,以及属性值,对于单选的属性值用Select显示,对于多选的属性值用CheckBoxList显示。 ○ 产品描述,表示数据库中产品表中的字段,当然实际情况中,这里的字段更多,比如上传时间,是否通过,产品卖点,等等。 ○ 产品属性,只有点击选择产品类别,这里才会显示 ○ 定价按钮,点击这个按钮,如果"产品属性"区域中有CheckBoxList项,"产品SKU与定价"区域会出现关于属性值、产品价格的SKU组合项;如果"产品属性"区域中没有CheckBoxList项,"产品SKU与定价"区域只出现一个有关价格的input元素。另外,每次点击定价按钮,出现提交按钮,定价按钮隐藏。 ○ 产品SKU与定价:这里要么呈现属性值、价格的SKU项,要么只出现一个有关价格的input元素 ※ 点击类别项,在"产品属性"区域包括CheckBoxList ○ 点击类名中的"家电"选项,在"产品属性"区域中出现属性及其值,有些属性值以Select呈现,有些属性值以CheckBoxList呈现 ○ 点击属性行后面的"删除行"直接删除属性行 ※ 点击类别项,在"产品属性"区域包括CheckBoxList,点击"定价"按钮 点击"定价"按钮,如果每组的CheckBoxList中没有一项被选中,会在属性行后面出现错误提示。在"产品SKU与定价"区域不会出现内容。 ※ 点击类别项,在"产品属性"区域包括CheckBoxList,点击"定价"按钮,再点击CheckBoxList选项,某些错误提示消失 点击CheckBoxList中的某项,该属性行后面的错误提示消失。在"产品SKU与定价"区域还是不会出现内容。 ※ 点击类别项,在"产品属性"区域包括CheckBoxList,如果所有的CheckBoxList至少有一项被选中,点击"定价"按钮 ○ 会把所有的选中属性值进行笛卡尔乘积显示到"产品SKU与定价"区域 ○ 出现"提交"按钮 ○ 如果有关价格的input验证不通过会出现异步验证错误信息 ○ 与有关价格的input一起渲染的还有一个隐藏域,用来存放该SKU项的属性值Id,以便和价格一起被保存到数据库 ※ 点击类别项,在"产品属性"区域不包括CheckBoxList 当选择类别中的"家具"项,在"产品属性"区域中的属性值只是以Select来呈现。 ※ 点击类别项,在"产品属性"区域不包括CheckBoxList,点击"定价"按钮 如果"产品属性"区域中只有Select元素,点击"定价"按钮,在"产品SKU与定价"区域只出现有关价格的input,并且带异步验证,同时还出现提交按钮。 ※ 在控制器提交产品的方法中打断点,点击"提交"按钮 在界面提交的包括: 在控制器方法中收到了所有的提交: 领域模型和视图模型
有关产品类别的领域模型:
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);
|
请发表评论