ASP.NET Core应用程序6:Razor Pages

  Razor Pages是生成Html响应的一种简化方式。它的简单性,意味着能够比MVC框架更快获得结果。它将单个视图与一个类关联起来,由该类为视图提供功能,并使用基于文件的路由系统来匹配URL。它的灵活性不如MVC,所以不适合复杂的应用程序。

1 准备工作

2 理解Razor Pages

  Razor Pages和MVC并不是二选一的情况,是可以共存的,Razor Pages以牺牲灵活性来换取专注度,将标记与C#代码捆绑在一起。

2.1 配置Razor Pages

在Startup中添加配置。

services.AddRazorPages().AddRazorRuntimeCompilation();
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
    endpoints.MapDefaultControllerRoute();
    endpoints.MapRazorPages();
});

  AddRazorPages用于设置Razor Pages必须用到的服务,而可选的AddRazorRuntimeCompilation则启用运行时重新编译。MapRazorPages创建路由配置,用于将URL匹配到页面。

2.2 创建Razor Pages

  Razor Pages是在Pages文件夹中定义的,添加Pages文件夹并创建Index.cshtml的Razor Pages文件。

@page
@model IndexModel
@using Microsoft.AspNetCore.Mvc.RazorPages
@using MyWebApp.Models

<!DOCTYPE html>
<html>
<head>
    <link href="/lib/twitter-bootstrap/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
    <div class="bg-primary text-white text-center m-2 p-2">@Model.Product.Name</div>
</body>
</html>

@functions
{
    public class IndexModel : PageModel
    {
        private DataContext context;

        public Product Product { get; set; }

        public IndexModel(DataContext ctx)
        {
            context = ctx;
        }

        public async Task OnGetAsync(long id = 1)
        {
            Product = await context.Products.FindAsync(id);
        }
    }
}

  在Razor Pages中,@page指令必须第一个出现,这确保不会将该文件误认为与控制器关联在一起的视图。最重要的区别是使用@functions指令在同一个文件中定义支持Razor内容的C#代码。启动程序浏览器请求http://localhost:5000/index。

3 理解Razor Pages的路由

  Razor Pages依赖于cshtml文件位置来进行路由,要添加复杂URL结构,可以添加文件夹。Pages文件夹下添加Suppliers文件夹,再添加List.cshtml的Razor Pages。访问http://localhost:5000/suppliers/list。

@page
@model ListModel
@using Microsoft.AspNetCore.Mvc.RazorPages
@using MyWebApp.Models;

<!DOCTYPE html>
<html>
<head>
    <link href="/lib/twitter-bootstrap/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
    <h5 class="bg-primary text-white text-center m-2 p-2">Suppliers</h5>
    <ul class="list-group m-2">
        @foreach (string s in Model.Suppliers)
        {
            <li class="list-group-item">@s</li>
        }
    </ul>
</body>
</html>

@functions {

    public class ListModel : PageModel
    {
        private DataContext context;

        public IEnumerable<string> Suppliers { get; set; }

        public ListModel(DataContext ctx)
        {
            context = ctx;
        }

        public void OnGet()
        {
            Suppliers = context.Suppliers.Select(s => s.Name);
        }
    }
}

  注意:当混和了Razor Pages和MVC时框架时,可以在配置中修改端点路由语句顺序,endpoints.MapRazorPages()放到endpoints.MapControllers()前面,先配置谁就由谁来设置默认路由。

3.1 在Razor Pages中指定路由模式

  使用文件结构来执行路由,可以从URL查询中获取请求处理程序方法的值。请求http://localhost:5000/index?id=2,可以对应上OnGetAsync(long id = 1)方法中定义的参数。
  可以在@page指令中使用路由模式。@page "{id:long?}",请求http://localhost:5000/index/4。
  @page指令可用于覆盖Razor Pages的基于文件的路由约定。@page "/lists/suppliers,请求http://localhost:5000/lists/suppliers。

3.2 为Razor Pages添加路由

  如果想为页面定义多个路由,可在Startup类配置。

services.Configure<RazorPagesOptions>(options =>
{
    options.Conventions.AddPageRoute("/Index", "/extra/page/{id:long?}");
});

  这里RazorPagesOptions类为Razor Pages添加额外路由。AddPageRoute方法的第一个实参相对于Pages文件夹页面路径,第二个实参是要添加路由配置的URL模式。请求http://localhost:5000/extra/page/2。

