编程语言
首页 > 编程语言> > 《 Pro ASP.NET Core 6 》--- 读书随记(6)

《 Pro ASP.NET Core 6 》--- 读书随记(6)

作者:互联网

Part 3

CHAPTER 21

内容来自书籍:

Pro ASP.NET Core 6
Develop Cloud-Ready Web Applications Using MVC, Blazor, and Razor Pages (Ninth Edition)

Author: Adam Freeman
需要该电子书的小伙伴,可以留下邮箱,有空看到就会发送的

Using Controllers with Views, Part I

Getting Started with Views

Web API 控制器与使用视图的控制器的相似性。认为 Web API和视图控制器是分开的很容易,但是理解这两种响应类型使用相同的底层特性很重要。

Configuring the Application

builder.Services.AddControllersWithViews();

app.MapControllerRoute("Default", "{controller=Home}/{action=Index}/{id?}");

在之前的章节中,开启MVC 框架的只有Web API特性的Controller是使用AddControllers方法,但是现在要支持View的控制器,需要使用AddControllersWithViews方法

还有Web API的端点路由是使用路由属性来配置的,而使用视图的控制器不使用路由属性来配置URL,而是依赖一个叫做convention routing约定路由的功能

Creating an HTML Controller

public class HomeController : Controller {
        private DataContext context;
        public HomeController(DataContext ctx) {
            context = ctx;
        }
        public async Task<IActionResult> Index(long id = 1) {
            return View(await context.Products.FindAsync(id));
        }
    }

Web API 即基础类是ControllerBase,而 View控制器的基础类是Controller,会另外提供的处理视图的功能

View 方法创建 ViewResult 类的一个实例,该实例实现 IActionResult 接口,并告诉 MVC Framework 应该使用一个视图为客户机生成响应。View 方法的参数称为视图模型,并为视图提供生成响应所需的数据。

Understanding Convention Routing

HTML 控制器依赖约定路由而不是 Route 属性。此术语中的约定是指使用控制器类名称和用于配置路由系统的action方法名称

此语句设置的路由匹配两段和三段 URL。第一段的值用作控制器类的名称,没有 Controller 后缀,因此 Home 引用 HomeController 类。第二个段是 action 方法的名称,可选的第三个段允许 action 方法接收名为 id 的参数。

app.MapDefaultControllerRoute();

MapDefaultControllerRoute 方法避免了输入错误 URL 模式的风险,并设置了基于约定的路由。我在本章中配置了一个路由,但是应用程序可以根据需要定义任意多个路由,后面的章节将扩展路由配置,使示例更容易理解

Understanding the Razor View Convention

当一个操作方法调用 View 方法时,它会创建一个 ViewResult,告诉 MVC Framework 使用默认约定来定位一个视图。Razor 视图引擎查找与 action 方法同名的视图,并添加 cshtml 文件扩展名,这是 Razor 视图引擎使用的文件类型。视图存储在“View”文件夹中,按与其关联的控制器分组。搜索的第一个位置是 Views/Home 文件夹,因为 action 方法是由 Home 控制器定义的(其名称是通过从控制器类的名称中删除 Controller 获得的)。如果在 Views/Home 文件夹中找不到 Index.cshtml 文件,那么将检查 Views/Shared 文件夹,这是存储控制器之间共享的视图的位置。

路由约定用于使用 Home 控制器定义的 Index 操作方法处理请求,该方法告诉 Razor 视图引擎使用视图搜索约定来定位一个视图。视图引擎使用操作方法和控制器的名称来构建其搜索模式,并检查Views/Home/Index.cshtml 和Views/Shared/Index.cshtml 文件

Creating a Razor View

在上文中的视图文件中,可以创建Index.cshtml文件,然后会有以下部分内容

<tr><th>Name</th><td>@Model?.Name</td></tr>
<tr><th>Price</th><td>@Model?.Price.ToString("c")</td></tr>

这是html和c#代码混合在一起,Model就是action方法的View方法插入的值

Selecting a View by Name

return View("Watersports", prod);

注意,action 方法没有指定视图的文件扩展名或位置。视图引擎的工作是将Watersports转换为视图文件

Using Shared Views

当 Razor 视图引擎找到一个视图时,它会先查看 View/[ controller ]文件夹,然后再查看 Views/Shared 文件夹。这种搜索模式意味着包含公共内容的视图可以在控制器之间共享,从而避免重复

其实对于View方法,也可以指定视图文件的位置,不过是相对于项目的路径,而且要带上文件的扩展名,这种方法一般不推荐使用,因为这个和文件耦合在一起了

 return View("/Views/Shared/Common.cshtml");

