7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天

目錄

0. 前言

歡迎來(lái)到第五天的學(xué)習(xí)桑李。希望第一天到第四天的學(xué)習(xí)塔淤,你都是開(kāi)心的集侯。

1. Lab 22 — 增加 Footer

在這個(gè)實(shí)驗(yàn)中炎码,我們將會(huì)向 Employee 頁(yè)面添加 Footer郑叠。本次實(shí)驗(yàn)的目標(biāo)是理解分部視圖(Partial Views)。

什么是「Partial Views」产禾?

邏輯上講排作,分部視圖(Partial Views) 是一個(gè)可重用的視圖,它不會(huì)被直接顯示亚情。它會(huì)被其它視圖所包含妄痪,然后作為該視圖的一部分來(lái)顯示。它類似于 ASP.NET Web Forms 中的用戶控件楞件,但是沒(méi)有后臺(tái)代碼衫生。

第一步:為 Partial View 創(chuàng)建 ViewModel

右擊 ViewModel 文件夾,然后創(chuàng)建一個(gè)類土浸,命名為 FooterViewModel罪针。

public class FooterViewModel
{
   public string CompanyName { get; set; }
   public string Year { get; set; }
}

第二步:創(chuàng)建 Partial View

右擊「~/Views/Shared」文件夾,選擇 Add -> View黄伊。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天

設(shè)置視圖的名稱為 Footer泪酱。選中「Create as a partial view」復(fù)選框,然后點(diǎn)擊「Add」。

注意:我們已經(jīng)在第一天的學(xué)習(xí)中談?wù)摿?Shared 文件夾墓阀。Shared 文件夾包含了視圖毡惜,這些視圖不會(huì)屬于一個(gè)特定的控制器。在 Shared 文件夾下的視圖適用于所有控制器斯撮。

第三步:在 Partial View 中顯示數(shù)據(jù)

打開(kāi) Footer.cshtml经伙,然后放置如下代碼。

@using WebApplication1.ViewModels

@model FooterViewModel

<div style="text-align:right;background-color: silver;color: darkcyan;border: 1px solid gray;margin-top:2px;padding-right:10px;">

@Model.CompanyName &copy; @Model.Year

</div>

第四步:在 Main ViewModel 中包含 Footer 數(shù)據(jù)

打開(kāi) EmployeeListViewModel 類吮成,然后增加一個(gè)新的屬性來(lái)承載 Footer 數(shù)據(jù)橱乱。

public class EmployeeListViewModel
{
    public List<EmployeeViewModel> Employees { get; set; }

    public string UserName { get; set; }

    public FooterViewModel FooterData { get; set; }//New Property
}

在我們的例子中辜梳,F(xiàn)ooter 視圖將會(huì)作為 Index 視圖的一部分展示粱甫。

我們將會(huì)在 Index 視圖中向 Footer 傳輸必要數(shù)據(jù)。

Index 視圖是一個(gè) EmployeeListViewModel 的強(qiáng)類型視圖作瞄,因此 Footer 中需要的數(shù)據(jù)都應(yīng)該被封裝在 EmployeeListViewModel 類中茶宵。

第五步:設(shè)置 Footer 數(shù)據(jù)

打開(kāi) EmployeeController,然后在 Index 行為方法中設(shè)定 FooterData 屬性值宗挥。

public ActionResult Index()
{
   ...
   ...
    employeeListViewModel.FooterData = new FooterViewModel();
    employeeListViewModel.FooterData.CompanyName = "StepByStepSchools";//Can be set to dynamic value
    employeeListViewModel.FooterData.Year = DateTime.Now.Year.ToString();
    return View("Index", employeeListViewModel);
}

第六步:展示 Footer

打開(kāi) Index.cshtml 文件乌庶,然后在 Table 標(biāo)簽后展示 Footer 視圖。

</table>
        @{
            Html.RenderPartial("Footer", Model.FooterData);
        }
    </div>
</body>
</html>

第七步:執(zhí)行并測(cè)試

按下 F5契耿。導(dǎo)航到 Index 視圖瞒大。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天

Lab 22 的 Q&A

Html.Partial 是用來(lái)做什么的?

它類似于 Html.RenderPartial搪桂,Html.Partial 用于在視圖中展示 Partial View透敌。

它的語(yǔ)法如下。非常簡(jiǎn)單踢械。

@Html.Partial("Footer", Model.FooterData);

兩者的區(qū)別是什么酗电?

Html.RenderPartial 將會(huì)把 Partial View 的結(jié)果寫入 HTTP 響應(yīng)流中,而 Html.Partial 將會(huì)以 MvcHtmlString 的格式返回結(jié)果内列。

什么是 MvcHtmlString撵术,為什么 Html.Partial 返回的是 MvcHtmlString,而不是字符串话瞧?

首先讓我們理解下什么是 MvcHtmlString嫩与。

MSDN 的定義是「MvcHtmlString 代表一個(gè) HTML 編碼的字符串,這種字符串不應(yīng)該再次編碼」交排。

更好地理解這個(gè)定義蕴纳,請(qǐng)查看下面代碼。

