ASP.NET Core完善MVC框架

为什么需要在ASP.NET Core MVC中使用ViewModel

​ 在某些情况下,Model对象可能无法包含View所需的所有数据。这个时候就需要使用ViewModel了,它会包含所需的所有数据,请参考以下Details()方法。注意,需要将学生的详细信息和PageTitle传递给View。

public class HomeController:Controller
    {
        private readonly IStudentRepository _studentRepository;

        public HomeController(IStudentRepository studentRepository)
        {
            _studentRepository = studentRepository;
        }



        public ViewResult Details()
        {
            Student model = _studentRepository.GetStudent(1);
            ViewBag.PageTitle = "学生详情";
            
            return View(model);
        }
	}

​ 我们使用Student模型对象传递学生的详细信息,而是用ViewBag传递PageTitle。使用ViewBag将数据从Controller传递到View会创建一个弱类型的视图,因此我们不会得到智能提示,以及编译时类型和错误检查。

​ 在Student类中包含PageTitle属性没有任何意义,因为Student类应该只包含与学生相关的属性。此时我们就需要创建一个能包含View所需的数据类,该类称为ViewModel。

ViewModel示例

​ ViewModel类可以放在ASP.NET Core MVC项目的任何位置。但为了管理方便,我们通常将它们放在一个名为ViewModels的文件夹中。

​ 我们将创建一个名为HomeDetailsViewModel的类。这个类的名称中有Home一词,这是因为Controller的名称是HomeController。因为操作方法的名称是Details(),所以包含单词Details()。

​ 此ViewModel类包含View所需的所有数据。通常,我们使用ViewModels在View和Controller之间传递数据。因此,VIewModels也简称为数据传输对象或DTO(Data Transfer Object,数据传输对象)。

​ 现在可以使用此ViewModel装载Student类的数据和PageTitle,然后从Controller传递数据到View,代码如下:

public class HomeDetailsViewModel
    {
        public Student  Student { get; set; }
        public string PageTitle { get; set; }
    }

在这里插入图片描述

在Controller中使用ViewModel

​ 在Controller中使用ViewModel的代码如下:

 public ViewResult Details()
        {
            //实例化HomeDetailsViewModel并存储Student详细信息和PageTitle
            HomeDetailsViewModel homeDetailsViewModel = new HomeDetailsViewModel()
            {
                Student = _studentRepository.GetStudent(1),
                PageTitle = "学生详情"
            };
            //将ViewModel对象传递给View()方法
            return View(homeDetailsViewModel);
        }

在视图中使用ViewModel

  • 该视图可以使用VIew()方法访问控制器中的ViewModel对象。
  • 使用@model指令,将HomeDetailsViewModel设置为视图的Model。
  • 然后就可以访问学生的详细信息和PageTitle的属性。
  • 请注意,@model指令中的 ‘m’ 为小写字母,@Model属性的 **“M”**为大写字母。
@model WebApplication12.ViewModels.HomeDetailsViewModel
<html>
    <head>
            <title>Details</title>
    </head>
    <body>
        <h3>@Model.PageTitle</h3>
        <div>
            姓名:@Model.Student.Name
        </div>
        <div>
            邮箱:@Model.Student.Email
        </div>
        <div>
            主修科目:@Model.Student.Major
        </div>
    </body>
</html>

在ASP.NET Core MVC中实现List视图

​ 让我们通过一个示例来理解这一点。我们想要查询所有学生信息并将其显示在网页上,如表所示:

IDNameEmailMajor
1张三zhangsan@qq.com计科
2李四lisi@foxmail.com信安

修改IStuentRepository中的代码

​ 修改IStudentRepository接口以包含GetAllStudents()方法,此方法返回所有学生的列表信息。

public interface IStudentRepository
    {
        Student GetStudent(int ID);
        IEnumerable<Student> GetAllStudents();
    }

修改StudentRepository中的代码

​ 目前,在我们的应用程序中,只有一个实现IStudentRepository接口的类(StudentRepository)。因此修改StudentRepository类文件,实现GetAllStudents()方法。

