7 天玩转 ASP.NET MVC — 第 5 天

目录

0. 前言

欢迎来到第五天的学习。希望第一天到第四天的学习,你都是开心的。

1. Lab 22 — 增加 Footer

在这个实验中,我们将会向 Employee 页面添加 Footer。本次实验的目标是理解分部视图(Partial Views)。

什么是「Partial Views」?

逻辑上讲,分部视图(Partial Views) 是一个可重用的视图,它不会被直接显示。它会被其它视图所包含,然后作为该视图的一部分来显示。它类似于 ASP.NET Web Forms 中的用户控件,但是没有后台代码。

第一步:为 Partial View 创建 ViewModel

右击 ViewModel 文件夹,然后创建一个类,命名为 FooterViewModel。

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

第二步:创建 Partial View

右击「~/Views/Shared」文件夹,选择 Add -> View。

7 天玩转 ASP.NET MVC — 第 5 天
7 天玩转 ASP.NET MVC — 第 5 天

设置视图的名称为 Footer。选中「Create as a partial view」复选框,然后点击「Add」。

注意:我们已经在第一天的学习中谈论了 Shared 文件夹。Shared 文件夹包含了视图,这些视图不会属于一个特定的控制器。在 Shared 文件夹下的视图适用于所有控制器。

第三步:在 Partial View 中显示数据

打开 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 数据

打开 EmployeeListViewModel 类,然后增加一个新的属性来承载 Footer 数据。

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

    public string UserName { get; set; }

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

在我们的例子中,Footer 视图将会作为 Index 视图的一部分展示。

我们将会在 Index 视图中向 Footer 传输必要数据。

Index 视图是一个 EmployeeListViewModel 的强类型视图,因此 Footer 中需要的数据都应该被封装在 EmployeeListViewModel 类中。

第五步:设置 Footer 数据

打开 EmployeeController,然后在 Index 行为方法中设定 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

打开 Index.cshtml 文件,然后在 Table 标签后展示 Footer 视图。

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

第七步:执行并测试

按下 F5。导航到 Index 视图。

7 天玩转 ASP.NET MVC — 第 5 天
7 天玩转 ASP.NET MVC — 第 5 天

Lab 22 的 Q&A

Html.Partial 是用来做什么的?

它类似于 Html.RenderPartial,Html.Partial 用于在视图中展示 Partial View。

它的语法如下。非常简单。

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

两者的区别是什么?

Html.RenderPartial 将会把 Partial View 的结果写入 HTTP 响应流中,而 Html.Partial 将会以 MvcHtmlString 的格式返回结果。

什么是 MvcHtmlString,为什么 Html.Partial 返回的是 MvcHtmlString,而不是字符串?

首先让我们理解下什么是 MvcHtmlString。

MSDN 的定义是「MvcHtmlString 代表一个 HTML 编码的字符串,这种字符串不应该再次编码」。

更好地理解这个定义,请查看下面代码。

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

它将会产生如下输出。

7 天玩转 ASP.NET MVC — 第 5 天
7 天玩转 ASP.NET MVC — 第 5 天

正如你所看见的,Razor 展示了所有的内容。可能许多人会以为将输出加粗的字符串,但是 Razor Html 在展示之前对内容进行了编码,这就是为什么我们获得的是纯内容,而不是加粗的字符串。

当我们不想用 Razor 编码时,我们可以使用 MvcHtmlString。MvcHtmlString 是 Razor 的一种表示,即「字符串已经编码了,不再需要额外编码」。

例如我们可以看下面的代码。

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

它将会产生如下输出。

7 天玩转 ASP.NET MVC — 第 5 天
7 天玩转 ASP.NET MVC — 第 5 天

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

我们已经理解了「Razor 将会编码字符串,但是不会对 MvcHtmlString 编码」这一事实。如果 Partial View 内容被认为是像它展示的那样的纯字符串,便没有意义。我们希望它被当成是一个 HTML 内容,这样我们就需要停止 Razor 编码,因此 Partial 方法被设计为返回 MvcHtmlString。

