http://www.cnblogs.com/powertoolsteam/p/MVC_five.html
系列文章
七天学会ASP.NET MVC (一)——深入理解ASP.NET MVC
七天学会ASP.NET MVC (二)——ASP.NET MVC 数据传递
七天学会ASP.NET MVC (三)——ASP.Net MVC 数据处理
七天学会ASP.NET MVC (五)——Layout页面使用和用户角色管理
七天学会ASP.NET MVC (六)——线程问题、异常处理、自定义URL
目录
实验22——添加页脚
实验23——实现用户角色管理
实验24——实现项目外观一致性
实验25——使用Action 过滤器让页眉和页脚代码更有效
总结
实验22——添加页脚
在本实验中,我们会在Employee 页面添加页脚,通过本实验理解分部视图。
什么是“分部视图”?
从逻辑上看,分部视图是一种可重用的视图,不会直接显示,包含于其他视图中,作为其视图的一部分来显示。用法与用户控件类似,但不需要编写后台代码。
1. 创建分部视图的 ViewModel
右击 ViewModel 文件夹,新建 FooterViewModel 类,如下:
1: public class FooterViewModel
2: {
3: public string CompanyName { get; set; }
4: public string Year { get; set; }
5: }
2. 创建分部视图
右击“~/Views/Shared”文件夹,选择添加->视图。
输入View 名称”Footer”,选择复选框“Create as a partial view”,点击添加按钮。
注意:View中的Shared 共享文件夹是为每个控制器都可用的文件夹,不是某个特定的控制器所属。
3. 在分部View 中显示数据
打开Footer.cshtml,输入以下HTML 代码。
1: @using WebApplication1.ViewModels
2:
3: @model FooterViewModel
4:
5: <div style="text-align:right;background-color: silver;color: darkcyan;border: 1px solid gray;margin-top:2px;padding-right:10px;">
6:
7: @Model.CompanyName © @Model.Year
8:
9: </div>
4. 在Main ViewModel 中包含Footer 数据
打开 EmployeeListViewModel 类,添加新属性,保存 Footer数据,如下:
1: public class EmployeeListViewModel
2: {
3: public List<EmployeeViewModel> Employees { get; set; }
4:
5: public string UserName { get; set; }
6:
7: public FooterViewModel FooterData { get; set; }//New Property
8: }
在本实验中Footer会作为Index View的一部分显示,因此需要将Footer的数据传到Index View页面中。Index View 是EmployeeListViewModel的强类型View,因此Footer需要的所有数据都应该封装在 EmployeeListViewModel中。
5. 设置Footer 数据
打开 EmployeeController ,在Index action 方法中设置FooterData 属性值,如下:
1: public ActionResult Index()
2: {
3: ...
4: ...
5: employeeListViewModel.FooterData = new FooterViewModel();
6: employeeListViewModel.FooterData.CompanyName = "StepByStepSchools";//Can be set to dynamic value
7: employeeListViewModel.FooterData.Year = DateTime.Now.Year.ToString();
8: return View("Index", employeeListViewModel);
9: }
6. 显示Footer
打开Index.cshtml 文件,在Table 标签后显示Footer 分部View,如下:
1: </table>
2: @{
3: Html.RenderPartial("Footer", Model.FooterData);
4: }
5: </div>
6: </body>
7: </html>
7. 运行,打开Index View
关于实验22
Html.Partial的作用是什么?与Html.RenderPartial区别是什么?
与Html.RenderPartial作用相同,Html.Partial会在View 中用来显示分部View。
Html.RenderPartial会将分部View的结果直接写入HTTP 响应流中,而 Html.Partial会返回 MvcHtmlString值。
什么是MvcHtmlString,为什么 Html.Partial返回的是MvcHtmlString 而不是字符串?
根据MSDN规定,”MvcHtmlString”代表了一个 HTML编码的字符串,不需要二次编码。代码如下:
1: @{
2: string MyString = "My Simple String";
3: }
4: @MyString
以上代码会转换为:
Razor显示了全部的内容,许多人会认为已经看到加粗的字符串,是Razor Html在显示内容之前将内容编码,这就是为什么使用纯内容来代替粗体。
当不适用razor编码时,使用 MvcHtmlString,MvcHtmlString是razor的一种表示,即“字符串已经编码完毕,不需要其他编码”。
如:
1: @{
2: string MyString = "My Simple String";
3: }
4: @MvcHtmlString.Create(MyString)
输出:
Html.RenderAction 和 Html.Action两者之间有什么不同?更推荐使用哪种方法?
Html.RenderAction会将Action 方法的执行结果直接写入HTTP 响应请求流中,而 Html.Action会返回MVC HTML 字符串。更推荐使用Html.RenderAction,因为它更快。当我们想在显示前修改action执行的结果时,推荐使用Html.Action。
实验23——实现用户角色管理
在实验23中我们将实现管理员和非管理员登录的功能。需求很简单:非管理员用户没有创建新Employee的权限。实验23会帮助大家理解MVC提供的Session 和Action过滤器。
因此我们将实验23分为两部分:
第一部分:非管理员用户登录时,隐藏 Add New 链接
1. 创建标识用户身份的枚举类型
右击Model 文件夹,选择添加新项目。选择“Code File”选项。
输入“UserStatus”名,点击添加。
“Code File”选项会创建一个“.cs”文件.
创UserStatus枚举类型,如下:
1: namespace WebApplication1.Models
2: {
3: public enum UserStatus
4: {
5: AuthenticatedAdmin,
6: AuthentucatedUser,
7: NonAuthenticatedUser
8: }
9: }
2. 修改业务层功能
删除 IsValidUser函数,创建新函数“GetUserValidity“,如下:
1: public UserStatus GetUserValidity(UserDetails u)
2: {
3: if (u.UserName == "Admin" && u.Password == "Admin")
4: {
5: return UserStatus.AuthenticatedAdmin;
6: }
7: else if (u.UserName == "Sukesh" && u.Password == "Sukesh")
8: {
9: return UserStatus.AuthentucatedUser;
10: }
11: else
12: {
13: return UserStatus.NonAuthenticatedUser;
14: }
15: }
3. 修改DoLogin action方法
打开 AuthenticationController, 修改DoLogin action:
1: [HttpPost]
2: public ActionResult DoLogin(UserDetails u)
3: {
4: if (ModelState.IsValid)
5: {
6: EmployeeBusinessLayer bal = new EmployeeBusinessLayer();
7: //New Code Start
8: UserStatus status = bal.GetUserValidity(u);
9: bool IsAdmin = false;
10: if (status==UserStatus.AuthenticatedAdmin)
11: {
12: IsAdmin = true;
13: }
14: else if (status == UserStatus.AuthentucatedUser)
15: {
16: IsAdmin = false;
17: }
18: else
19: {
20: ModelState.AddModelError("CredentialError", "Invalid Username or Password");
21: return View("Login");
22: }
23: FormsAuthentication.SetAuthCookie(u.UserName, false);
24: Session["IsAdmin"] = IsAdmin;
25: return RedirectToAction("Index", "Employee");
26: //New Code End
27: }
28: else
29: {
30: return View("Login");
31: }
32: }
在上述代码中,已经出现Session 变量来识别用户身份。
什么是Session?
Session是Asp.Net的特性之一,可以在MVC中重用,可用于暂存用户相关数据,session变量周期是穿插于整个用户生命周期的。
4. 移除存在的 AddNew 链接
打开“~/Views/Employee”文件夹下 Index.cshtml View,移除”Add New“超链接。
<!-- Remove following line from Index.cshtml -->
<a href="/Employee/AddNew">Add New</a>
5. 创建分部View
右击“~/Views/Employee”文件夹,选择添加View,设置View名称”“AddNewLink”“,选中”Create a partial View“复选框。
6. 输入分部View的内容
在新创建的分部视图中输入以下内容:
<a href="/Employee/AddNew">Add New</a>
7. 新建 Action 方法
打开 EmployeeController,新建Action 方法”GetAddNewLink“,如下:
1: public ActionResult GetAddNewLink()
2: {
3: if (Convert.ToBoolean(Session["IsAdmin"]))
4: {
5: return Partial View("AddNewLink");
6: }
7: else
8: {
9: return new EmptyResult();
10: }
11: }
8. 显示 AddNew 链接
打开 Index.html,输入以下代码:
1: <a href="/Authentication/Logout">Logout</a>
2: </div>
3: <hr />
4: @{
5: Html.RenderAction("GetAddNewLink");
6: }
7: <div>
8: <table border="1">
9: <tr>
Html.RenderAction 执行Action 方法,并将结果直接写入响应流中。
9. 运行
测试1
测试2
第二部分: 直接URL 安全
以上实验实现了非管理员用户无法导航到AddNew链接。这样还不够,如果非管理员用户直接输入AddNew URL,则会直接跳转到此页面。
非管理员用户还是可以直接访问AddNew方法,为了解决这个问题,我们会引入MVC action 过滤器。Action 过滤器使得在action方法中添加一些预处理和后处理的逻辑判断问题。在整个实验中,会注重ActionFilters预处理的支持和后处理的功能。
1. 安装过滤器
新建文件夹Filters,新建类”AdminFilter“。
2. 创建过滤器
通过继承 ActionFilterAttribute ,将 AdminFilter类升级为”ActionFilter“,如下:
1: public class AdminFilter:ActionFilterAttribute
2: {
3:
4: }
注意:使用”ActionFilterAttribute “需要在文件顶部输入”System.Web.Mvc“。
3. 添加安全验证逻辑
在ActionFliter中重写 OnActionExecuting方法:
1: public override void OnActionExecuting(ActionExecutingContext filterContext)
2: {
3: if (!Convert.ToBoolean(filterContext.HttpContext.Session["IsAdmin"]))
4: {
5: filterContext.Result = new ContentResult()
6: {
7: Content="Unauthorized to access specified resource."
8: };
9: }
10: }
4. 绑定过滤器
在AddNew和 SaveEmployee方法中绑定过滤器,如下:
1: [AdminFilter]
2: public ActionResult AddNew()
3: {
4: return View("CreateEmployee",new Employee());
5: }
6: ...
7: ...
8: [AdminFilter]
9: public ActionResult SaveEmployee(Employee e, string BtnSubmit)
10: {
11: switch (BtnSubmit)
12: {
13: case "Save Employee":
14: if (ModelState.IsValid)
15: {
16: EmployeeBusinessLayer empBal = new EmployeeBusinessLayer();
17: ....
18: ....
5. 运行
关于实验23
可以通过浏览器直接调用GetAddNewLink方法吗?
可以直接调用,也可直接停止”GetAddNewLink“的运行。
Html.Action有什么作用?
与Html.RenderAction作用相同,Html.Action会执行action 方法,并在View中显示结果。
语法:
1: @Html.Action("GetAddNewLink");
Html.RenderAction 和 Html.Action两者之间有什么不同?更推荐使用哪种方法?
Html.RenderAction会将Action 方法的执行结果直接写入HTTP 响应请求流中,而 Html.Action会返回MVCHTMLString。更推荐使用Html.RenderAction,因为它更快。当我们想在显示前修改 action执行的结果时,推荐使用Html.Action。
什么是 ActionFilter ?
与AuthorizationFilter类似,ActionFilter是ASP.NET MVC过滤器中的一种,允许在action 方法中添加预处理和后处理逻辑。
实验24——实现项目外观的一致性
在ASP.NET能够保证外观一致性的是母版页的使用。MVC却不同于ASP.NET,在RAZOR中,母版页称为布局页面。
在开始实验之前,首先来了解布局页面
1. 带有欢迎消息的页眉
2. 带有数据的页脚
最大的问题是什么?
带有数据的页脚和页眉作为ViewModel的一部分传从Controller传给View。
现在最大的问题是在页眉和页脚移动到布局页面后,如何将数据从View传给Layout页面。
解决方案——继承
可使用继承原则,通过实验来深入理解。
1. 创建ViewModel基类
在ViewModel 文件夹下新建ViewModel 类 ”BaseViewModel“,如下:
1: public class BaseViewModel
2: {
3: public string UserName { get; set; }
4: public FooterViewModel FooterData { get; set; }//New Property
5: }
BaseViewModel可封装布局页所需要的所有值。
2. 准备 EmployeeListViewModel
删除EmployeeListViewModel类的 UserName和 FooterData属性,并继承 BaseViewModel:
1: public class EmployeeListViewModel:BaseViewModel
2: {
3: public List<EmployeeViewModel> Employees { get; set; }
4: }
3. 创建布局页面
右击shared文件夹,选择添加>>MVC5 Layout Page。输入名称”MyLayout“,点击确认
1: <!DOCTYPE html>
2:
3: <html>
4: <head>
5: <meta name="viewport" content="width=device-width" />
6: <title>@ViewBag.Title</title>
7: </head>
8: <body>
9: <div>
10: @RenderBody()
11: </div>
12: </body>
13: </html>
4. 将布局转换为强类型布局
1: @using WebApplication1.ViewModels
2: @model BaseViewModel
5. 设计布局页面
在布局页面添加页眉,页脚和内容,内容,三部分,如下:
1: <html>
2: <head>
3: <meta name="viewport" content="width=device-width" />
4: <title>@RenderSection("TitleSection")</title>
5: @RenderSection("HeaderSection",false)
6: </head>
7: <body>
8: <div style="text-align:right">
9: Hello, @Model.UserName
10: <a href="/Authentication/Logout">Logout</a>
11: </div>
12: <hr />
13: <div>
14: @RenderSection("ContentBody")
15: </div>
16: @Html.Partial("Footer",Model.FooterData)
17: </body>
18: </html>
如上所示,布局页面包含三部分,TitleSection, HeaderSection 和 ContentBody,内容页面将使用这些部分来定义合适的内容。
6. 在 Index View中绑定布局页面
打开Index.cshtml,在文件顶部会发现以下代码:
1: @{
2: Layout = null;
3: }
修改:
1: @{
2: Layout = "~/Views/Shared/MyLayout.cshtml";
3: }
7.设计Index View
- 从Index View中去除页眉和页脚
- 在Body标签中复制保留的内容,并存放在某个地方。
- 复制Title标签中的内容
- 移除View中所有的HTML 内容,确保只移动了HTML,@model 且没有移动layout语句
- 在复制的内容中定义TitleSection和 Contentbody
完整的View代码如下:
1: @using WebApplication1.ViewModels
2: @model EmployeeListViewModel
3: @{
4: Layout = "~/Views/Shared/MyLayout.cshtml";
5: }
6:
7: @section TitleSection{
8: MyView
9: }
10: @section ContentBody{
11: <div>
12: @{
13: Html.RenderAction("GetAddNewLink");
14: }
15: <table border="1">
16: <tr>
17: <th>Employee Name</th>
18: <th>Salary</th>
19: </tr>
20: @foreach (EmployeeViewModel item in Model.Employees)
21: {
22: <tr>
23: <td>@item.EmployeeName</td>
24: <td style="background-color:@item.SalaryColor">@item.Salary</td>
25: </tr>
26: }
27: </table>
28: </div>
29: }
8. 运行
9. 在 CreateEmployee 中绑定布局页面
打开 Index.cshtml,修改顶部代码:
1: @{
2: Layout = "~/Views/Shared/MyLayout.cshtml";
3: }
10. 设计 CreateEmployee View
与第7步中的程序类似,定义 CreateEmployee View中的Section ,在本次定义中只添加一项,如下:
1: @using WebApplication1.Models
2: @model Employee
3: @{
4: Layout = "~/Views/Shared/MyLayout.cshtml";
5: }
6:
7: @section TitleSection{
8: CreateEmployee
9: }
10:
11: @section HeaderSection{
12: <script src="~/Scripts/Validations.js"></script>
13: <script>
14: function ResetForm() {
15: document.getElementById('TxtFName').value = "";
16: document.getElementById('TxtLName').value = "";
17: document.getElementById('TxtSalary').value = "";
18: }
19: </script>
20: }
21: @section ContentBody{
22: <div>
23: <form action="/Employee/SaveEmployee" method="post" id="EmployeeForm">
24: <table>
25: <tr>
26: <td>
27: First Name:
28: </td>
29: <td>
30: <input type="text" id="TxtFName" name="FirstName" value="@Model.FirstName" />
31: </td>
32: </tr>
33: <tr>
34: <td colspan="2" align="right">
35: @Html.ValidationMessage("FirstName")
36: </td>
37: </tr>
38: <tr>
39: <td>
40: Last Name:
41: </td>
42: <td>
43: <input type="text" id="TxtLName" name="LastName" value="@Model.LastName" />
44: </td>
45: </tr>
46: <tr>
47: <td colspan="2" align="right">
48: @Html.ValidationMessage("LastName")
49: </td>
50: </tr>
51:
52: <tr>
53: <td>
54: Salary:
55: </td>
56: <td>
57: <input type="text" id="TxtSalary" name="Salary" value="@Model.Salary" />
58: </td>
59: </tr>
60: <tr>
61: <td colspan="2" align="right">
62: @Html.ValidationMessage("Salary")
63: </td>
64: </tr>
65:
66: <tr>
67: <td colspan="2">
68:
69: <input type="submit" name="BtnSubmit" value="Save Employee" onclick="return IsValid();" />
70: <input type="submit" name="BtnSubmit" value="Cancel" />
71: <input type="button" name="BtnReset" value="Reset" onclick="ResetForm();" />
72: </td>
73: </tr>
74: </table>
75: </div>
76: }
11. 运行
Index View是 EmployeeListViewModel类型的强View类型,是 BaseViewModel的子类,这就是为什么Index View可一直发挥作用。CreateEmployee View 是CreateEmployeeViewModel的强类型,并不是BaseViewModel的子类,因此会出现以上错误。
12. 准备 CreateEmployeeViewModel
使CreateEmployeeViewModel 继承 BaseViewModel,如下:
1: public class CreateEmployeeViewModel:BaseViewModel
2: {
3: ...
13. 运行
报错,该错误好像与步骤11中的错误完全不同,出现这些错误的根本原因是未初始化AddNew action方法中的Header和Footer数据。
14. 初始化Header和Footer 数据
修改AddNew方法:
1: public ActionResult AddNew()
2: {
3: CreateEmployeeViewModel employeeListViewModel = new CreateEmployeeViewModel();
4: employeeListViewModel.FooterData = new FooterViewModel();
5: employeeListViewModel.FooterData.CompanyName = "StepByStepSchools";//Can be set to dynamic value
6: employeeListViewModel.FooterData.Year = DateTime.Now.Year.ToString();
7: employeeListViewModel.UserName = User.Identity.Name; //New Line
8: return View("CreateEmployee", employeeListViewModel);
9: }
15. 初始化 SaveEmployee中的Header和 FooterData
1: public ActionResult SaveEmployee(Employee e, string BtnSubmit)
2: {
3: switch (BtnSubmit)
4: {
5: case "Save Employee":
6: if (ModelState.IsValid)
7: {
8: ...
9: }
10: else
11: {
12: CreateEmployeeViewModel vm = new CreateEmployeeViewModel();
13: ...
14: vm.FooterData = new FooterViewModel();
15: vm.FooterData.CompanyName = "StepByStepSchools";//Can be set to dynamic value
16: vm.FooterData.Year = DateTime.Now.Year.ToString();
17: vm.UserName = User.Identity.Name; //New Line
18: return View("CreateEmployee", vm); // Day 4 Change - Passing e here
19: }
20: case "Cancel":
21: return RedirectToAction("Index");
22: }
23: return new EmptyResult();
24: }
16. 运行
关于实验24
RenderBody 有什么作用?
之前创建了Layout 页面,包含一个Razor语句如:
1: @Html.RenderBody()
首先我们先来了RenderBody是用来做什么的?
在内容页面,通常会定义Section,声明Layout页面。但是奇怪的是,Razor允许定义在Section外部定义一些内容。所有的非section内容会使用RenderBody函数来渲染,下图能够更好的理解:
布局是否可嵌套?
可以嵌套,创建Layout页面,可使用其他存在的Layout页面,语法相同。
是否需要为每个View定义Layout页面?
会在View文件夹下发现特殊的文件“__ViewStart.cshtml”,在其内部的设置会应用所有的View。
例如:在__ViewStart.cshtml中输入以下代码,并给所有View 设置 Layout页面。
1: @{
2: Layout = "~/Views/Shared/_Layout.cshtml";
3: }
是否在每个Action 方法中需要加入Header和Footer数据代码?
不需要,可在Action 过滤器的帮助下删除重复的代码。
是否强制定义了所有子View中的Section?
是的,如果Section定义为需要的section,默认的值会设置为true。如下
1: @RenderSection("HeaderSection",false) // Not required
2: @RenderSection("HeaderSection",true) // required
3: @RenderSection("HeaderSection") // required
实验25——使用Action Fliter让Header和Footer数据更有效
在实验23中,我们已经知道了使用 ActionFilter的一个优点,现在来看看使用 ActionFilter的其他好处
1. 删除Action 方法中的冗余代码
删除Index,AddNew, SaveEmployee方法中的Header和Footer数据代码。
Header代码如:
1: bvm.UserName = HttpContext.Current.User.Identity.Name;
Footer代码如:
1: bvm.FooterData = new FooterViewModel();
2: bvm.FooterData.CompanyName = "StepByStepSchools";//Can be set to dynamic value
3: bvm.FooterData.Year = DateTime.Now.Year.ToString();
2.创建HeaderFooter 过滤器
在Filter文件夹下新建类”HeaderFooterFilter“,并通过继承ActionFilterAttribute类升级为Action Filter
3. 升级ViewModel
重写 HeaderFooterFilter类的 OnActionExecuted方法,在该方法中获取当前View Model ,并绑定Header和Footer数据。
1: public class HeaderFooterFilter : ActionFilterAttribute
2: {
3: public override void OnActionExecuted(ActionExecutedContext filterContext)
4: {
5: ViewResult v = filterContext.Result as ViewResult;
6: if(v!=null) // v will null when v is not a ViewResult
7: {
8: BaseViewModel bvm = v.Model as BaseViewModel;
9: if(bvm!=null)//bvm will be null when we want a view without Header and footer
10: {
11: bvm.UserName = HttpContext.Current.User.Identity.Name;
12: bvm.FooterData = new FooterViewModel();
13: bvm.FooterData.CompanyName = "StepByStepSchools";//Can be set to dynamic value
14: bvm.FooterData.Year = DateTime.Now.Year.ToString();
15: }
16: }
17: }
18: }
4. 绑定过滤器
在Index中,AddNew,SaveEmployee的action 方法中绑定 HeaderFooterFilter
1: [HeaderFooterFilter]
2: public ActionResult Index()
3: {
4: EmployeeListViewModel employeeListViewModel = new EmployeeListViewModel();
5: ...
6: }
7: ...
8: [AdminFilter]
9: [HeaderFooterFilter]
10: public ActionResult AddNew()
11: {
12: CreateEmployeeViewModel employeeListViewModel = new CreateEmployeeViewModel();
13: //employeeListViewModel.FooterData = new FooterViewModel();
14: //employeeListViewModel.FooterData.CompanyName = "StepByStepSchools";
15: ...
16: }
17: ...
18: [AdminFilter]
19: [HeaderFooterFilter]
20: public ActionResult SaveEmployee(Employee e, string BtnSubmit)
21: {
22: switch (BtnSubmit)
23: {
24: ...
5. 运行
总结
本文主要介绍了ASP.NET MVC中页眉页脚的添加和Layout页面的使用,并实现了用户角色分配及Action Filter的使用,下一节中我们将是最难和最有趣的一篇,请持续关注吧!
工欲善其事,必先利其器,使用 ComponentOne Studio Enterprise 中提供的 ComponentOne Studio ASP.NET MVC 版本,您能获取快速的轻量级控件来满足用户所有需求。
它与Visual Studio无缝集成,完全与MVC6和ASP.NET 5.0兼容,将大幅提高效率,能提供从桌面到移动设备的体验,不用任何妥协就能创建触控优先的现代化解决方案。
原文链接:http://www.codeproject.com/Articles/1000435/Learn-MVC-Project-in-days-Day