@{
   string MyString = "My Simple String";
}
@MyString

它將會(huì)產(chǎn)生如下輸出个粱。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天

正如你所看見(jiàn)的古毛,Razor 展示了所有的內(nèi)容。可能許多人會(huì)以為將輸出加粗的字符串稻薇,但是 Razor Html 在展示之前對(duì)內(nèi)容進(jìn)行了編碼嫂冻,這就是為什么我們獲得的是純內(nèi)容,而不是加粗的字符串塞椎。

當(dāng)我們不想用 Razor 編碼時(shí)桨仿,我們可以使用 MvcHtmlString。MvcHtmlString 是 Razor 的一種表示案狠,即「字符串已經(jīng)編碼了服傍,不再需要額外編碼」。

例如我們可以看下面的代碼骂铁。

@{
   string MyString = "My Simple String";
}
@MvcHtmlString.Create(MyString)

它將會(huì)產(chǎn)生如下輸出吹零。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天

為什么 Html.Partial 返回的是 MvcHtmlString,而不是字符串呢拉庵?

我們已經(jīng)理解了「Razor 將會(huì)編碼字符串灿椅,但是不會(huì)對(duì) MvcHtmlString 編碼」這一事實(shí)。如果 Partial View 內(nèi)容被認(rèn)為是像它展示的那樣的純字符串钞支,便沒(méi)有意義茫蛹。我們希望它被當(dāng)成是一個(gè) HTML 內(nèi)容,這樣我們就需要停止 Razor 編碼烁挟,因此 Partial 方法被設(shè)計(jì)為返回 MvcHtmlString婴洼。

哪個(gè)更加推崇,Html.RenderPartial 還是 Html.Partial 撼嗓?

Html.RenderPartial 更被推崇柬采,因?yàn)樗臁?/p>

什么時(shí)候運(yùn)用 Html.Partial 更好?

當(dāng)我們想在展示之前改變 Partial View 返回的結(jié)果静稻,推薦使用 Html.Partial警没。

打開(kāi) Index.cshtml,然后打開(kāi) Footer振湾,放置如下代碼杀迹。

@{        
    MvcHtmlString result = Html.Partial ("Footer", Model.FooterData);
    string finalResult = result.ToHtmlString().Replace("2015", "20000");            
}
@MvcHtmlString.Create(finalResult)

現(xiàn)在頁(yè)腳展示如下。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天

為什么將 Partial View 放置在 Shared 文件夾下押搪?

因?yàn)?Partial View 意味著可以重復(fù)利用的資源树酪,因此放置它們的地點(diǎn)是 Shared 文件夾下。

我們不能將 Partial View 放置到一個(gè)特殊的控制器文件夾內(nèi)嗎大州?例如 Employee 或者 Authentication续语?

我們可以這樣做,但是在這種場(chǎng)景下厦画,它將不會(huì)適用于指定控制器疮茄。

例如:當(dāng)我們將 Partial View 放置到 Employee 文件夾下滥朱,它將不會(huì)適用于 AuthenticationController 或者適用于 AuthenticationController 相關(guān)的視圖。

為什么 Partial View 的定義包含「邏輯」詞匯力试?

在定義中徙邻,我們已經(jīng)知道 Partial View 是一個(gè)可重用的視圖,但是它不能通過(guò)自己執(zhí)行畸裳。它需要放置到其它視圖中缰犁,然后作為這些視圖的一部分來(lái)展示。

我們所說(shuō)的 Partial View 可重用是事實(shí)怖糊,但是我們提到的執(zhí)行在邏輯上是事實(shí)帅容。技術(shù)上而言,這不是一個(gè)正確的解釋伍伤。我們可以創(chuàng)建一個(gè)行為方法并徘,來(lái)返回如下的視圖結(jié)果。

public ActionResult MyFooter()
{
    FooterViewModel FooterData = new FooterViewModel();
    FooterData.CompanyName = "StepByStepSchools";//Can be set to dynamic value
    FooterData.Year = DateTime.Now.Year.ToString();
    return View("Footer", FooterData);
}

它將會(huì)展示如下的輸出嚷缭。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天

盡管在邏輯上沒(méi)有意義饮亏,但是技術(shù)上是可行的耍贾。Footer.cshtml 不會(huì)包含正確的結(jié)構(gòu)性 HTML阅爽。它意味著作為視圖的一部分來(lái)展示。因?yàn)槲覀冋f(shuō)「邏輯上是沒(méi)有意義的」荐开。

為什么要?jiǎng)?chuàng)建 Partial View付翁,而不是直接在視圖底部添加內(nèi)容?

這樣做有兩個(gè)優(yōu)勢(shì)晃听。

  1. 可重用性百侧。我們可以將一個(gè) Partial View 運(yùn)用到多個(gè)視圖中去。

  2. 代碼保留能扒。將其放置為一個(gè)分割的文件佣渴,使其管理和操縱都非常方便。

為什么在 Partial View 中沒(méi)有創(chuàng)建 Header初斑?

