ASP.NET Core Views系列二

8 Partial 视图

部分视图是普通的视图文件(.cshtml),可以嵌入到另外的视图文件里,这意味这相同的视图文件能被使用在多个地方并且减少代码重复,如果在我们应用程序中有重复的视图,我们可以将这个视图作为部分视图,在别的视图中加载这个文件,这种方式可以阻止代码重复

在Views->Shared目录下添加TestPratialView.cshtml视图.

@model List<string><div class="border border-warning">    This is partial View:    <ul>        @foreach (string str in Model)        {            <li>@str</li>        }    </ul></div>
这个视图文件接收一个List 类型,并且循环List中的每个项在页面上展示,我们可以通过下面方法在别的视图上引用这个部分视图@await Html.PartialAsync(“name_of_partialview”, model)

在TestLayout.cshtml视图中添加如下代码:

@await Html.PartialAsync("TestPartialView", new List<string> {    "Classic ASP", "ASP.NET Web Forms", "ASP.NET MVC", "ASP.NET Core MVC"})
如意我们传递部分视图的名字到@await Html.PartialAsync()方法,dotnet会在Shared目录下查找该文件,如果我们部分视图在别的目录下,我们必须提供视图所在文件的目录
运行应用程序进入 ,你将会发现部分视图的内容已经添加到视图上,展示如下:

ASP.NET Core Views系列二

9 视图组件

视图组件有些像部分视图但是又有一些不同,视图组件相比部分视图更强大,我们可以在里面创建服务器的逻辑,这是和部分视图完全不同的

视图组件是C#类,可以从视图中调用这个类并且我们提供数据模型到视图组件
有下面复杂的功能,你使用视图组件来完成而不是使用部分视图

1 在站点中创建身份验证面板,提供用户在不访问单独登录页面的情况下登录

2 根据用户的角色动态创建一个导航菜单

3 购物车面板,显示当前购物车中的产品

4 依赖性注射特征

视图组件是C#类继承于ViewComponent基类,视图组件必须定义一个Invoke()方法或者InvokeAsync()异步方法,在此方法中,视图组件必须执行为其创建的任务

视图组件可以在应用程序的任何地方创建,但是根据约定,我们一般创建在应用程序根目录下的Components文件夹

9.1 例子

让我们通过一个简单的例子来创建一个视图组件并且我们将会解释如何工作的,在解决方案目录下创建Components文件夹,在文件夹内创建一个Cart.cs类并且继承自ViewComponent基类在该类内部添加Invoke方法,代码如下:
using Microsoft.AspNetCore.Mvc;namespace AspNetCore.Views.Components{    public class Cart : ViewComponent    {        public string Invoke()        {            return "This is from View Component";        }    }}

这个视图组件仅仅返回一个字符串消息,现在,从视图使用@await Component.InvokeAsync("NameofViewComponent")调用这个视图组件,这将调用视图组件中的Invoke方法

在_Layout.cshmtl中添加@await Component.InvokeAsync("Cart") 在页面顶部,代码如下:

<!DOCTYPE html><html><head>    <meta name="viewport" content="width=device-width" />    <title>@ViewData["Title"]</title>    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" /></head><body>    <header>    </header>    <div class="p-3 mb-2 border border-info">        @await Component.InvokeAsync("Cart")    </div></body></html>
现在运行应用程序,你将会看到字符串显示在页面上,检查下面图片

ASP.NET Core Views系列二

我们介绍一下视图组件的返回类型

9.2 视图组件的返回类型

在前面我们学习了关于视图组件返回字符串,视图组件也能返回IViewComponentResult接口通过调用Invoke方法,使用IViewComponentResult接口视图组件能够返回string,html,和partial view

类名 描述
ContentViewComponentResult 返回编码的HTML.例如Content("<h2>some text</h2>")
HtmlContentViewComponentResult 返回没有编码的HTML.例如HtmlContentViewComponentResult("<h2>some text</h2>")
ViewViewComponentResult 返回部分视图. Eg View("NameofView", model)
9.3 ContentViewComponentResult
ContentViewComponentResult类使用呈现编码的HTML针对视图组件,使用Content()方法创建一个ContentViewComponentResult类

让我们演示这个,修改Cart视图组件使用Content()方法返回IViewComponentResult类型

public IViewComponentResult Invoke(){   return Content("This is from <h2>View Component</h2>");}

运行应用程序进行测试

ASP.NET Core Views系列二

如果检查页面源代码你会发现HTML被编码

