摘要:

在这篇文章中,我将继续完成SportsStore应用程序,让站点管理者可以管理产品列表。我将添加创建、修改和删除产品功能。

本篇文章将分模块的方式,逐个介绍SportsStore站点管理功能的开发过程。

数据管理部分

修改IProductRepository.cs接口代码文件。

 using SportsStore.Domain.Entities;
using System.Collections.Generic; namespace SportsStore.Domain.Abstract
{
public interface IProductRepository
{
IEnumerable<Product> Products { get; } void SaveProduct(Product product); Product DeleteProduct(int productID);
}
}

添加了两个接口方法SaveProduct和DeleteProduct。

修改EFProductRepository.cs代码文件,添加这两个新添加的接口的实现。

 using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using System.Collections.Generic; namespace SportsStore.Domain.Concrete
{
public class EFProductRepository : IProductRepository
{
private EFDbContext context = new EFDbContext();
public IEnumerable<Product> Products
{
get
{
try
{
return context.Products;
}
catch (System.Exception e)
{
return null;
}
}
} public void SaveProduct(Product product)
{
if (product.ProductID == )
{
context.Products.Add(product);
}
else
{
Product dbEntry = context.Products.Find(product.ProductID);
if (dbEntry != null)
{
dbEntry.Name = product.Name;
dbEntry.Description = product.Description;
dbEntry.Price = product.Price;
dbEntry.Category = product.Category;
}
}
context.SaveChanges();
} public Product DeleteProduct(int productID)
{
Product dbEntry = context.Products.Find(productID);
if (dbEntry != null)
{
context.Products.Remove(dbEntry);
context.SaveChanges();
}
return dbEntry;
}
}
}

这两个方法SaveProduct和方法DeleteProduct,调用EntifyFramework,实现了保存(添加和修改)和删除产品功能。

产品管理控制器

添加控制器AdminController,此控制器区别于只用于显示产品的网站首页控制器ProductController,实现了增删改Action方法。

 using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using System.Linq;
using System.Web.Mvc; namespace SportsStore.WebUI.Controllers
{
public class AdminController : Controller
{
private IProductRepository repository; public AdminController(IProductRepository repo)
{
repository = repo;
} public ViewResult Index()
{
return View(repository.Products);
} public ViewResult Edit(int productId)
{
Product product = repository.Products.FirstOrDefault(p => p.ProductID == productId);
return View(product);
} [HttpPost]
public ActionResult Edit(Product product)
{
if (ModelState.IsValid)
{
repository.SaveProduct(product);
TempData["message"] = string.Format("{0} has been saved", product.Name);
return RedirectToAction("Index");
}
else
{
// there is something wrong with the data values
return View(product);
}
} public ViewResult Create()
{
return View("Edit", new Product());
} [HttpPost]
public ActionResult Delete(int productId)
{
Product deletedProduct = repository.DeleteProduct(productId);
if (deletedProduct != null)
{
TempData["message"] = string.Format("{0} was deleted", deletedProduct.Name);
}
return RedirectToAction("Index");
}
}
}
  • 以Ninject构造函数注入方式生成属性IProductRepository repository。
  • Index方法Action:返回产品列表
  • Edit方法Action(Get方式):传入productId参数,返回带有这个产品信息的Edit视图。Edit视图的视图模型类是Product类型。
  • Edit方法Action(Post方式):从视图上通过模型绑定返回产品product参数,如果绑定的模型验证结果返回的ModelState.IsValid为true,则保存修改后的product信息,使用Temp对象在页面上显示保存成功字符串,并返回产品列表视图。否则,返回原视图(Edit视图),并向视图传入正在编辑的产品对象,用户可以继续编辑该产品。
  • Create方法Action:重用了Edit方法Action。返回Edit方法对应的视图,使用new Product()传入一个初始化的产品对象。
  • Delete方法Action:使用了Post特性修饰,表示只能通过Post方式调用该Action,比Get方式更加安全。删除成功后,使用Temp对象在页面上显示删除成功字符串。
  • 使用Temp对象保存和显示操作成功的字符串,而不能使用ViewData,因为ViewData只能在当前Action上使用。这里使用了RedirectToAction跳转到了另一个视图。

视图