哪个更加推崇,Html.RenderPartial 还是 Html.Partial ?

Html.RenderPartial 更被推崇,因为它更快。

什么时候运用 Html.Partial 更好?

当我们想在展示之前改变 Partial View 返回的结果,推荐使用 Html.Partial。

打开 Index.cshtml,然后打开 Footer,放置如下代码。

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

现在页脚展示如下。

7 天玩转 ASP.NET MVC — 第 5 天
7 天玩转 ASP.NET MVC — 第 5 天

为什么将 Partial View 放置在 Shared 文件夹下?

因为 Partial View 意味着可以重复利用的资源,因此放置它们的地点是 Shared 文件夹下。

我们不能将 Partial View 放置到一个特殊的控制器文件夹内吗?例如 Employee 或者 Authentication?

我们可以这样做,但是在这种场景下,它将不会适用于指定控制器。

例如:当我们将 Partial View 放置到 Employee 文件夹下,它将不会适用于 AuthenticationController 或者适用于 AuthenticationController 相关的视图。

为什么 Partial View 的定义包含「逻辑」词汇?

在定义中,我们已经知道 Partial View 是一个可重用的视图,但是它不能通过自己执行。它需要放置到其它视图中,然后作为这些视图的一部分来展示。

我们所说的 Partial View 可重用是事实,但是我们提到的执行在逻辑上是事实。技术上而言,这不是一个正确的解释。我们可以创建一个行为方法,来返回如下的视图结果。

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);
}

它将会展示如下的输出。

7 天玩转 ASP.NET MVC — 第 5 天
7 天玩转 ASP.NET MVC — 第 5 天

尽管在逻辑上没有意义,但是技术上是可行的。Footer.cshtml 不会包含正确的结构性 HTML。它意味着作为视图的一部分来展示。因为我们说「逻辑上是没有意义的」。

为什么要创建 Partial View,而不是直接在视图底部添加内容?

这样做有两个优势。

  1. 可重用性。我们可以将一个 Partial View 运用到多个视图中去。

  2. 代码保留。将其放置为一个分割的文件,使其管理和操纵都非常方便。

为什么在 Partial View 中没有创建 Header?

作为最佳实践,我们需要为 Partial View 创建 Header,但是为了保持最初实验简单化,我们并没有这样做。

2. Lab 23 — 实现基于角色的安全性

在本次实验中,我们将会实现 Admin 和 Non-Admin 两种登录功能。

需求是很简单的。即「Non-Admin 用户不能创建 Employees」。

通过这个实验,我们将会理解 MVC 中的两个主题。

  • Session

  • Action Filters

现在我们开始进行实验。为了简单化,我们将实验分为两部分。

Part 1 — Non-Admin 用户登录,隐藏 AddNew 链接

第一步:创建标识 UserStatus 的枚举

右击 Models 文件夹,选择「Add New Item」。

在对话框中选择「Code File」选项。

7 天玩转 ASP.NET MVC — 第 5 天
7 天玩转 ASP.NET MVC — 第 5 天

在名称栏中输入「UserStatus」,然后点击添加。「Code File」的选项将会创建一个空白的「.cs」文件。

创建一个枚举,命名为 UserStatus,代码如下。

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

第二步:更改业务层功能

删除 IsValidUser 功能,然后创建一个新的功能,命名为 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 行为方法

打开 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");
    }
}

正如你所看见的,我们运用 Session 变量来识别用户是 Admin 用户还是 non-Admin 用户。

不知道什么是 Session?

Session 是 ASP.NET 的一个功能,在 ASP.NET MVC 中被重用。

我们运用 Session 变量来承载用户相关的数据。Session 的生命周期取决于用户的生命周期。它将一直可用直到当前的 Session 结束。

第四步:删除已经存在的 AddNew 链接

在「~/Views/Employee」文件夹下打开 Index.cshtml 视图,然后完全删除「Add New」超链接。

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

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

第五步:创建 Partial View