Working with Razor Views

Razor 视图包含 HTML 元素和 C # 表达式。表达式与 HTML 元素混合在一起,用@字符表示,如下所示:
<tr><th>Name</th><td>@Model?.Name</td></tr>

这种转变可能看起来像魔术,但 Razor 比最初看起来要简单。Razor 视图被转换成从 RazorPage 类继承的 C # 类,然后像其他 C # 类一样编译

internal sealed class Views_Home_Watersports : RazorPage<dynamic> {
        public async override Task ExecuteAsync() {
            WriteLiteral("<!DOCTYPE html>\r\n<html>\r\n");
            WriteLiteral("\r\n<link href=\"/lib/bootstrap/css/bootstrap.min.css\"
                rel=\"stylesheet\" />\r\n");
            HeadTagHelper = CreateTagHelper<TagHelpers.HeadTagHelper>();
            __tagHelperExecutionContext.Add(HeadTagHelper);
            Write(__tagHelperExecutionContext.Output);
            WriteLiteral("\r\n");
            __tagHelperExecutionContext = __tagHelperScopeManager.Begin("body",
                TagMode.StartTagAndEndTag, "76ad69...", async() => {
                WriteLiteral("<h6 class=\"bg-secondary text-white text-center m-2
                         p-2\">Watersports</h6>\r\n
                     <div class=\"m-2\"><table class=\"table table-sm table-striped
                         table-bordered\">\r\n
                      <tbody>\r\n <tr>");
                WriteLiteral("<th>Name</th><td>");
                Write(Model?.Name);
                WriteLiteral("</td></tr>\r\n<tr><th>Price</th><td>");
                Write(Model?.Price.ToString("c"));
                WriteLiteral("</td></tr>\r\n<tr><th>Category ID</th><td>");
                Write(Model?.CategoryId);
                WriteLiteral("</td></tr>\r\n</tbody>\r\n</table>\r\n</div>\r\n");
            });
            BodyTagHelper = CreateTagHelper<TagHelpers.BodyTagHelper>();
            __tagHelperExecutionContext.Add(BodyTagHelper);
            Write(__tagHelperExecutionContext.Output);
            WriteLiteral("\r\n</html>\r\n");
        }
        public IModelExpressionProvider ModelExpressionProvider { get; private set; }
        public IUrlHelper Url { get; private set; }
        public IViewComponentHelper Component { get; private set; }
        public IJsonHelper Json { get; private set; }
        public IHtmlHelper<dynamic> Html { get; private set; }
    }

Setting the View Model Type

为 Watersports.cshtml 文件生成的类派生自 RazorPage < T > ,但是 Razor 不知道视图模型的 action 方法将使用什么类型,所以它选择 Dynamic 作为泛型类型参数。这意味着@Model 表达式可以与任何属性或方法名一起使用,该属性或方法名在生成响应时在运行时进行计算

可以通过指定ViewModel的类型@model WebApp.Models.Product

Using a View Imports File

@model WebApp.Models.Product

默认情况下,在 Razor 视图中引用的所有类型都必须用命名空间限定。当只有模型对象的类型参考时,这没什么大不了的,但是当写更复杂的 Razor 表达式时,它会使视图更难阅读

通过向项目中添加视图导入文件,可以指定一组应搜索类型的命名空间。视图导入文件放置在 View 文件夹中,名为 _ViewImport.cshtml

在文件中添加@using WebApp.Models

应该搜索 Razor 视图中使用的类的名称空间使用@using 表达式指定,后跟名称空间

那么在视图文件中,就不需要编写全类名@model Product

Understanding the Razor Syntax

Razor 编译器将 HTML 的静态片段从 C # 表达式中分离出来,然后在生成的类文件中单独处理这些表达式。视图中可以包含几种类型的表达式

Understanding Directives

指令是指示 Razor 视图引擎的表达式。例如,@model 表达式是一个指令,它告诉视图引擎为视图模型使用特定的类型,而@using 指令告诉视图引擎导入名称空间。

Understanding Content Expressions

Razor 内容表达式生成的内容包含在视图生成的输出中

CHAPTER 22

Using Controllers with Views, Part II

Using the View Bag

Action 方法为视图提供用视图模型显示的数据,但有时需要额外的信息。Action 方法可以使用视图包提供包含额外数据的视图

public async Task<IActionResult> Index(long id = 1) {
            ViewBag.AveragePrice =
                await context.Products.AverageAsync(p => p.Price);
            return View(await context.Products.FindAsync(id));
        }

ViewBag 属性继承自 Controller 基类并返回一个动态对象。这允许操作方法仅通过向它们分配值来创建新属性

<body>
    <h6 class="bg-primary text-white text-center m-2 p-2">Product Table</h6>
    <div class="m-2">
        <table class="table table-sm table-striped table-bordered">
            <tbody>
                <tr><th>Name</th><td>@Model?.Name</td></tr>
                <tr><th>Price</th>
                    <td>
                        @Model?.Price.ToString("c")
                        (@(((Model?.Price / ViewBag.AveragePrice)
                            * 100).ToString("F2"))% of average price)
                    </td>
                </tr>
                <tr><th>Category ID</th><td>@Model?.CategoryId</td></tr>
            </tbody>
        </table>
    </div>
</body>

当 view bag 被用来为视图提供少量的补充数据而不必为每个操作方法创建新的视图模型类时,它的效果最好。这个 view bag 的问题在于,编译器无法检查动态对象上属性的使用,就像视图不使用@model 表达式一样。很难判断什么时候应该使用一个新的视图模型类,我的经验法则是,当同一个视图模型属性被多个操作使用时,或者当一个操作方法向 view bag 添加多个属性时,创建一个新的视图模型类。

Using Temp Data

临时数据特性允许控制器保存从一个请求到另一个请求的数据,这在执行重定向时非常有用。使用 Cookie 存储临时数据,除非在将临时数据存储为会话数据时启用了会话状态。与会话数据不同,临时数据值在读取时标记为删除,在处理请求时标记为删除。

public class CubedController: Controller {
        public IActionResult Index() {
            return View("Cubed");
        }

