目录
实验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;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中显示结果。
语法:
@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=" padding: 0px; color: rgb(0, 0, 255);">>@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语句如:
@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的使用,下一节中我们将是最难和最有趣的一篇,请持续关注吧!
在学习了本节Layout页面及用户角色管理之后,你是否也跃跃欲试想要进行MVC开发?不妨试试 ComponentOne Studio ASP.NET MVC 这款轻量级控件,它与Visual Studio无缝集成,完全与MVC6和ASP.NET 5.0兼容,将大幅提高工作效率.
原文链接:http://www.codeproject.com/Articles/1000435/Learn-MVC-Project-in-days-Day