右击「~/Views/Employee」文件夹,然后选择 Add -> View。设置视图的名称为「AddNewLink」,然后确保选择「Create as a partial view」复选框。

7 天玩转 ASP.NET MVC — 第 5 天
7 天玩转 ASP.NET MVC — 第 5 天

第六步:在 Partial View 中放置内容

在刚创建的 Partial View 中放置如下内容。

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

第七步:创建行为方法

打开 EmployeeController 然后创建一个新的行为方法,命名为「GetAddNewLink」。

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

第八步:展示 AddNew 链接

打开 Index.html,然后放置如下代码。

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

Html.RenderAction 执行行为方法,然后向响应流中直接写入结果。

第九步:执行并测试

按下 F5,然后执行应用。

  • Test 1
7 天玩转 ASP.NET MVC — 第 5 天
7 天玩转 ASP.NET MVC — 第 5 天
  • Test 2
7 天玩转 ASP.NET MVC — 第 5 天
7 天玩转 ASP.NET MVC — 第 5 天

Part 2 — 直接的 URL 安全性

按照上述的逻辑,一件事是可以确保的。即现在 non-Admin 用户不能通过超链接导航到 AddNew 行为。

这样就够了吗?

答案是否定的,这还不够。如果一个 non-Admin 用户直接通过 URL 试图导航到 AddNew 行为会发生什么呢。

7 天玩转 ASP.NET MVC — 第 5 天
7 天玩转 ASP.NET MVC — 第 5 天

正如你在上述例子中所看见的,一个 non-Admin 用户依然可以访问 AddNew 行为。

为了解决这个问题,我们需要运用 MVC 中的 Action Filters。Action Filters 让我们向行为方法中添加一些预处理和后处理的逻辑。在本实验中,我们将着重于 Action Filters 的预处理功能,在后面的实验中,我们再着重于后处理功能。

第一步:设置过滤器

在项目下创建一个新的文件夹,命名为 Filters,然后创建一个新的类,命名为 AdminFilter。

7 天玩转 ASP.NET MVC — 第 5 天
7 天玩转 ASP.NET MVC — 第 5 天

第二步:创建过滤器

升级简单的 AdminFilter 类到 ActionFilter,通过将其继承 ActionFilterAttribute 类,代码如下。

public class AdminFilter:ActionFilterAttribute
{

}

注:为了运用 ActionFilterAttribute,你需要在顶部引用 System.Web.Mvc。

第三步:增加安全认证逻辑

在 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."
        };
    }
}

第四步:附加过滤器

向 AddNew 和 SaveEmployee 行为方法添加过滤器。