在Shared文件夹中,添加_AdminLayout.cshtml视图。

 <!DOCTYPE html>

 <html>
<head>
<meta name="viewport" content="width=device-width" />
<link href="~/Content/bootstrap.css" rel="stylesheet" />
<link href="~/Content/bootstrap-theme.css" rel="stylesheet" />
<title>@ViewBag.Title</title>
</head>
<body>
<div class="navbar navbar-inverse" role="navigation">
<a class="navbar-brand" href="#">
<span class="hidden-xs">SPORTS STORE</span>
<div class="visible-xs">SPORTS</div>
<div class="visible-xs">STORE</div>
</a>
</div>
<div>
@if (TempData["message"] != null)
{
<div class="alert alert-success">@TempData["message"]</div>
}
@RenderBody()
</div>
</body>
</html>

_AdminLayout.cshtml的页面布局跟网站首页的布局类似。只是在Body的上部增加了显示操作成功的字符串的DIV元素。

产品管理的Index.cshtml视图

 @model IEnumerable<SportsStore.Domain.Entities.Product>

 @{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_AdminLayout.cshtml";
} <div class="panel panel-default">
<div class="panel-heading">
<h3>All Products</h3>
</div>
<div class="panel-body">
<table class="table table-striped table-condensed table-bordered">
<tr>
<th class="text-right">ID</th>
<th>Name</th>
<th class="text-right">Price</th>
<th class="text-center">Actions</th>
</tr>
@foreach (var item in Model)
{
<tr>
<td class="text-right">@item.ProductID</td>
<td>
@Html.ActionLink(item.Name, "Edit", new
{
item.ProductID
})
</td>
<td class="text-right">@item.Price.ToString("c")</td>
<td class="text-center">
@using (Html.BeginForm("Delete", "Admin"))
{
@Html.Hidden("ProductID", item.ProductID)
<input type="submit" class="btn btn-default btn-xs" value="Delete" />
}
</td>
</tr>
}
</table>
</div>
<div class="panel-footer">
@Html.ActionLink("Add a new product", "Create", null, new { @class = "btn btn-default" })
</div>
</div>
  • 该视图通过语句:Layout = "~/Views/Shared/_AdminLayout.cshtml";,指定它的布局视图是刚才创建的_AdminLayout.cshtml。
  • 该视图通过表格的形式呈现了产品列表。显示产品名称的列是一个指向Edit方法Action的链接。每一行的最后一列放置一个删除产品的表单,表达内容是一个保存ProductID信息的隐藏元素和一个删除按钮。
  • 页面底部显示一个创建产品的超链接,该链接通过css样式呈现成按钮样式。

最后是Edit.cshtml视图

 @model SportsStore.Domain.Entities.Product

 @{
ViewBag.Title = "Admin: Edit " + @Model.Name;
Layout = "~/Views/Shared/_AdminLayout.cshtml";
} <h1>Edit @Model.Name</h1>
@using (Html.BeginForm("Edit", "Admin", FormMethod.Post))
{
<div class="panel-body">
@Html.HiddenFor(m => m.ProductID)
@foreach (var property in ViewData.ModelMetadata.Properties)
{
switch (property.PropertyName)
{
case "ProductID":
break;
default:
<div class="form-group">
<label>
@(property.DisplayName ?? property.PropertyName)
</label>
@if (property.PropertyName == "Description")
{
@Html.TextArea(property.PropertyName, null, new { @class = "form-control", rows = 5 })
}
else
{
@Html.TextBox(property.PropertyName, null, new { @class = "form-control" })
}
</div>
break;
}
}
</div>
<div class="panel-footer">
<input type="submit" value="Save" class="btn btn-primary" />
@Html.ActionLink("Cancel and return to List", "Index", null, new { @class = "btn btn-default" })
</div>
}
  • 首先使用@Html.HiddenFor(m => m.ProductID)向页面发送保存有ProductID信息的隐藏元素。
  • 使用ViewData.ModelMetadata.Properties,返回视图绑定类的所有属性。这里是Product类的所有属性。
  • 如果该属性名是ProductID(ProductID属性),则不生成表单HTML元素。如果该属性是Description属性,则显示成一个TextArea,通过@class指定他的行数。否则,只显示成一个普通的text输入框。
  • @(property.DisplayName ?? property.PropertyName)用于显示输入元素前面的Label元素。如果属性使用了Display特性指定了属性显示在页面上的字符串,则显示这个字符串。否则,只显示这个属性名。这个对于使用了多语言的系统非常有用。