This is from &lt;h2&gt;View Component&lt;/h2&gt;
这个HTML编码通过ASP.NET Core运行时完成,这样可以阻止黑客添加脚本到站点
9.4 HtmlContentViewComponentResult
HtmlContentViewComponentResult类返回未编码的HTML针对视图组件,修改Cart视图组件返回一个HtmlContent-ViewComponentResult类,代码如下:
public IViewComponentResult Invoke(){   return new HtmlContentViewComponentResult(new HtmlString("This is from <h2>View Component</h2>"));}

运行应用,你将会看到如下信息展示在浏览器中

ASP.NET Core Views系列二

使用这个方法你需要确保你100%返回的信息是安全的而且不会被篡改
9.5 返回部分视图

你可以使用视图组件返回部分视图,ViewComponent基类提供了View()方法返回部分视图

有4个版本的View方法:

View();//选择默认部分视图View(model);//选择默认部分视图并提供数据模型给它View("viewname");//通过名字选择部分视图View("viewname",model);//通过名字选择视图并且提供数据模型给它
ASP.NET Core将从下面位置查找部分视图:
/Views/{controller}/Components/{view component}/{partial view name}/Views/Shared/Components/{view component}/{partial view name}
1 控制器是用来处理HTTP请求
2 如果在View()没有指定视图名称,{partial view name}使用Default.cshtml
9.6 复杂视图的例子

让我们创建一个复杂视图组件返回部分视图,创建一个模型类Product.cs在Models文件夹:

namespace AspNetCore.Views.Models{    public class Product    {        public string Name { get; set; }        public int Price { get; set; }    }}
现在更新你的Cart视图组件返回一个View使用product模型
 public IViewComponentResult Invoke() {     Product[] products = new Product[] {                new Product() { Name = "Women Shoes", Price = 99 },                new Product() { Name = "Mens Shirts", Price = 59 },                new Product() { Name = "Children Belts", Price = 19 },                new Product() { Name = "Girls Socks", Price = 9 }            };
    return View(products); }
运行应用程序,你会发现下面错误

ASP.NET Core Views系列二

注意控制器处理HTTP请求是HomeController并且我们没有在View()中指定视图名称,因此ASP.Net Core会从下面位置搜索视图

/Views/Home/Components/Cart/Default.cshtml/Views/Shared/Components/Cart/Default.cshtml
为了解决这个问题创建部分视图,创建一个Default.cshtml的Razor视图在/Views/Shared/Components/Cart/ 文件夹下并且添加下面代码
@model Product[]<table class="table" style="width:50%">    <thead class="thead-dark">        <tr>            <td><u>Product Name</u></td>            <td><u>Price</u></td>        </tr>    </thead>    <tbody>        @{            foreach (Product p in Model)            {                <tr class="table-secondary">                    <td>@p.Name</td>                    <td>@p.Price</td>                </tr>            }        }    </tbody></table>
运行应用程序进入https://localhost:7019/Home/TestLayout,这次你会发现部分视图包含在layout页面并且它在购物车中展示所有产品,检查下面图片

ASP.NET Core Views系列二

9.7 在视图组件中使用DI