​ 注意,GetAllStudents()返回的是_studentList中的学生列表,而这些数据都是我们硬编码来的。

 public class StudentRepository : IStudentRepository
    {
        private List<Student> _studentList;
        public StudentRepository()
        {
            _studentList = new List<Student>() {
                new Student(){ID=1,Name="张三",Major="计科",Email="zhangsan@qq.com"},
                new Student(){ID=2,Name="李四",Major="信安",Email="lisi@outlook.com"},
                new Student(){ID=3,Name="王五",Major="网工",Email="wangwu@foxmail.com"}
            };
        }

        public IEnumerable<Student> GetAllStudents()
        {
            return _studentList;
        }

        public Student GetStudent(int ID)
        {
            return _studentList.FirstOrDefault(a => a.ID == ID);
        }
    }

修改HomeController中的代码

​ 我们需要添加HomeController()中的Index()方法,如下所示:

public class HomeController:Controller
    {
        private readonly IStudentRepository _studentRepository;

        public HomeController(IStudentRepository studentRepository)
        {
            _studentRepository = studentRepository;
        }

        //返回学生的信息
        public ViewResult Index()
        {
            //查询所有的学生信息
            var model = _studentRepository.GetAllStudents();
            //将学生列表传递到视图
            return View(model);
        }


        public ViewResult Details()
        {
            //实例化HomeDetailsViewModel并存储Student详细信息和PageTitle
            HomeDetailsViewModel homeDetailsViewModel = new HomeDetailsViewModel()
            {
                Student = _studentRepository.GetStudent(1),
                PageTitle = "学生详情"
            };
            //将ViewModel对象传递给View()方法
            return View(homeDetailsViewModel);
        }
    }

​ 请注意,我们通过调用GetAllStudents()方法查询学生列表,并将该列表传递给View。

视图Index.cshtml中代码的变化

​ 使用@model指令来为View指定模型IEnumberable<WebApplication12.Models.Student>,同时在View指令中使用foreach循环遍历学生列表,并动态生成表单的行和单元格以显示学生信息,代码如下:

@model IEnumerable<WebApplication12.Models.Student>
<html>
    <head>
            <title>学生详细信息</title>
    </head>
    <body>
        <table align="center" border="1" cellpadding="0" cellspacing="0" width="500" height="250">
            <thead >
                <tr>
                    <th>ID</th>
                    <th>Name</th>
                    <th>Email</th>
                    <th>Major</th>
                </tr>
            </thead>
            <tbody>
                @foreach (var student in Model)
                {
                    <tr>
                        <td >@student.ID</td>
                        <td>@student.Name</td>
                        <td>@student.Email</td>
                        <td>@student.Major</td>
                    </tr>
                }
            </tbody>
        </table>
    </body>
</html>

在这里插入图片描述

布局视图

​ 如果读者平时浏览网页,会发现大多Web应用程序网站由以下部分组成。

  • Header——头部。
  • Footer——页脚。
  • Menu——导航菜单。
  • View——具体内容的视图。

学过前端的同学,一定可以理解这一点。没学过的,可以看看淘宝、京东等网站,观察一下,基本上由上述四部分组成。

在这里插入图片描述

​ 如果没有布局视图,我们将在Web程序中的每个视图中重复显示很多HTML代码,比如菜单栏、导航信息、关于我们和页脚等。在每个视图中都有这些重复的HTML,这样Web程序的维护是一场灾难。

​ 比如,我们需要在导航菜单中添加或删除菜单项,或更改页眉或页脚。我们必须在每个视图中进行此修改,这显然单调乏味、耗时且容易出错,试想读者如果有400个视图文件需要维护会怎么样。那么我们可以在布局视图中定义它们,然后在所有视图中继承该布局视图文件,而不是在每个视图中都包含所有这些部分。

​ 使用布局视图,让所有视图保持一致的外观变得更加容易,因为如果有任何更改,则只要一个要修改的布局视图文件,然后更改后将立即反映在整个应用程序的所有视图中,这样易于维护而且还提升效率。

ASP.NET Core MVC中的布局视图

​ 就像常规视图一样,布局视图也具有扩展名为.cshtml的文件。读者如果使用过WebForm,则可以将布局视图视为ASP.NET Web Form中的母版页。