运行程序,得到运行结果。

点击任意一个产品的链接,返回Edit视图。

如果点击按钮Add a new product,返回Create视图。

添加视图模型验证

修改Product.cs类。

 using System.ComponentModel.DataAnnotations;

namespace SportsStore.Domain.Entities
{
public class Product
{
public int ProductID { get; set; }
[Required(ErrorMessage = "Please enter a product name")]
public string Name { get; set; }
[Required(ErrorMessage = "Please enter a description")]
public string Description { get; set; }
[Required]
[Range(0.01, double.MaxValue, ErrorMessage = "Please enter a positive price")]
public decimal Price { get; set; }
[Required(ErrorMessage = "Please specify a category")]
public string Category { get; set; }
}
}

需要引入名称空间System.ComponentModel.DataAnnotations;。该名称空间下包含了数量庞大的模型验证特性类。这里只使用了Required和Range。

继续编辑Edit.cshtml视图,将验证结果字符串显示在视图页面上。

 @model SportsStore.Domain.Entities.Product

 @{
ViewBag.Title = "Admin: Edit " + @Model.Name;
Layout = "~/Views/Shared/_AdminLayout.cshtml";
} <h1>Edit @Model.Name</h1>
@using (Html.BeginForm("Edit", "Admin", FormMethod.Post))
{
<div class="panel-body">
@Html.HiddenFor(m => m.ProductID)
@foreach (var property in ViewData.ModelMetadata.Properties)
{
switch (property.PropertyName)
{
case "ProductID":
break;
default:
<div class="form-group">
<label>
@(property.DisplayName ?? property.PropertyName)
</label>
@if (property.PropertyName == "Description")
{
@Html.TextArea(property.PropertyName, null, new { @class = "form-control", rows = 5 })
}
else
{
@Html.TextBox(property.PropertyName, null, new { @class = "form-control" })
}
</div>
@Html.ValidationMessage(property.PropertyName)
break;
}
}
</div>
<div class="panel-footer">
<input type="submit" value="Save" class="btn btn-primary" />
@Html.ActionLink("Cancel and return to List", "Index", null, new { @class = "btn btn-default" })
</div>
}

这里通过在每个表单元素所在的DIV下面,使用语句@Html.ValidationMessage(property.PropertyName)返回表单验证结果。

还要修改_AdminLayout.cshtml,引入验证出现错误时所用的CSS样式表。

 <!DOCTYPE html>

 <html>
<head>
<meta name="viewport" content="width=device-width" />
<link href="~/Content/bootstrap.css" rel="stylesheet" />
<link href="~/Content/bootstrap-theme.css" rel="stylesheet" />
<link href="~/Content/ErrorStyles.css" rel="stylesheet" />
<title>@ViewBag.Title</title>
</head>
<body>
<div class="navbar navbar-inverse" role="navigation">
<a class="navbar-brand" href="#">
<span class="hidden-xs">SPORTS STORE</span>
<div class="visible-xs">SPORTS</div>
<div class="visible-xs">STORE</div>
</a>
</div>
<div>
@if (TempData["message"] != null)
{
<div class="alert alert-success">@TempData["message"]</div>
}
@RenderBody()
</div>
</body>
</html>

运行程序,访问/Admin/Edit视图。如果清空表单元素,则返回验证失败的Edit视图。错误消息显示在每个表单元素的下面。

如果表单元素填写正确

点击Save按钮,则保存成功。并返回Index视图,在Index视图上看到新创建的产品。在产品列表上部,显示保存成功的字符串。

如果在Index视图上,删除Daniel这个产品。删除成功后,返回Index视图。

为表单添加客户端验证

现在使用的是服务器端验证。也就是说验证必须是发送到服务器端完成的。可以使用客户端验证方式,加快页面的访问速度。

首先使用NutGet向SportsStore.WebUI工程,添加javascript包Microsoft.jQuery.Unobtrusive.Validation 。