我们可以在视图组件中使用依赖注入,只需要在视图组件构造函数中添加依赖的类,让我们创建一个服务,其任务是在购物车视图组件上提供折扣优惠券代码。通过此优惠券,用户可以获得产品总成本的折扣。在应用程序根目录上创建一个名为“Services”的新文件夹,并在其中添加一个Coupon.cs类
namespace AspNetCore.Views.Models{    public class Coupon    {        public string GetCoupon()        {            //get coupons from external database or an external web api call            return "Discount10";        }    }}
GetCoupon()方法提供折扣码来自数据库或者外部api,这里仅仅作为演示返回一个字符串
接下来在Programe.cs中注册Coupon 服务
builder.Services.AddTransient<Coupon>();
在Cart视图组件中注册这个服务. 代码如下:
using AspNetCore.Views.Models;using AspNetCore.Views.Service;using Microsoft.AspNetCore.Mvc;namespace AspNetCore.Views.Components{    public class Cart : ViewComponent    {        private Coupon _coupon;        public Cart(Coupon coupon)        {            _coupon = coupon;        }        #region 返回复杂视图        public IViewComponentResult Invoke()        {            Product[] products = new Product[] {                new Product() { Name = "Women Shoes", Price = 99 },                new Product() { Name = "Mens Shirts", Price = 59 },                new Product() { Name = "Children Belts", Price = 19 },                new Product() { Name = "Girls Socks", Price = 9 }            };
            ViewBag.Coupon = _coupon.GetCoupon();            return View(products);        }        #endregion    }}

现在我们在Default.cshtml视图中展示折扣信息,读取ViewBag变量并展示

@model Product[]<table class="table" style="width:50%">    <thead class="thead-dark">        <tr>            <td><u>Product Name</u></td>            <td><u>Price</u></td>        </tr>    </thead>    <tbody>        @{            foreach (Product p in Model)            {                <tr class="table-secondary">                    <td>@p.Name</td>                    <td>@p.Price</td>                </tr>            }        }    </tbody></table><p class="p-3 text-white bg-dark">Apply coupon - @ViewBag.Coupon</p>

运行应用程序并展示

ASP.NET Core Views系列二

9.8 父组件中的值传递给子组件

我们可以从父组件向子组件传递至,使用@await Component.InvokeAsync()的第二个参数提供一个匿名对象,在_Layout.cshtml文件修改InvokeAsync()传递一个false属性:

@await Component.InvokeAsync("Cart",new {showCart=false})
修改Cart组件代码并且在Invoke()方法中添加showCart参数,显示代码如下:
using AspNetCore.Views.Models;using AspNetCore.Views.Service;using Microsoft.AspNetCore.Mvc;namespace AspNetCore.Views.Components{    public class Cart : ViewComponent    {        private Coupon _coupon;        public Cart(Coupon coupon)        {            _coupon = coupon;        }        #region 返回复杂视图        public IViewComponentResult Invoke(bool showCart)        {            Product[] products;            if (showCart)            {                products = new Product[] {                new Product() { Name = "Women Shoes", Price = 99 },                new Product() { Name = "Mens Shirts", Price = 59 },                new Product() { Name = "Children Belts", Price = 19 },                new Product() { Name = "Girls Socks", Price = 9 }                };                ViewBag.Coupon = _coupon.GetCoupon();            }            else            {                products = new Product[] { };            }            return View(products);        }        #endregion    }}
当showCart变量为true时我们显示cart产品,运行应用程序,这次我们没有显示任何产品

10 匿名视图组件

异步视图组件使用指定异步任务,InvokeAsync方法返回一个task对象,ASP.NET Core将等待任务完成并且在view中呈现结果

右击"Components"文件夹并且添加新的类命名为PageSize.cs,添加下面代码:
using Microsoft.AspNetCore.Mvc;namespace AspNetCore.Views.Components{    public class PageSize: ViewComponent    {        public async Task<IViewComponentResult> InvokeAsync()        {            HttpClient client = new HttpClient();            HttpResponseMessage response = await client.GetAsync("http://www.msn.com");            return View(response.Content.Headers.ContentLength);        }    }}

异步视图组件将获取MSN页数使用HTTP GET 请求并且将传递页数到default视图

在View/Home/Components/PageSize目录下创建一个Default.cshtml目录,在文件夹中添加下面代码:

@model long<div class="p-3 mb-2 text-danger">Page size: @Model</div>
现在在_Layout.cshtml文件中调用异步组件,如下所示:
@await Component.InvokeAsync("PageSize")
运行程序,进入https://localhost:7113/Home/TestLayout,你将会看到MSN页面页数显示如下

ASP.NET Core Views系列二

11 @inject 指令
通过使用@inject指令可以把服务注入到View,假如我们有个服务提供了随机笑话,这个笑话来自随机的api. 因此在你应用程序中Service文件夹创建一个Joke.cs类使用下面代码:
using System.Text.Json;namespace AspNetCore.Views.Service{    public class Joke    {        public async Task<string> GetJoke()        {            string apiResponse = "";            using (var httpClient = new HttpClient())            {                using (var response = await httpClient.GetAsync("https://api.vvhan.com/api/joke"))                {                    apiResponse = await response.Content.ReadAsStringAsync();                }            }            return apiResponse;        }    }}

该类有一个名为 GetJoke 的方法,该方法调用 Web API 并取名为joke的笑话,最后返回,接下来,在 Program.cs 类中添加Joke为transient 服务

builder.Services.AddTransient<Joke>();
现在在HomeController中添加一个新的action方法
public IActionResult Joke(){    return View();}

在Views->Home目录下添加Joke.cshtml代码如下

@using AspNetCore.Views.Service@inject Joke joke;
<h2 class="text-primary">Today's Joke</h2><p>@await joke.GetJoke()</p>
在上面代码,我使用了@inject 指令引用依赖注入的服务-@inject Joke joke,这样我们就可以在页面上使用DI,最后我调用GetJoke方法-@await joke.GetJoke()展示笑话

运行程序,进入URL- https://localhost:7019/Home/Joke,会发现我们每次随机展示一个笑话

ASP.NET Core Views系列二

总结

通过这两篇文章介绍ASP.NET Core MVC中视图的应用包含创建视图,视图中使用Razor语法,在视图中调用action方法,共享视图文件,ASP.NET Core如何查找视图文件,Layout视图是如何工作的,部分视图和视图组件