4 理解页面模型类

  页面模型派生自PageModel类,它提供一些方法处理请求,并使用一些属性提供上下文数据。但在Razor Pages开发中常常不需要使用他们,因为开发更关注选择必要数据来渲染页面的视图部分。

4.1 使用代码隐藏类文件

  把@functions指令中的类移到Index.cshtml.cs中,并修修改Index.cshtml。

@page "{id:long?}"
@model MyWebApp.Pages.IndexModel
<!DOCTYPE html>
<html>
<head>
    <link href="/lib/twitter-bootstrap/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
    <div class="bg-primary text-white text-center m-2 p-2">@Model.Product.Name</div>
</body>
</html>

添加视图导入文件
  视图导入文件可用于避免在视图文件中使用模型的全类名,添加_ViewImports.cshtml文件。这样Index.cshtml中就这样写@model IndexModel

@namespace MyWebApp.Pages
@using MyWebApp.Models

4.2 理解Razor Pages的操作结果

  Razor Pages处理程序方法使用IActionResult接口来控制生成的响应。

public async Task<IActionResult> OnGetAsync(long id = 1)
{
    Product = await context.Products.FindAsync(id);
    return Page();
}

使用操作结果
  在没请求到数据时,相比使用NotFound方法,更好的方法是将客户端重定向到另一个URL。添加NotFound.cshtml的Razor Pages。

@page "/noid"
@model NotFoundModel
@using Microsoft.AspNetCore.Mvc.RazorPages
@using WebApp.Models;

<!DOCTYPE html>
<html>
<head>
    <link href="/lib/twitter-bootstrap/css/bootstrap.min.css" rel="stylesheet" />
    <title>Not Found</title>
</head>
<body>
    <div class="bg-primary text-white text-center m-2 p-2">No Matching ID</div>
    <ul class="list-group m-2">
        @foreach (Product p in Model.Products) {
            <li class="list-group-item">@p.Name (ID: @p.ProductId)</li>                
        }
    </ul>    
</body>
</html>

@functions {

    public class NotFoundModel: PageModel {
        private DataContext context;

        public IEnumerable<Product> Products { get; set; }

        public NotFoundModel(DataContext ctx) {
            context = ctx;
        }

        public void OnGetAsync(long id = 1) {
            Products = context.Products;
        }
    }
}

  更新了IndexModel类的处理如下,当请求http://localhost:5000/index/500时,Product没有查询到数据,则将用户重定向到NotFound页面。

public async Task<IActionResult> OnGetAsync(long id = 1)
{
    Product = await context.Products.FindAsync(id);
    if(Product == null)
    {
        return RedirectToPage("NotFound");
    }
    return Page();
}

4.3 处理多个HTTP方法

  Razor Pages可以定义多个处理程序方法来响应HTTP方法。常见的GET、POST方法。下面添加Editor.cshtml的Razor Pages。

@page "{id:long}"
@model EditorModel

<!DOCTYPE html>
<html>
<head>
    <link href="/lib/twitter-bootstrap/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
    <div class="bg-primary text-white text-center m-2 p-2">Editor</div>
    <div class="m-2">
        <table class="table table-sm table-striped table-bordered">
            <tbody>
                <tr><th>Name</th><td>@Model.Product.Name</td></tr>
                <tr><th>Price</th><td>@Model.Product.Price</td></tr>
            </tbody>
        </table>
        <form method="post">
            @Html.AntiForgeryToken()
            <div class="form-group">
                <label>Price</label>
                <input name="price" class="form-control" 
                       value="@Model.Product.Price" />
            </div>
            <button class="btn btn-primary" type="submit">Submit</button>
        </form>
    </div>
</body>
</html>

  此视图定义的form元素不包含action特性,这意味着当单击Submit按钮时发送一个Post请求。
  在Editor.cshtml.cs中添加如下。OnGetAsync方法用于处理Get请求,OnPostAsync处理Post请求。

    public class EditorModel : PageModel
    {
        private DataContext context;

        public Product Product { get; set; }

        public EditorModel(DataContext ctx)
        {
            context = ctx;
        }

        public async Task OnGetAsync(long id)
        {
            Product = await context.Products.FindAsync(id);
        }

        public async Task<IActionResult> OnPostAsync(long id, decimal price)
        {
            Product p = await context.Products.FindAsync(id);
            p.Price = price;
            await context.SaveChangesAsync();
            return RedirectToPage();
        }
    }

  请求http://localhost:5000/editor/1,编辑字段修改价格并提交,数据将被更新。