作為最佳實(shí)踐辛润,我們需要為 Partial View 創(chuàng)建 Header,但是為了保持最初實(shí)驗(yàn)簡(jiǎn)單化见秤,我們并沒(méi)有這樣做砂竖。

2. Lab 23 — 實(shí)現(xiàn)基于角色的安全性

在本次實(shí)驗(yàn)中,我們將會(huì)實(shí)現(xiàn) Admin 和 Non-Admin 兩種登錄功能鹃答。

需求是很簡(jiǎn)單的乎澄。即「Non-Admin 用戶不能創(chuàng)建 Employees」。

通過(guò)這個(gè)實(shí)驗(yàn)测摔,我們將會(huì)理解 MVC 中的兩個(gè)主題置济。

  • Session

  • Action Filters

現(xiàn)在我們開(kāi)始進(jìn)行實(shí)驗(yàn)。為了簡(jiǎn)單化,我們將實(shí)驗(yàn)分為兩部分浙于。

Part 1 — Non-Admin 用戶登錄修噪,隱藏 AddNew 鏈接

第一步:創(chuàng)建標(biāo)識(shí) UserStatus 的枚舉

右擊 Models 文件夾,選擇「Add New Item」路媚。

在對(duì)話框中選擇「Code File」選項(xiàng)黄琼。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天

在名稱欄中輸入「UserStatus」,然后點(diǎn)擊添加整慎≡嗫睿「Code File」的選項(xiàng)將會(huì)創(chuàng)建一個(gè)空白的「.cs」文件。

創(chuàng)建一個(gè)枚舉裤园,命名為 UserStatus撤师,代碼如下。

namespace WebApplication1.Models
{
    public enum UserStatus
    {
        AuthenticatedAdmin,
        AuthentucatedUser,
        NonAuthenticatedUser
    }
}

第二步:更改業(yè)務(wù)層功能

刪除 IsValidUser 功能,然后創(chuàng)建一個(gè)新的功能,命名為 GetUserValidity磕仅。

public UserStatus GetUserValidity(UserDetails u)
{
    if (u.UserName == "Admin" && u.Password == "Admin")
    {
        return UserStatus.AuthenticatedAdmin;
    }
    else if (u.UserName == "Sukesh" && u.Password == "Sukesh")
    {
        return UserStatus.AuthentucatedUser;
    }
    else
    {
        return UserStatus.NonAuthenticatedUser;
    }
}

第三步:更改 DoLogin 行為方法

打開(kāi) AuthenticationController虑啤,然后更改 DoLogin 行為方法如下。

[HttpPost]
public ActionResult DoLogin(UserDetails u)
{
    if (ModelState.IsValid)
    {
        EmployeeBusinessLayer bal = new EmployeeBusinessLayer();
        //New Code Start
        UserStatus status = bal.GetUserValidity(u);
        bool IsAdmin = false;
        if (status==UserStatus.AuthenticatedAdmin)
        {
            IsAdmin = true;
        }
        else if (status == UserStatus.AuthentucatedUser)
        {
            IsAdmin = false;
        }
        else
        {
            ModelState.AddModelError("CredentialError", "Invalid Username or Password");
            return View("Login");
        }
        FormsAuthentication.SetAuthCookie(u.UserName, false);
        Session["IsAdmin"] = IsAdmin;
        return RedirectToAction("Index", "Employee");
        //New Code End
    }
    else
    {
        return View("Login");
    }
}

正如你所看見(jiàn)的魏蔗,我們運(yùn)用 Session 變量來(lái)識(shí)別用戶是 Admin 用戶還是 non-Admin 用戶。

不知道什么是 Session?

Session 是 ASP.NET 的一個(gè)功能积蔚,在 ASP.NET MVC 中被重用。

我們運(yùn)用 Session 變量來(lái)承載用戶相關(guān)的數(shù)據(jù)烦周。Session 的生命周期取決于用戶的生命周期尽爆。它將一直可用直到當(dāng)前的 Session 結(jié)束。

第四步:刪除已經(jīng)存在的 AddNew 鏈接

在「~/Views/Employee」文件夾下打開(kāi) Index.cshtml 視圖读慎,然后完全刪除「Add New」超鏈接漱贱。

<!-- Remove following line from Index.cshtml -->

<a  href="/Employee/AddNew">Add New</a>

第五步:創(chuàng)建 Partial View

右擊「~/Views/Employee」文件夾,然后選擇 Add -> View夭委。設(shè)置視圖的名稱為「AddNewLink」幅狮,然后確保選擇「Create as a partial view」復(fù)選框。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天

第六步:在 Partial View 中放置內(nèi)容

在剛創(chuàng)建的 Partial View 中放置如下內(nèi)容闰靴。

<a  href="/Employee/AddNew">Add New</a>

第七步:創(chuàng)建行為方法

打開(kāi) EmployeeController 然后創(chuàng)建一個(gè)新的行為方法彪笼,命名為「GetAddNewLink」。

public ActionResult GetAddNewLink()
{
    if (Convert.ToBoolean(Session["IsAdmin"]))
    {
        return Partial View("AddNewLink");
    }
    else
    {
        return new EmptyResult();
    }
}