它将同时安装jquery.validate和jquery.validate.unobtrusive。

安装完成后的scrips文件夹内容是这样的。

修改_AdminLayout.cshtml视图,添加对新的JavaScript引用。

 <!DOCTYPE html>

 <html>
<head>
<meta name="viewport" content="width=device-width" />
<link href="~/Content/bootstrap.css" rel="stylesheet" />
<link href="~/Content/bootstrap-theme.css" rel="stylesheet" />
<link href="~/Content/ErrorStyles.css" rel="stylesheet" />
<script src="~/Scripts/jquery-1.9.1.js"></script>
<script src="~/Scripts/jquery.validate.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>
<title>@ViewBag.Title</title>
</head>
<body>
<div class="navbar navbar-inverse" role="navigation">
<a class="navbar-brand" href="#">
<span class="hidden-xs">SPORTS STORE</span>
<div class="visible-xs">SPORTS</div>
<div class="visible-xs">STORE</div>
</a>
</div>
<div>
@if (TempData["message"] != null)
{
<div class="alert alert-success">@TempData["message"]</div>
}
@RenderBody()
</div>
</body>
</html>

运行程序,在/Admin/Create视图上,在空的表单元素页面上点击Save按钮,发现新的表达验证直接在客户端完成了。

你也可以修改Web.config文件里的ClientValidationEnabled属性和UnobtrusiveJavaScriptEnabled属性,将他们都改为false(默认值都为true),来禁用客户端验证(变成服务器端验证)。

添加Admin路由规则

这时候,如果我们访问URL:/Admin,将继续返回Product控制器的List视图,将Admin字符串作为category参数传入List方法Action。而此时,我们希望访问/Admin/Index视图。

此时我们需要修改RouteConfig.cs代码文件的方法RegisterRoutes,向路由表头部插入新的路由规则(新的路由规则代码放在第二个位置)。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing; namespace SportsStore
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute(
name: null,
url: "",
defaults: new { controller = "Product", action = "List", category = (string)null, page = }
); routes.MapRoute(
name: null,
url: "Admin",
defaults: new { controller = "Admin", action = "Index" }
);

routes.MapRoute(
name: null,
url: "Page{page}",
defaults: new { controller = "Product", action = "List", category = (string)null },
constraints: new { page = @"\d+" }
); routes.MapRoute(
name: null,
url: "{category}",
defaults: new { controller = "Product", action = "List", page = }
); routes.MapRoute(
name: null,
url: "{category}/Page{page}",
defaults: new { controller = "Product", action = "List" },
constraints: new { page = @"\d+" }
); routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Product", action = "List", id = UrlParameter.Optional }
);
}
}
}

新的路由规则的url是Admin,控制器是AdminController,Action是Index方法。

这时运行程序,访问/Admin页面,将得到/Admin/Index视图。

而其他的路由规则均不受影响。