4.4 选择处理程序方法

  页面模型类可定义多个处理程序方法,允许请求使用handler查询字符串参数或者路由片段来选择方法。添加HandlerSelector.cshtml的Razor Pages如下。

@page
@model HandlerSelectorModel
@using Microsoft.AspNetCore.Mvc.RazorPages
@using Microsoft.EntityFrameworkCore

<!DOCTYPE html>
<html>
<head>
    <link href="/lib/twitter-bootstrap/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
    <div class="bg-primary text-white text-center m-2 p-2">Selector</div>
    <div class="m-2">
        <table class="table table-sm table-striped table-bordered">
            <tbody>
                <tr><th>Name</th><td>@Model.Product.Name</td></tr>
                <tr><th>Price</th><td>@Model.Product.Name</td></tr>
                <tr><th>Category</th><td>@Model.Product.Category?.Name</td></tr>
                <tr><th>Supplier</th><td>@Model.Product.Supplier?.Name</td></tr>
            </tbody>
        </table>
        <a href="/handlerselector" class="btn btn-primary">Standard</a>
        <a href="/handlerselector?handler=related" class="btn btn-primary">
            Related
        </a>
    </div>
</body>
</html>

@functions{

    public class HandlerSelectorModel: PageModel {
        private DataContext context;

        public Product Product { get; set; }

        public HandlerSelectorModel(DataContext ctx) {
            context = ctx;
        }

        public async Task OnGetAsync(long id = 1) {
            Product = await context.Products.FindAsync(id);
        }

        public async Task OnGetRelatedAsync(long id = 1) {
            Product = await context.Products
                .Include(p => p.Supplier)
                .Include(p => p.Category)
                .FirstOrDefaultAsync(p => p.ProductId == id);
            Product.Supplier.Products = null;
            Product.Category.Products = null;
        }
    }
}

  本页面模型定义了OnGetAsync和OnGetRelatedAsync两个方法,请求http://localhost:5000/handlerselector默认使用OnGetAsync,可以用参数来决定目标URL,/handlerselector?handler=related,参数可以不用On前缀和Async后缀。

5 理解Razor Pages视图

  Razor Pages视图语法等与控制器的基本相同。

5.1 为Razor Pages创建布局

  创建Shared文件夹,添加_Layout.cshtml文件。

<!DOCTYPE html>
<html>
<head>
    <link href="/lib/twitter-bootstrap/css/bootstrap.min.css" rel="stylesheet" />
    <title>@ViewBag.Title</title>
</head>
<body>
    <h5 class="bg-secondary text-white text-center m-2 p-2">
        Razor Page
    </h5>
    @RenderBody()
</body>
</html>

  在Pages文件夹中添加_ViewStart.cshtml的文件。

@{
    Layout = "_Layout";
}

  修改Index.cshtml。

@page "{id:long?}"
@model IndexModel

<div class="bg-primary text-white text-center m-2 p-2">@Model.Product.Name</div>

  _ViewStart文件将布局应用到所有覆盖Layout属性值的页面。
  Editor.cshtml中禁用布局。

@{
    Layout = null;
}

  请求http://localhost:5000/index看到新布局,请求http://localhost:5000/editor/1将看到没有布局。

5.2 在Razor Pages中使用分部视图

  使用分部视图拉避免重复公共内容。
  在Pages/_ViewImports.cshtml中添加如下。

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

  在Pages/Shared添加_ProductPartial.cshtml视图。

@model Product

<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</td></tr>
        </tbody>
    </table>
</div>

  在Index.cshtml中使用分部视图。

@page "{id:long?}"
@model IndexModel

<div class="bg-primary text-white text-center m-2 p-2">@Model.Product.Name</div>
<partial name="_ProductPartial" model="Model.Product" />

5.3 创建没有页面模型的Pazor Pages

  如果Pazor Pages只是简单地向用户展示数据,那么结果可以是一个页面模型类。
  在Pages文件夹中添加Data.cshtml的Pazor Pages。当页面模型只用于访问服务,而不添加其他操作时,可使用@inject指令在视图中获取服务,并不需要使用页面模型。

@page
@inject DataContext context;

<h5 class="bg-primary text-white text-center m-2 p-2">Categories</h5>
<ul class="list-group m-2">
    @foreach (Category c in context.Categories)
    {
        <li class="list-group-item">@c.Name</li>
    }
</ul>
posted @ 2024-05-11 09:49  一纸年华  阅读(219)  评论(0编辑  收藏  举报