​ 由于布局视图不特定于控制器,因此它通常放Views文件夹的子文件夹Shared中。默认情况下,在ASP.NET Core MVC中,布局视图文件名为**_Layout.cshtml**。

​ 在ASP.NET Core MVC中有一些视图文件,如**_Layout.cshtml、_ViewStart.cshtml_ViewImports.cshtml等,它们的文件名以下划线开头。这些文件名中的下划线**表示这些文件不是直接面向浏览器的,因此用户无法直接访问它们。它们是服务于应用程序的。一个应用程序中包含多个布局视图文件。比如一个布局试图文件服务于管理用户,另一个不同的布局视图文件服务于普通用户。

创建布局视图

​ 创建布局视图的步骤如下:

  • 右击Views文件夹并添加Shared文件夹。
  • 右击Shared文件夹,然后选择添加➡新建项
  • 添加新项窗口中选择Razor布局并单击添加按钮。
  • 名为**_Layout.cshtml**的文件将添加到Shared文件夹。
  • 打开**_Layout.cshtml**文件,其中已经自动生成HTML代码。

以下是**_Layout.cshtml**中默认生成的HTML代码。

<!DOCTYPE html>

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

​ 请注意,此布局视图文件已经包含了**<html>、<head>、<title>和<body>**元素。现在将它们放在布局试图文件中,因此我们不必在每个视图中重复所有这些HTML。

​ 使用@ViewBag.Title指令查看特定标题。比如,当使用此布局试图文件呈现Index.cshtml视图时,Index.cshtml将在ViewBag上设置Title属性。

​ 使用指令@ViewBag.Title通过布局视图检索它,并将其设置为<title>元素的值。

​ ViewBag不提供智能提示和编译时错误检查。因此,使用它将大量数据从普通Razor视图传递到布局视图并不是很好,但是传递像PageTitle这样非常小的内容,就很合适了。

@RenderBody()是注入视图特定内容的位置。比如,如果使用此布局视图呈现Index.cshtml视图,则会在我们调用@RenderBody()方法的位置注入Index.cshtml视图内容。

使用布局视图

​ 要使用布局视图(_Layout.cshtml)渲染视图,需要设置Layout属性。比如,要将布局视图与Details.cshtml一起使用,需要修改Details.cshtml中的代码以包含Layout属性,如下所示:

@model WebApplication12.ViewModels.HomeDetailsViewModel
@{ 
    Layout = "/Views/Shared/_Layout.cshtml";
    ViewBag.Title = "Student Details";
}

        <h3>@Model.PageTitle</h3>
        <div>
            姓名:@Model.Student.Name
        </div>
        <div>
            邮箱:@Model.Student.Email
        </div>
        <div>
            主修科目:@Model.Student.Major
        </div>

​ 有一种更好的方法来设置Layout属性,而不是在每个视图中设置它,我们将在后面的内容中学习此方法。

​ 总体来说,关于布局视图,我们需要了解以下内容。

  • 布局视图是让Web应用程序中所有的视图保持外观一致。
  • 布局视图看起来像ASP.NET WebForm中的母版页。
  • 布局视图也具有.cshtml扩展名。
  • 在ASP.NET Core MVC中,默认情况下布局文件名为_Layout.cshtml。
  • 布局视图文件通常放在Views/Shared文件夹中。
  • 在一个应用程序中可以包含多个布局视图文件。

布局页面的节点

​ ASP.NEt Core MVC中的布局页面还可以包含一些节点。节点可以是必需的,也可以是可选的,它提供了一种方法让某些页面元素有组织地放置在一起。

布局页面示例

​ 假设读者有一个自定义Javascript文件,如果所有视图都需要这个自定义Javascript文件,那么我们可以将它放在布局页面中,如下所示:

<!DOCTYPE html>

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

​ 在这个例子中,不需要在每个视图中使用自定义Javascript文件。假设我们只在Details视图中需要它,而在其他视图中不需要它,就可以使用一个节点。

渲染节点

​ 在布局页面中,在要渲染节点内容的位置调用RenderSection()方法。在本例中,我们把@RenderSection()放置在<body>结束元素之前。