第八步:展示 AddNew 鏈接

打開(kāi) Index.html蚂且,然后放置如下代碼配猫。

<a href="/Authentication/Logout">Logout</a>
</div>
<hr />
@{
  Html.RenderAction("GetAddNewLink");
}
<div>
<table border="1">
<tr>

Html.RenderAction 執(zhí)行行為方法,然后向響應(yīng)流中直接寫入結(jié)果杏死。

第九步:執(zhí)行并測(cè)試

按下 F5泵肄,然后執(zhí)行應(yīng)用捆交。

  • Test 1
7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天
  • Test 2
7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天

Part 2 — 直接的 URL 安全性

按照上述的邏輯,一件事是可以確保的腐巢。即現(xiàn)在 non-Admin 用戶不能通過(guò)超鏈接導(dǎo)航到 AddNew 行為品追。

這樣就夠了嗎?

答案是否定的冯丙,這還不夠肉瓦。如果一個(gè) non-Admin 用戶直接通過(guò) URL 試圖導(dǎo)航到 AddNew 行為會(huì)發(fā)生什么呢。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天

正如你在上述例子中所看見(jiàn)的胃惜,一個(gè) non-Admin 用戶依然可以訪問(wèn) AddNew 行為泞莉。

為了解決這個(gè)問(wèn)題,我們需要運(yùn)用 MVC 中的 Action Filters船殉。Action Filters 讓我們向行為方法中添加一些預(yù)處理和后處理的邏輯鲫趁。在本實(shí)驗(yàn)中,我們將著重于 Action Filters 的預(yù)處理功能利虫,在后面的實(shí)驗(yàn)中挨厚,我們?cè)僦赜诤筇幚砉δ堋?/p>

第一步:設(shè)置過(guò)濾器

在項(xiàng)目下創(chuàng)建一個(gè)新的文件夾,命名為 Filters糠惫,然后創(chuàng)建一個(gè)新的類疫剃,命名為 AdminFilter。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天

第二步:創(chuàng)建過(guò)濾器

升級(jí)簡(jiǎn)單的 AdminFilter 類到 ActionFilter寞钥,通過(guò)將其繼承 ActionFilterAttribute 類慌申,代碼如下陌选。

public class AdminFilter:ActionFilterAttribute
{

}

注:為了運(yùn)用 ActionFilterAttribute理郑,你需要在頂部引用 System.Web.Mvc。

第三步:增加安全認(rèn)證邏輯

在 ActionFilter 中重寫 OnActionExecuting 方法咨油。

public override void OnActionExecuting(ActionExecutingContext filterContext)
{
    if (!Convert.ToBoolean(filterContext.HttpContext.Session["IsAdmin"]))
    {
        filterContext.Result = new ContentResult()
        {
            Content="Unauthorized to access specified resource."
        };
    }
}

第四步:附加過(guò)濾器

向 AddNew 和 SaveEmployee 行為方法添加過(guò)濾器您炉。