跟我学ASP.NET MVC之九:SportsStrore产品管理的更多相关文章

  1. 跟我学ASP.NET MVC之五:SportsStrore开始

    摘要: 这篇文章将介绍一个ASP.NET应用程序SportsStore的开发过程. 开始 创建解决方案 创建工程 在New ASP.NET Project - SportsStore窗口中,选择Emp ...

  2. 跟我学ASP.NET MVC之八:SportsStrore移动设备

    摘要: 现在的web程序开发避免不了智能手机和平板电脑上的使用,如果你希望发布你的应用程序给更广大客户使用的话,你将要拥抱可移动web浏览器的世界.向移动设备用户发布一个好的使用体验是很困难的-比只是 ...

  3. 跟我学ASP.NET MVC之六:SportsStrore添加产品目录导航

    摘要: 上一篇文章,我建立了SportsStore应用程序的核心架构.现在我将使用这个架构向这个应用程序添加功能,你将开始看到这个基础架构的作用.我将添加重要的面向客户的简单功能,在这个过程中,你将看 ...

  4. 跟我学ASP.NET MVC之七:SportsStrore一个完整的购物车

    摘要: SportsStore应用程序进展很顺利,但是我不能销售产品直到设计了一个购物车.在这篇文章里,我就将创建一个购物车. 在目录下的每个产品旁边添加一个添加到购物车按钮.点击这个按钮将显示客户到 ...

  5. bootstrap-data-target触发模态弹出窗元素的data使用 data-toggle与data-target的作用 深入ASP.NET MVC之九:Ajax支持 Asp.Net MVC4系列--进阶篇之AJAX

    bootstrap-data-target触发模态弹出窗元素的data使用 时间:2017-05-27 14:22:34      阅读:4479      评论:0      收藏:0      [ ...

  6. [转]ASP.NET MVC 4 (九) 模型绑定

    本文转自:http://www.cnblogs.com/duanshuiliu/p/3706701.html 模型绑定指的是MVC从浏览器发送的HTTP请求中为我们创建.NET对象,在HTTP请求和C ...

  7. 跟我学ASP.NET MVC之三:完整的ASP.NET MVC程序-PartyInvites

    摘要: 在这篇文章中,我将在一个例子中实际地展示MVC. 场景 假设一个朋友决定举办一个新年晚会,她邀请我创建一个用来邀请朋友参加晚会的WEB程序.她提出了四个注意的需求: 一个首页展示这个晚会 一个 ...

  8. 跟我学ASP.NET MVC之二:第一个ASP.NET MVC程序

    摘要: 本篇文章带你一步一步创建一个简单的ASP.NET MVC程序.  创建新ASP.NET MVC工程 点击“OK”按钮后,打开下面的窗口: 这里选择“Empty”模板以及“MVC”选项.这次不创 ...

  9. 跟我学ASP.NET MVC之一:开篇有益

    摘要: ASP.NET MVC是微软的Web开发框架,结合了模型-视图-控制器(MVC)架构的有效性和整洁性,敏捷开发最前沿的思想和技术,以及现存的ASP.NET平台最好的部分.它是传统ASP.NET ...

随机推荐

  1. [D3] Convert Input Data to Output Values with Linear Scales in D3

    Mapping abstract values to visual representations is what data visualization is all about, and that’ ...

  2. session了解及超时处理

    Session了解 Session是什么 引言     在web开发中,session是个非常重要的概念.在许多动态网站的开发者看来,session就是一个变量,而且其表现像个黑洞,他只需要将东西在合 ...

  3. PatentTips - Sprite Graphics Rendering System

    BACKGROUND This disclosure relates generally to the field of computer graphics. More particularly, b ...

  4. Android中的动画具体解释系列【3】——自己定义动画研究

    在上一篇中我们使用到了位移动画TranslateAnimation,以下我们先来看看TranslateAnimation是怎样实现Animation中的抽象方法的: /* * Copyright (C ...

  5. 某整形数组中除了两个单身整数外, 其余的整数都是成对出现的, 利用C/C++代码求出这两个单身整数。 要求: 时间复杂度o(n), 空间复杂度o(1)------某公司招聘试题

    先看看这个题目:某整形数组中除了两个单身整数外, 其余的整数都是成对出现的, 利用C代码求出这两个单身整数. 要求: 时间复杂度o(n), 空间复杂度o(1). 我们先用最傻瓜的方式来做吧: #inc ...

  6. Hdu4771(杭州赛区)

    Stealing Harry Potter's Precious Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 ...

  7. 关于ulimit -a中需要修改的两个值

    以root用户运行 ulimit -a 命令,其中有两个参数分别为: open files和max user processes   修改方法:  vi /etc/security/limits.co ...

  8. 图标插件--jqplot实现柱状图及饼图,表盘图演示样例

    柱状图 在jqPlot图表插件使用说明(一)中,我们已经能够通过jqPlot绘制出比較简单的线形图.通过查看源码.我们也能够看出,线形图是jqPlot默认的图表类型: /** * Class: Ser ...

  9. .net core 下的分布式事务锁

    原文:.net core 下的分布式事务锁 目录 系统分布式锁的用法 锁的实现 锁的使用 API内的范例: 引用链接 系统分布式锁的用法 公司框架新增功能分布式锁: 锁的性能之王: 缓存 > Z ...

  10. java命名规则(转)

    1. JAVA源文件的命名 JAVA源文件名必须和源文件中所定义的类的类名相同. 2. Package的命名 Package名的第一部分应是小写ASCII字符,并且是顶级域名之一,通常是com.edu ...