​ RenderSection()方法有两个参数。第一个参数指定节点的名称,第二个参数指定该节点是必需的还是可选的。

<!DOCTYPE html>

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

​ 如果将required设置为true,而内容视图不包含该部分,则会出现以下错误。

在这里插入图片描述

使布局部分可选

有两个选项将布局部分标记为可选。

  • 选项1:将RenderSection()方法的必需参数设置为false。
@RednerSection("Scripts",required:false);
  • 选项2:使用IsSectionDefined()方法
@if (IsSectionDefined("Scripts"))
{
    @RenderSection("Scripts",required:false);
}

节点的使用

​ 要使用节点,那么每个视图都必须包含具有相同名称的部分。我们使用@section指令包含该部分并提供如下所示的内容。

​ 在我们的示例中希望Details视图在布局页面的Scripts节点中包含<script>元素。为此,我们在Details.cshtml中包含Scripts节点,如下所示:

@model WebApplication12.ViewModels.HomeDetailsViewModel
@{ 
    Layout = "/Views/Shared/_Layout.cshtml";
    ViewBag.Title = "Student Details";
}

        <h3>@Model.PageTitle</h3>
        <div>
            姓名:@Model.Student.Name
        </div>
        <div>
            邮箱:@Model.Student.Email
        </div>
        <div>
            主修科目:@Model.Student.Major
        </div>
@section Scripts{ 
<script src="/js/JavaScript.js"></script>
}

​ 运行此项目测试节点是否生效。当导航到/Home/Details时,查看网页源代码,可以看到<script>元素位于</body>结束元素之前。

在这里插入图片描述

​ 而我们导航到/Home/Index查看网页源码,看不到<script>元素,如图所示,表示节点已经正常运行。

在这里插入图片描述

什么是_ViewStart.cshtml文件

​ 在当前Details.cshtml中,通过代码Layout="~/Views/Shared/_Layout.cshtml";的形式指定布局页。Layout属性会将视图与布局视图相关联。但是这样会引发一个问题,如果要使用其他布局文件,则需要更新每个视图。试想,如果项目有400个视图文件,而现在要修改其中200个文件,这项工作不仅繁琐而且耗时,而且还容易出错。MVC为我们提供了Razor试图开始文件,也就是**_ViewStart.cshtml**文件。

​ 如果没有**_ViewStart.cshtml文件,那么我们需要在每个视图中设置Layout**属性,这违反了DRY(Don’ t Repeat Yourself)原则,并具有以下缺点:

  • 冗余代码。
  • 维护成本高。

ASP.NET Core MVC中的_ViewStart.cshtml文件

​ 该文件是ASP.NET Core MVC中的一个特殊文件,该文件中的代码在调用单个视图中的代码之前执行。我们将公共代码放到**_ViewStart.cshtml**文件中,而不是在每个单独的视图中设置Layout属性。

  • 右击Views文件夹,然后依次选择添加新建项
  • 添加新项窗口中选择Razor视图开始并单击添加按钮。
  • 将名为**_ViewStart.cshtml的文件添加到Views**文件夹中。
  • 打开**_ViewStart.cshtml**文件,其中已经自动生成的HTML代码如下图所示。
@{
    Layout = "_Layout";
}

​ 通过在_ViewStart.cshtml文件中设置Layout属性,维护应用程序变得更加容易。如果想要使用不同的布局文件,则只需要在**_ViewStart.cshtml中的一个位置更改代码。但是好像还是没有解决400个页面中要修改200个布局页面的问题,这就要使用_ViewStart.cshtml**的分层功能。

_ViewStart.cshtml文件支持分层

​ 我们通常把**_ViewStart.cshtml文件放在Views文件夹中。由于此文件支持分层,因此也可以将它放在Views**文件中的任何子文件夹中,如图所示:

在这里插入图片描述

​ 在上面的文件夹结构中,我们在Views文件夹中放置了一个_ViewStart.cshtml文件,在Home子文件夹中放置了另一个_ViewStart.cshtml文件。

Home子文件夹的_ViewStart.cshtml文件中指定的布局页面,将覆盖Views文件夹下_ViewStart.cshtml文件中指定的布局页面。