[AdminFilter]
public ActionResult AddNew()
{
    return View("CreateEmployee",new Employee());
}
...
...
[AdminFilter]
public ActionResult SaveEmployee(Employee e, string BtnSubmit)
{
    switch (BtnSubmit)
    {
        case "Save Employee":
            if (ModelState.IsValid)
            {
                EmployeeBusinessLayer empBal = new EmployeeBusinessLayer();
....
....

第五步:執(zhí)行和測(cè)試

按下 F5,然后執(zhí)行應(yīng)用役电。運(yùn)用 non-Admin 身份登錄赚爵,然后試圖通過(guò) AddNew 行為的 URL 導(dǎo)航到 AddNew 行為方法。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天

正如你所看見(jiàn)的法瑟,現(xiàn)在你的行為方法處于完全安全狀態(tài)冀膝。

Lab 23 的 Q&A

我們可以通過(guò)地址欄直接觸發(fā) GetAddNewLink 嗎?

答案是肯定的霎挟,我們已經(jīng)在「Talk on Lab 22」部分討論了這個(gè)行為窝剖。

直接停止執(zhí)行 GetAddNewLink 有可能嗎?

可以直接在 GetAddNewLink 中添加 ChildActionOnly 屬性酥夭。

[ChildActionOnly]
public ActionResult GetAddNewLink()
{
    if (Convert.ToBoolean(Session["IsAdmin"]))
{

Html.Action 是用來(lái)做什么的赐纱?

就類似于 Html.RenderAction脊奋,Html.Action 將會(huì)執(zhí)行行為方法用于呈現(xiàn)視圖的結(jié)果。下面是語(yǔ)法疙描。

@Html.Action("GetAddNewLink");

可以看出诚隙,語(yǔ)法相對(duì)來(lái)說(shuō)簡(jiǎn)單多了。

兩者的區(qū)別是什么起胰?

Html.RenderAction 將會(huì)把行為方法的執(zhí)行結(jié)果直接寫入 HTTP 響應(yīng)流久又,而 Html.Action 將會(huì)返回 MvcHtmlString 結(jié)果。

哪個(gè)更推崇效五?是 Html.RenderAction 還是 Html.Action籽孙?

更推崇 Html.RenderAction,因?yàn)樗臁?/p>

什么時(shí)候用 Html.Action 更好火俄?

當(dāng)我們想在呈現(xiàn)之前改變行為方法執(zhí)行的結(jié)果時(shí)犯建,用 Html.Action 更好。

什么是 ActionFilter瓜客?

就類似于 AuthenticationFilter适瓦,ActionFilter 是 ASP.NET MVC 的一種過(guò)濾器類型。它允許我們向行為方法添加預(yù)處理和后處理邏輯谱仪。

3. Lab 24 — 任務(wù)實(shí)驗(yàn) — 處理 CSRF 攻擊

從視圖的安全性方面出發(fā)玻熙,我們還需要在項(xiàng)目中處理 CSRF 攻擊。這里我將不再做過(guò)多指導(dǎo)疯攒,你需要自己手動(dòng)完成嗦随。

我建議你閱讀下述文章,然后實(shí)現(xiàn)方法敬尺。

如何在 MVC 中防止 CSRF 攻擊

4. Lab 25 — 實(shí)現(xiàn)項(xiàng)目的一致性外觀

在 ASP.NET 領(lǐng)域中枚尼,一致性的布局意味著母版頁(yè)(MasterPage)。

但 ASP.NET MVC 是區(qū)別于此的砂吞。在 Razor 中署恍,母版頁(yè)被稱為布局頁(yè)(Layout Pages)。

在正式開(kāi)始試驗(yàn)之前蜻直,我們先來(lái)討論一下在母版頁(yè)中我們需要放置哪些元素盯质。

1.帶有歡迎信息的 Header。
2.帶有頁(yè)腳數(shù)據(jù)的 Footer概而。

最大的問(wèn)題是什么呼巷?

頁(yè)腳和頁(yè)眉的數(shù)據(jù)作為 ViewModel 的一部分從控制器傳輸?shù)揭晥D中。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天

現(xiàn)在最大的問(wèn)題便是赎瑰,當(dāng)頁(yè)眉和頁(yè)腳移動(dòng)到布局頁(yè)后王悍,數(shù)據(jù)如何從視圖傳輸?shù)讲季猪?yè)。

解決方案 — 繼承

在這里我們可以簡(jiǎn)單地遵循面向?qū)ο罄^承準(zhǔn)則乡范。讓我們通過(guò)一個(gè)小實(shí)驗(yàn)來(lái)理解配名。

第一步:創(chuàng)建 ViewModel 的基類

在 ViewModel 文件夾下創(chuàng)建一個(gè)新的 ViewModel 類啤咽,稱為 BaseViewModel 類。

public class BaseViewModel
{
    public string UserName { get; set; }
    public FooterViewModel FooterData { get; set; }//New Property
}

正如你所看見(jiàn)的渠脉,BaseViewModel 封裝了 Layout 頁(yè)所需的所有元素宇整。

第二步:準(zhǔn)備 EmployeeListViewModel

從 EmployeeListViewModel 類中移除 UserName 和 FooterData 屬性,然后讓它繼承 BaseViewModel 類芋膘。

public class EmployeeListViewModel:BaseViewModel
{
    public List<EmployeeViewModel> Employees { get; set; }
}

第三步:創(chuàng)建布局頁(yè)

右擊 Shared 文件夾鳞青,選擇 Add -> MVC 5 Layout Page。輸入名稱為 MyLayout为朋,然后點(diǎn)擊確定臂拓。

它將會(huì)創(chuàng)建一個(gè)如下格式的代碼。

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
</head>
<body>
    <div>
        @RenderBody()
    </div>
</body>
</html>

第四步:轉(zhuǎn)換布局頁(yè)為強(qiáng)類型布局

在布局頁(yè)的上方放置如下的簡(jiǎn)單聲明习寸,使其變?yōu)閺?qiáng)類型布局胶惰。

@using WebApplication1.ViewModels
@model BaseViewModel

第五步:設(shè)計(jì)布局頁(yè)

在布局頁(yè)添加頁(yè)眉,頁(yè)腳和內(nèi)容三部分霞溪。

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@RenderSection("TitleSection")</title>
    @RenderSection("HeaderSection",false)
</head>
<body>
    <div style="text-align:right">
        Hello, @Model.UserName
        <a href="/Authentication/Logout">Logout</a>
    </div>
    <hr />
    <div>
    @RenderSection("ContentBody")
    </div>
    @Html.Partial("Footer",Model.FooterData)
</body>
</html>

正如你所看見(jiàn)的孵滞,我們已經(jīng)為布局頁(yè)創(chuàng)建了三塊。Title 部分鸯匹,Header 部分和Content 部分坊饶。內(nèi)容頁(yè)面將會(huì)用到這三部分來(lái)定義合適的內(nèi)容。

第六步:向 Index 視圖附上 Layout 頁(yè)面

打開(kāi) Index.cshtml 頁(yè)面殴蓬,在頂部會(huì)發(fā)現(xiàn)如下代碼匿级。

@{
    Layout = null;
}

將這段代碼改為如下代碼。