[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();
....
....

第五步:执行和测试

按下 F5,然后执行应用。运用 non-Admin 身份登录,然后试图通过 AddNew 行为的 URL 导航到 AddNew 行为方法。

7 天玩转 ASP.NET MVC — 第 5 天
7 天玩转 ASP.NET MVC — 第 5 天

正如你所看见的,现在你的行为方法处于完全安全状态。

Lab 23 的 Q&A

我们可以通过地址栏直接触发 GetAddNewLink 吗?

答案是肯定的,我们已经在「Talk on Lab 22」部分讨论了这个行为。

直接停止执行 GetAddNewLink 有可能吗?

可以直接在 GetAddNewLink 中添加 ChildActionOnly 属性。

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

Html.Action 是用来做什么的?

就类似于 Html.RenderAction,Html.Action 将会执行行为方法用于呈现视图的结果。下面是语法。

@Html.Action("GetAddNewLink");

可以看出,语法相对来说简单多了。

两者的区别是什么?

Html.RenderAction 将会把行为方法的执行结果直接写入 HTTP 响应流,而 Html.Action 将会返回 MvcHtmlString 结果。

哪个更推崇?是 Html.RenderAction 还是 Html.Action?

更推崇 Html.RenderAction,因为它更快。

什么时候用 Html.Action 更好?

当我们想在呈现之前改变行为方法执行的结果时,用 Html.Action 更好。

什么是 ActionFilter?

就类似于 AuthenticationFilter,ActionFilter 是 ASP.NET MVC 的一种过滤器类型。它允许我们向行为方法添加预处理和后处理逻辑。

3. Lab 24 — 任务实验 — 处理 CSRF 攻击

从视图的安全性方面出发,我们还需要在项目中处理 CSRF 攻击。这里我将不再做过多指导,你需要自己手动完成。

我建议你阅读下述文章,然后实现方法。

如何在 MVC 中防止 CSRF 攻击

4. Lab 25 — 实现项目的一致性外观

在 ASP.NET 领域中,一致性的布局意味着母版页(MasterPage)。

但 ASP.NET MVC 是区别于此的。在 Razor 中,母版页被称为布局页(Layout Pages)。

在正式开始试验之前,我们先来讨论一下在母版页中我们需要放置哪些元素。

1.带有欢迎信息的 Header。
2.带有页脚数据的 Footer。

最大的问题是什么?

页脚和页眉的数据作为 ViewModel 的一部分从控制器传输到视图中。

7 天玩转 ASP.NET MVC — 第 5 天
7 天玩转 ASP.NET MVC — 第 5 天

现在最大的问题便是,当页眉和页脚移动到布局页后,数据如何从视图传输到布局页。

解决方案 — 继承

在这里我们可以简单地遵循面向对象继承准则。让我们通过一个小实验来理解。

第一步:创建 ViewModel 的基类

在 ViewModel 文件夹下创建一个新的 ViewModel 类,称为 BaseViewModel 类。

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

正如你所看见的,BaseViewModel 封装了 Layout 页所需的所有元素。

第二步:准备 EmployeeListViewModel

从 EmployeeListViewModel 类中移除 UserName 和 FooterData 属性,然后让它继承 BaseViewModel 类。

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

第三步:创建布局页

右击 Shared 文件夹,选择 Add -> MVC 5 Layout Page。输入名称为 MyLayout,然后点击确定。

它将会创建一个如下格式的代码。

<!DOCTYPE html>

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

第四步:转换布局页为强类型布局

在布局页的上方放置如下的简单声明,使其变为强类型布局。

@using WebApplication1.ViewModels
@model BaseViewModel

第五步:设计布局页

在布局页添加页眉,页脚和内容三部分。

<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>

正如你所看见的,我们已经为布局页创建了三块。Title 部分,Header 部分和Content 部分。内容页面将会用到这三部分来定义合适的内容。

第六步:向 Index 视图附上 Layout 页面

打开 Index.cshtml 页面,在顶部会发现如下代码。

@{
    Layout = null;
}

将这段代码改为如下代码。

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

第七步:设计 Index 视图

  1. 从 Index 视图中移除 Headers 和 Footers。
  2. 复制 Body 标签中的剩余内容,然后将它保存在别处。
  3. 复制 Title 标签里的内容。
  4. 将视图中的所有 HTML 内容都移除。确保你只是移除了 HTML,@model 和布局声明不需要被移除。
  5. 定义 Title 部分和 Content 部分,内容是刚才所复制下的内容。

完整的视图将会如下所示。

@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>
}

正如你所看见的,视图中所有的元素都定义在指定的位置上。

第八步:执行并测试

按下 F5,然后执行应用。导航到 Index 行为上。

7 天玩转 ASP.NET MVC — 第 5 天
7 天玩转 ASP.NET MVC — 第 5 天

第九步:在 CreateEmployee 视图中附上 Layout 页面

打开 Index.cshtml 页面,在顶部会发现如下代码。

@{
    Layout = null;
}

将其改为如下代码。

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

第十步:设计 CreateEmployee 视图

像第七步的步骤一样,定义 CreateEmployee 视图的区域。这一次会增加一点。我们将会定义 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>
}

第十一步:执行并测试

按下 F5,然后执行应用。通过尝试超链接来导航到 AddNew 行为上。

7 天玩转 ASP.NET MVC — 第 5 天
7 天玩转 ASP.NET MVC — 第 5 天