​ 这意味着,Views文件夹的所有视图都使用Views中_ViewStart.cshtml文件指定的布局页面,但Home文件夹中的视图将使用Home文件夹中_ViewStart.cshtml文件指定的布局页面。

​ 请注意,如果要使用_ViewStart.cshtml中指定的布局文件,可以通过在单个视图中设置Layout属性来实现。

​ 当有特殊需求的时候,比如某视图希望在不使用布局视图的情况下渲染视图,那么我们只需要在该视图中将Layout属性设置为null,代码如下。

@{Layout = null; ViewBag.Title="学生详情";}
<h3>@Model.PageTitle</h3>

逻辑判断调用布局视图

​ 在ASP.NET Core MVC应用程序中,我们可以有多个布局视图。比如,应用程序中有以下两个布局视图。

_AdminLayout.cshtml
_NotAdminLayout.cshtml

​ 在_ViewStart.cshtml中可以通过判断登录用户角色是否为Admin来选择对应的布局视图。

@
{
	if (User.IsInRole("Admin"))
    {
        Layout="_AdminLayout.cshtml";
	}
    else 
    {
        Layout="_NotAdminLayout.cshtml";
    }
}

修改视图

​ 以下代码是修改后的布局页代码。

Index.cshtml的代码如下:

@model IEnumerable<WebApplication12.Models.Student>
@{ ViewBag.Title = "学生列表";}
<html>
    <head>
            <title>学生详细信息</title>
    </head>
    <body>
        <table align="center" border="1" cellpadding="0" cellspacing="0" width="500" height="250">
            <thead >
                <tr>
                    <th>ID</th>
                    <th>Name</th>
                    <th>Email</th>
                    <th>Major</th>
                </tr>
            </thead>
            <tbody>
                @foreach (var student in Model)
                {
                    <tr>
                        <td >@student.ID</td>
                        <td>@student.Name</td>
                        <td>@student.Email</td>
                        <td>@student.Major</td>
                    </tr>
                }
            </tbody>
        </table>
    </body>
</html>

Details.cshtml中的代码如下:

@model WebApplication12.ViewModels.HomeDetailsViewModel
@{ 
    ViewBag.Title = "Student Details";
}

        <h3>@Model.PageTitle</h3>
        <div>
            姓名:@Model.Student.Name
        </div>
        <div>
            邮箱:@Model.Student.Email
        </div>
        <div>
            主修科目:@Model.Student.Major
        </div>
@section Scripts{ 
<script src="/js/JavaScript.js"></script>
}

_ViewStart.cshtml中的代码如下:

@{
    Layout = "_Layout";
}

项目文件夹结构如图所示:

在这里插入图片描述

ASP.NET Core MVC中的_ViewImports.cshtml文件

​ 创建_ViewImports.cshtml,步骤如下:

  • 右击Views文件夹,然后选择添加➡新建项
  • 添加新项窗口中选择Razor视图导入,并点击添加按钮。
  • 名为_ViewImports.cshtml的文件将添加到Views文件夹中。

_ViewImports.cshtml文件通常放在Views文件夹中。它用于包含公共命名空间,因此我们不必在每个视图中引用这些命名空间。

​ 如果我们在_ViewImports.cshtml文件中包含以下两个命名空间,则这两个命名空间中的所有类型都可用于Home文件夹的每个视图,而无需再次引入完整的命名空间,代码如下:

@using WebApplication12.Models
@using WebApplication12.ViewModels

​ 注意,@using指令用于包含公共命名空间。除@using指令外,_ViewImports.cshtml文件还支持以下指令:

  • @addTagHelper。
  • @removeTagHelper。
  • @tagHelperPrefix。
  • @model。
  • @inherits。
  • @inject。

_ViewStart.cshtml和**_ViewImports.cshtml**文件均支持分层,除了把它们放在Views文件夹中,我们还可以在Views文件夹的Home子文件夹中放置另一个_ViewImports.cshtml。

​ 请注意,如果在视图中指定了命名空间,则该设置将覆盖_ViewImports.cshtml文件中的匹配设置。

总结

​ 现在,学习了布局页得构成,Razor视图和Razor视图导入,以及不同情况下对节点的使用。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值