七天學會ASP.NET MVC (一)——深刻理解ASP.NET MVChtml
七天學會ASP.NET MVC (二)——ASP.NET MVC 數據傳遞瀏覽器
七天學會ASP.NET MVC (三)——ASP.Net MVC 數據處理安全
七天學會ASP.NET MVC (四)——用戶受權認證問題session
七天學會ASP.NET MVC (五)——Layout頁面使用和用戶角色管理mvc
目錄ide
實驗22——添加頁腳函數
實驗23——實現用戶角色管理佈局
實驗24——實現項目外觀一致性post
實驗25——使用Action 過濾器讓頁眉和頁腳代碼更有效學習
總結
在本實驗中,咱們會在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
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會返回MVCHTMLString。更推薦使用Html.RenderAction,由於它更快。當咱們想在顯示前修改action執行的結果時,推薦使用Html.Action。
在實驗23中咱們將實現管理員和非管理員登陸的功能。需求很簡單:非管理員用戶沒有建立新Employee的權限。實驗23會幫助你們理解MVC提供的Session 和Action過濾器。
所以咱們將實驗23分爲兩部分:
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
以上實驗實現了非管理員用戶沒法導航到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. 運行
能夠經過瀏覽器直接調用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 方法中添加預處理和後處理邏輯。
在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
完整的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. 運行
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
在實驗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