        public IActionResult Cube(double num) {
            TempData["value"] = num.ToString();
            TempData["result"] = Math.Pow(num, 3).ToString();
            return RedirectToAction(nameof(Index));
        }
    }

Cubed 控制器定义选择名为 Cubed 的视图的 Index 方法。还有一个 Cube 操作,它依赖于模型绑定过程来从请求中获取其 num 参数的值。Cubed action 方法执行其计算,并使用 TempData 属性存储 num 值和计算结果,该属性返回用于存储键值对的字典。由于临时数据特性构建在会话特性之上,因此只能存储可序列化为字符串的值,这就是为什么我将两个双精度值都转换为字符串的原因。将值存储为临时数据后,Cube 方法将执行到 Index 方法的重定向

<body>
    <h6 class="bg-secondary text-white text-center m-2 p-2">Cubed</h6>
    <form method="get" action="/cubed/cube" class="m-2">
        <div class="form-group">
            <label>Value</label>
            <input name="num" class="form-control" value="@(TempData["value"])" />
        </div>
        <button class="btn btn-primary mt-1" type="submit">Submit</button>
    </form>
    @if (TempData["result"] != null) {
        <div class="bg-info text-white m-2 p-2">
            The cube of @TempData["value"] is @TempData["result"]
        </div>
    }
</body>

用于 Razor 视图的基类通过一个 TempData 属性提供对临时数据的访问,允许在表达式中读取值。在这种情况下,临时数据用于设置输入元素的内容并显示结果摘要。读取临时数据值不会立即删除它,这意味着可以在同一视图中重复读取值。只有在处理完请求之后,才会删除标记的值。

由 TempData 属性返回的对象提供了一个 Peek 方法,它允许您获取一个数据值而不需要标记为删除,以及一个 Keep 方法,它可以用来防止以前读取的值被删除。Keep 方法不会永远保护值。如果再次读取该值,将再次标记为删除。如果希望存储项,以便在处理请求时不会删除它们,请使用会话数据。

Working with Layouts

应用程序中的视图包含处理设置 HTML 文档、定义 head 部分、加载 Bootstrap CSS 文件等问题的重复元素。Razor 支持布局,它将公共内容合并到一个文件中,任何视图都可以使用

<!DOCTYPE html>
<html>
<head>
    <link href="/lib/bootstrap/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
    <h6 class="bg-primary text-white text-center m-2 p-2">Shared View</h6>
    @RenderBody()
</body>
</html>

布局包含将由多个视图使用的公共内容。每个视图独有的内容通过调用 RenderBody 方法插入到响应中,该方法由 RazorPage < T > 类继承

@model Product
@{
    Layout = "_Layout";
}

通过添加一个由@{和}字符表示的代码块来选择布局,该代码块设置从 RazorPage < T > 类继承的 Layout 属性。在这种情况下,Layout 属性设置为布局文件的名称。与普通视图一样,指定布局时没有路径或文件扩展名,Razor 引擎将在/Views/[ controller ]和/View/Shared 文件夹中搜索匹配的文件

Configuring Layouts Using the View Bag

视图可以为布局提供数据值,从而允许自定义视图提供的公共内容。View bag 属性定义在选择布局的代码块中

@model Product
@{
    Layout = "_Layout";
    ViewBag.Title = "Product Table";
}
<!DOCTYPE html>
<html>
<head>
    <title>@ViewBag.Title</title>
    <link href="/lib/bootstrap/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
    <h6 class="bg-primary text-white text-center m-2 p-2">
        @(ViewBag.Title ?? "Layout")
    </h6>
    @RenderBody()
</body>
</html>

Using a View Start File

不必在每个视图中设置 Layout 属性,您可以向提供默认 Layout 值的项目添加_ViewStart.cshtml文件。

@{
    Layout = "_Layout";
}

Overriding the Default Layout

有两种情况可能需要在视图中定义 Layout 属性,即使项目中有视图启动文件。在第一种情况下,视图需要与视图起始文件指定的布局不同的布局

@{
    Layout = "_ImportantLayout";
    ViewBag.Title = "Product Table";
}

需要 Layout 属性的第二种情况是,视图包含一个完整的 HTML 文档,并且根本不需要布局

@model IEnumerable<Product>
@{
    Layout = null;
    decimal average = Model?.Average(p => p.Price) ?? 0;
}

Using Layout Sections

Razor 视图引擎支持sections的概念,允许你在布局中提供内容区域。Razor 部分可以更好地控制视图的哪些部分插入到布局中以及放置在哪里。

@model Product
@{
    Layout = "_Layout";
    ViewBag.Title = ViewBag.Title ?? "Product Table";
}

@section Header {
    Product Information
}

<tr><th>Name</th><td>@Model?.Name</td></tr>
<tr>
    <th>Price</th>
    <td>@Model?.Price.ToString("c")</td>
</tr>
<tr><th>Category ID</th><td>@Model?.CategoryId</td></tr>

@section Footer {
    @(((Model?.Price / ViewBag.AveragePrice)
        * 100).ToString("F2"))% of average price
}
<!DOCTYPE html>
<html>
<head>
    <title>@ViewBag.Title</title>
    <link href="/lib/bootstrap/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
    <div class="bg-info text-white m-2 p-1">
        This is part of the layout
    </div>
    <h6 class="bg-primary text-white text-center m-2 p-2">
        @RenderSection("Header")
    </h6>
    <div class="bg-info text-white m-2 p-1">
        This is part of the layout
    </div>
    <div class="m-2">
        <table class="table table-sm table-striped table-bordered">
            <tbody>
                @RenderBody()
            </tbody>
        </table>
    </div>
    <div class="bg-info text-white m-2 p-1">
        This is part of the layout
    </div>
    <h6 class="bg-primary text-white text-center m-2 p-2">
        @RenderSection("Footer")
    </h6>
    <div class="bg-info text-white m-2 p-1">
        This is part of the layout
    </div>
</body>
</html>

在视图中定义section,然后在layout中RenderSection这个section

Using Optional Layout Sections

如果在layout中RenderSection一个section,但是在view中没有定义section,那么会报错

@RenderSection("Header", false),第二个参数指定是否需要某个section,并且当视图未定义该section时,使用 false 可防止异常

Testing for Layout Sections

IsSectionDefied 方法用于确定视图是否定义了指定的section,并且可以在 if 表达式中使用该方法来呈现回退内容

Using Partial Views

您通常需要在几个不同的地方使用同一组 HTML 元素和表达式。Partial Views是包含内容片段的view,这些内容片段将纳入其他view,以便在不重复的情况下作出复杂的responses

Enabling Partial Views

Partial views使用称为tag helpers的特性进行应用

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

Creating a Partial View

Partial views只是常规的 CSHTML 文件,它们的使用方式只是将它们与标准视图区分开来

@model Product
<tr>
    <td>@Model?.Name</td>
    <td>@Model?.Price</td>
</tr>

Applying a Partial View

Partial views通过在另一个视图或布局中添加partial元素来应用

@foreach (Product p in Model ?? Enumerable.Empty<Product>()) {
                    <partial name="_RowPartial" model="p" />
                }

partial元素的属性

Selecting the Partial View Model Using an Expression.

标签:Core,ASP,Layout,Razor,Views,视图,Model,随记,View
来源: https://www.cnblogs.com/huangwenhao1024/p/16439343.html