@{
    Layout = "~/Views/Shared/MyLayout.cshtml";
}

第七步:設(shè)計(jì) Index 視圖

  1. 從 Index 視圖中移除 Headers 和 Footers染厅。
  2. 復(fù)制 Body 標(biāo)簽中的剩余內(nèi)容痘绎,然后將它保存在別處。
  3. 復(fù)制 Title 標(biāo)簽里的內(nèi)容糟秘。
  4. 將視圖中的所有 HTML 內(nèi)容都移除简逮。確保你只是移除了 HTML,@model 和布局聲明不需要被移除尿赚。
  5. 定義 Title 部分和 Content 部分,內(nèi)容是剛才所復(fù)制下的內(nèi)容蕉堰。

完整的視圖將會(huì)如下所示凌净。

@using WebApplication1.ViewModels
@model EmployeeListViewModel
@{
    Layout = "~/Views/Shared/MyLayout.cshtml";
}

@section TitleSection{
    MyView
}
@section ContentBody{       
    <div>        
        @{
            Html.RenderAction("GetAddNewLink");
        }
        <table border="1">
            <tr>
                <th>Employee Name</th>
                <th>Salary</th>
            </tr>
            @foreach (EmployeeViewModel item in Model.Employees)
            {
                <tr>
                    <td>@item.EmployeeName</td>
                    <td style="background-color:@item.SalaryColor">@item.Salary</td>
                </tr>
            }
        </table>
    </div>
}

正如你所看見(jiàn)的,視圖中所有的元素都定義在指定的位置上屋讶。

第八步:執(zhí)行并測(cè)試

按下 F5冰寻,然后執(zhí)行應(yīng)用。導(dǎo)航到 Index 行為上皿渗。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天

第九步:在 CreateEmployee 視圖中附上 Layout 頁(yè)面

打開(kāi) Index.cshtml 頁(yè)面斩芭,在頂部會(huì)發(fā)現(xiàn)如下代碼轻腺。

@{
    Layout = null;
}

將其改為如下代碼。

@{
    Layout = "~/Views/Shared/MyLayout.cshtml";
}

第十步:設(shè)計(jì) CreateEmployee 視圖

像第七步的步驟一樣划乖,定義 CreateEmployee 視圖的區(qū)域贬养。這一次會(huì)增加一點(diǎn)。我們將會(huì)定義 Header 部分琴庵。

完整的 HTML 代碼如下误算。

@using WebApplication1.Models
@model Employee
@{
    Layout = "~/Views/Shared/MyLayout.cshtml";
}

@section TitleSection{
    CreateEmployee
}

@section HeaderSection{
<script src="~/Scripts/Validations.js"></script>
<script>
    function ResetForm() {
        document.getElementById('TxtFName').value = "";
        document.getElementById('TxtLName').value = "";
        document.getElementById('TxtSalary').value = "";
    }
</script>
}
@section ContentBody{ 
    <div>
        <form action="/Employee/SaveEmployee" method="post" id="EmployeeForm">
            <table>
            <tr>
                <td>
                    First Name:
                </td>
                <td>
                    <input type="text" id="TxtFName" name="FirstName" value="@Model.FirstName" />
                </td>
            </tr>
            <tr>
                <td colspan="2" align="right">
                    @Html.ValidationMessage("FirstName")
                </td>
            </tr>
            <tr>
                <td>
                    Last Name:
                </td>
                <td>
                    <input type="text" id="TxtLName" name="LastName" value="@Model.LastName" />
                </td>
            </tr>
            <tr>
                <td colspan="2" align="right">
                    @Html.ValidationMessage("LastName")
                </td>
            </tr>

            <tr>
                <td>
                    Salary:
                </td>
                <td>
                    <input type="text" id="TxtSalary" name="Salary" value="@Model.Salary" />
                </td>
            </tr>
            <tr>
                <td colspan="2" align="right">
                    @Html.ValidationMessage("Salary")
                </td>
            </tr>

            <tr>
                <td colspan="2">

                    <input type="submit" name="BtnSubmit" value="Save Employee" onclick="return IsValid();" />
                    <input type="submit" name="BtnSubmit" value="Cancel" />
                    <input type="button" name="BtnReset" value="Reset" onclick="ResetForm();" />
                </td>
            </tr>
            </table>
    </div>
}

第十一步:執(zhí)行并測(cè)試

按下 F5,然后執(zhí)行應(yīng)用迷殿。通過(guò)嘗試超鏈接來(lái)導(dǎo)航到 AddNew 行為上儿礼。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天

Index 視圖是 EmployeeListViewModel 的強(qiáng)類型視圖,EmployeeListViewModel 又是 BaseViewModel 的子類庆寺,所以 Index 視圖可以運(yùn)轉(zhuǎn)蚊夫。但是 CreateEmployee 視圖是 CreateEmployeeViewModel 的強(qiáng)類型視圖,而 CreateEmployeeViewModel 不是 BaseViewModel 的子類懦尝,所以 CreateEmployee 出現(xiàn)了這樣的錯(cuò)誤这橙。