Index 视图是 EmployeeListViewModel 的强类型视图,EmployeeListViewModel 又是 BaseViewModel 的子类,所以 Index 视图可以运转。但是 CreateEmployee 视图是 CreateEmployeeViewModel 的强类型视图,而 CreateEmployeeViewModel 不是 BaseViewModel 的子类,所以 CreateEmployee 出现了这样的错误。

第十二步:准备 CreateEmployeeViewModel

让 CreateEmployeeViewModel 继承 BaseViewModel,代码如下。

public class CreateEmployeeViewModel:BaseViewModel
{
...

第十三步:执行并测试

再一次测试。

7 天玩转 ASP.NET MVC — 第 5 天
7 天玩转 ASP.NET MVC — 第 5 天

这次的错误看起来与之前的不一样。错误真正的原因是,我们在 AddNew 行为中没有初始化 Header 和 Footer 的数据。

第十四步:初始化 Header 和 Footer 数据

将 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 数据

类似于 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();
}

第十六步:执行并测试

按下 F5,然后执行应用。

7 天玩转 ASP.NET MVC — 第 5 天
7 天玩转 ASP.NET MVC — 第 5 天

Lab 25 的 Q&A

RenderBody 是用于做什么的?

当我们第一次创建 Layout 页面时,我们有一个 Razor 声明如下。

@Html.RenderBody()

现在让我们来理解下它是做什么的。在内容页面上,我们正常地定义区域,这些区域在布局页声明。

但是奇怪的是,Razor 允许我们在区域外定义一些内容。在内容页面上,所有非区域内的内容将会被 RenderBody 函数呈现。

下图将会更好地进行解释。

7 天玩转 ASP.NET MVC — 第 5 天
7 天玩转 ASP.NET MVC — 第 5 天

我们有嵌套的布局吗?

答案是肯定的。我们能创建一个嵌套了其它布局页的布局页。语法是一样的。

在每一个视图中都指定布局页是必须的吗?

你能够在 Views 文件夹下发现一个特殊的布局页,称为「__ ViewStart.cshtml」。在其内部设定定义,将会应用于所有视图。

例如,在「__ ViewStart.cshtml」中放置如下的代码,将会使「_Layout.cshtml」适用于所有视图的布局。

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

在每一个行为方法中,是否都需要放置 Header 和 Footer 的数据代码?

答案是否定的。我们可以运用 Action 过滤器来避免这种重复。我们将会在接下来的实验中实践。

在子视图中定义所有区域是否是必须的?

答案是肯定的。如果 Section 的声明为必须的,那么默认值是 True。

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

5. Lab 26 — 运用 Action 过滤器让 Header 和 Footer 数据更高效

在 Lab 23 中,我们已经知道了 ActionFilter 的优势,现在我们来看它的第二点优势。

第一步:从行为方法中删除冗余代码

从 Index,AddNew 和 SaveEmployee 方法中删除 Header 和 Footer 数据的代码。

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();           

第二步:创建 HeaderFooterFilter

在 Filters 文件夹下创建一个类,命名为 HeaderFooterFilter,然后通过将它继承 ActionFilterAttribute 类来将其升级 Action 过滤器。

第三步:升级 ViewModel

在 HeaderFooterFilter 类中重写 OnActionExecuted。在这个方法中我们将会得到当前的视图模型,然后将其附上 Header 和 Footer 数据。

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 方法用于添加行为方法执行的逻辑操作。

第四步:附上过滤器

在 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)
    {
        ...

第五步:执行并测试

按下 F5,执行应用。

7 天玩转 ASP.NET MVC — 第 5 天
7 天玩转 ASP.NET MVC — 第 5 天

6. 总结

这里我们已经完成了第五天的学习。接下来的第六天学习是最困难的,也是最有意思的。

继续保持学习的热情吧!

原文地址:Learn MVC Project in 7 days

本文系 OneAPM 工程师编译整理。OneAPM 是应用性能管理领域的新兴领军企业,能帮助企业用户和开发者轻松实现:缓慢的程序代码和 SQL 语句的实时抓取。想阅读更多技术文章,请访问 OneAPM 官方博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容