第十二步:準(zhǔn)備 CreateEmployeeViewModel

讓 CreateEmployeeViewModel 繼承 BaseViewModel,代碼如下导披。

public class CreateEmployeeViewModel:BaseViewModel
{
...

第十三步:執(zhí)行并測(cè)試

再一次測(cè)試屈扎。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天

這次的錯(cuò)誤看起來(lái)與之前的不一樣。錯(cuò)誤真正的原因是撩匕,我們?cè)?AddNew 行為中沒(méi)有初始化 Header 和 Footer 的數(shù)據(jù)鹰晨。

第十四步:初始化 Header 和 Footer 數(shù)據(jù)

將 AddNew 行為方法的代碼改為如下所示。

public ActionResult AddNew()
{
    CreateEmployeeViewModel employeeListViewModel = new CreateEmployeeViewModel();
    employeeListViewModel.FooterData = new FooterViewModel();
    employeeListViewModel.FooterData.CompanyName = "StepByStepSchools";//Can be set to dynamic value
    employeeListViewModel.FooterData.Year = DateTime.Now.Year.ToString();
    employeeListViewModel.UserName = User.Identity.Name; //New Line
    return View("CreateEmployee", employeeListViewModel);
}

第十五步:在 SaveEmployee 中初始化 Header 和 Footer 數(shù)據(jù)

類似于 SaveEmployee 行為方法一樣止毕,我們更改其代碼如下模蜡。

public ActionResult SaveEmployee(Employee e, string BtnSubmit)
{
    switch (BtnSubmit)
    {
        case "Save Employee":
            if (ModelState.IsValid)
            {
                ...
            }
            else
            {
                CreateEmployeeViewModel vm = new CreateEmployeeViewModel();
                ...
                vm.FooterData = new FooterViewModel();
                vm.FooterData.CompanyName = "StepByStepSchools";//Can be set to dynamic value
                vm.FooterData.Year = DateTime.Now.Year.ToString();
                vm.UserName = User.Identity.Name; //New Line
                return View("CreateEmployee", vm); // Day 4 Change - Passing e here
            }
        case "Cancel":
            return RedirectToAction("Index");
    }
    return new EmptyResult();
}

第十六步:執(zhí)行并測(cè)試

按下 F5,然后執(zhí)行應(yīng)用扁凛。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天

Lab 25 的 Q&A

RenderBody 是用于做什么的忍疾?

當(dāng)我們第一次創(chuàng)建 Layout 頁(yè)面時(shí),我們有一個(gè) Razor 聲明如下谨朝。

@Html.RenderBody()

現(xiàn)在讓我們來(lái)理解下它是做什么的卤妒。在內(nèi)容頁(yè)面上,我們正常地定義區(qū)域字币,這些區(qū)域在布局頁(yè)聲明则披。

但是奇怪的是,Razor 允許我們?cè)趨^(qū)域外定義一些內(nèi)容洗出。在內(nèi)容頁(yè)面上士复,所有非區(qū)域內(nèi)的內(nèi)容將會(huì)被 RenderBody 函數(shù)呈現(xiàn)。

下圖將會(huì)更好地進(jìn)行解釋翩活。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天

我們有嵌套的布局嗎阱洪?

答案是肯定的便贵。我們能創(chuàng)建一個(gè)嵌套了其它布局頁(yè)的布局頁(yè)。語(yǔ)法是一樣的冗荸。

在每一個(gè)視圖中都指定布局頁(yè)是必須的嗎承璃?

你能夠在 Views 文件夾下發(fā)現(xiàn)一個(gè)特殊的布局頁(yè),稱為「__ ViewStart.cshtml」俏竞。在其內(nèi)部設(shè)定定義绸硕,將會(huì)應(yīng)用于所有視圖。

例如魂毁,在「__ ViewStart.cshtml」中放置如下的代碼玻佩,將會(huì)使「_Layout.cshtml」適用于所有視圖的布局。

@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}

在每一個(gè)行為方法中席楚,是否都需要放置 Header 和 Footer 的數(shù)據(jù)代碼咬崔?

答案是否定的。我們可以運(yùn)用 Action 過(guò)濾器來(lái)避免這種重復(fù)烦秩。我們將會(huì)在接下來(lái)的實(shí)驗(yàn)中實(shí)踐垮斯。

在子視圖中定義所有區(qū)域是否是必須的?

答案是肯定的只祠。如果 Section 的聲明為必須的兜蠕,那么默認(rèn)值是 True。

@RenderSection("HeaderSection",false) // Not required
@RenderSection("HeaderSection",true) // required
@RenderSection("HeaderSection") // required

5. Lab 26 — 運(yùn)用 Action 過(guò)濾器讓 Header 和 Footer 數(shù)據(jù)更高效

在 Lab 23 中抛寝,我們已經(jīng)知道了 ActionFilter 的優(yōu)勢(shì)熊杨,現(xiàn)在我們來(lái)看它的第二點(diǎn)優(yōu)勢(shì)。

第一步:從行為方法中刪除冗余代碼

從 Index盗舰,AddNew 和 SaveEmployee 方法中刪除 Header 和 Footer 數(shù)據(jù)的代碼晶府。

Header 代碼如下。

bvm.UserName = HttpContext.Current.User.Identity.Name;

Footer 代碼如下钻趋。

bvm.FooterData = new FooterViewModel();
bvm.FooterData.CompanyName = "StepByStepSchools";//Can be set to dynamic value
bvm.FooterData.Year = DateTime.Now.Year.ToString();           

第二步:創(chuàng)建 HeaderFooterFilter

在 Filters 文件夾下創(chuàng)建一個(gè)類川陆,命名為 HeaderFooterFilter,然后通過(guò)將它繼承 ActionFilterAttribute 類來(lái)將其升級(jí) Action 過(guò)濾器蛮位。

第三步:升級(jí) ViewModel

在 HeaderFooterFilter 類中重寫 OnActionExecuted较沪。在這個(gè)方法中我們將會(huì)得到當(dāng)前的視圖模型,然后將其附上 Header 和 Footer 數(shù)據(jù)土至。

public class HeaderFooterFilter : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        ViewResult v = filterContext.Result as ViewResult;
        if(v!=null) // v will null when v is not a ViewResult
        {
                BaseViewModel bvm = v.Model as BaseViewModel;
                if(bvm!=null)//bvm will be null when we want a view without Header and footer
                {
                        bvm.UserName = HttpContext.Current.User.Identity.Name;
                        bvm.FooterData = new FooterViewModel();
                        bvm.FooterData.CompanyName = "StepByStepSchools";//Can be set to dynamic value
                        bvm.FooterData.Year = DateTime.Now.Year.ToString();            
                }
        }
    }
}

OnActionExecuted 方法用于添加行為方法執(zhí)行的邏輯操作购对。

第四步:附上過(guò)濾器

在 Index,AddNew 和 SaveEmployee 方法中附上 HeaderFooterFilter陶因。

[HeaderFooterFilter]
public ActionResult Index()
{
    EmployeeListViewModel employeeListViewModel = new EmployeeListViewModel();
...
}
...
[AdminFilter]
[HeaderFooterFilter]
public ActionResult AddNew()
{
    CreateEmployeeViewModel employeeListViewModel = new CreateEmployeeViewModel();
    //employeeListViewModel.FooterData = new FooterViewModel();
    //employeeListViewModel.FooterData.CompanyName = "StepByStepSchools";
...
}
...
[AdminFilter]
[HeaderFooterFilter]
public ActionResult SaveEmployee(Employee e, string BtnSubmit)
{
    switch (BtnSubmit)
    {
        ...

第五步:執(zhí)行并測(cè)試

按下 F5,執(zhí)行應(yīng)用垂蜗。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 5 天

6. 總結(jié)

這里我們已經(jīng)完成了第五天的學(xué)習(xí)楷扬。接下來(lái)的第六天學(xué)習(xí)是最困難的解幽,也是最有意思的。

繼續(xù)保持學(xué)習(xí)的熱情吧烘苹!

原文地址:Learn MVC Project in 7 days

本文系 OneAPM 工程師編譯整理躲株。OneAPM 是應(yīng)用性能管理領(lǐng)域的新興領(lǐng)軍企業(yè),能幫助企業(yè)用戶和開(kāi)發(fā)者輕松實(shí)現(xiàn):緩慢的程序代碼和 SQL 語(yǔ)句的實(shí)時(shí)抓取镣衡。想閱讀更多技術(shù)文章霜定,請(qǐng)?jiān)L問(wèn) OneAPM 官方博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末廊鸥,一起剝皮案震驚了整個(gè)濱河市望浩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌惰说,老刑警劉巖磨德,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異吆视,居然都是意外死亡典挑,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門啦吧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)您觉,“玉大人,你說(shuō)我怎么就攤上這事授滓×账” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵褒墨,是天一觀的道長(zhǎng)炫刷。 經(jīng)常有香客問(wèn)我,道長(zhǎng)郁妈,這世上最難降的妖魔是什么浑玛? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮噩咪,結(jié)果婚禮上顾彰,老公的妹妹穿的比我還像新娘。我一直安慰自己胃碾,他們只是感情好涨享,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著仆百,像睡著了一般厕隧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天吁讨,我揣著相機(jī)與錄音髓迎,去河邊找鬼。 笑死建丧,一個(gè)胖子當(dāng)著我的面吹牛排龄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播翎朱,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼橄维,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了拴曲?” 一聲冷哼從身側(cè)響起争舞,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎疗韵,沒(méi)想到半個(gè)月后兑障,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蕉汪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年流译,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片者疤。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡福澡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出驹马,到底是詐尸還是另有隱情革砸,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布糯累,位于F島的核電站算利,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏泳姐。R本人自食惡果不足惜效拭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望胖秒。 院中可真熱鬧缎患,春花似錦、人聲如沸阎肝。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)风题。三九已至判导,卻和暖如春嫉父,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背骡楼。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工熔号, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留稽鞭,地道東北人鸟整。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像朦蕴,于是被迫代替她去往敵國(guó)和親篮条。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容