ASP.NET Core应用程序7:使用视图组件

  视图组件是类,为支持分部视图或者在父视图中注入少量Html或Json数据提供了应用程序逻辑。

1 准备工作

  Models文件夹中添加City.cs类和CitiesData类,为CitiesData添加服务。

    public class City
    {
        public string Name { get; set; }
        public string Country { get; set; }
        public int Population { get; set; }
    }
    public class CitiesData
    {
        private List<City> cities = new List<City>
        {
            new City { Name = "London", Country = "UK", Population = 8539000},
            new City { Name = "New York", Country = "USA", Population = 8406000 },
            new City { Name = "San Jose", Country = "USA", Population = 998537 },
            new City { Name = "Paris", Country = "France", Population = 2244000 }
        };

        public IEnumerable<City> Cities => cities;

        public void AddCity(City newCity)
        {
            cities.Add(newCity);
        }
    }
services.AddSingleton<CitiesData>();

  在Pages文件夹下添加Cities.cshtml的Razor Pages。

@page
@inject CitiesData Data

<div class="m-2">
    <table class="table table-sm table-striped table-bordered">
        <tbody>
            @foreach (var item in Data.Cities)
            {
                <tr>
                    <td>@item.Name</td>
                    <td>@item.Country</td>
                    <td>@item.Population</td>
                </tr>
            }
        </tbody>
    </table>
</div>

  浏览器请求http://localhost:5000/cities。

2 理解视图组件

  可将视图组件想象成一个专门的操作页面,但它只用于为分部视图提供数据,不能接收http请求,而且提供的内容将总是被包含到父视图中。

3 创建和使用视图组件

  视图组件是名称以ViewComponent结束,并定义了Invoke或InvokeAsync方法的类,派生自ViewComponent基类,或者使用ViewComponent特性装饰的类。
  在项目的任何位置都可以定义视图组件,但约定做法是把它们放到名为Components的文件夹中。

  创建Components文件夹,添加CitySummary.cs。

    public class CitySummary : ViewComponent
    {
        private CitiesData data;

        public CitySummary(CitiesData cdata)
        {
            data = cdata;
        }
        public string Invoke()
        {
            return $"{data.Cities.Count()}座城市," +
                $"{data.Cities.Sum(a => a.Population)}人";
        }
    }

(1)应用视图组件
  使用添加到视图和Pazor Pages生成的C#类的Component属性,该属性返回的对象实现了IViewComponentHelper接口,接口提供了InvokeAsync方法。

  如下Views/Home/Index.cshtml文件中应用视图组件,在使用时将视图组件类名作为实参。把名称空间添加到Views/_ViewImports.cshtml中。

@section Summary {
    <div class="bg-info text-white m-2 p-2">
        @await Component.InvokeAsync("CitySummary")
    </div>
}
@using MyWebApp.Components

  浏览器请求http://localhost:5000/home/index/1。
(2)使用标签助手应用视图组件
  在Views/_ViewImports.cshtml中添加配置标签助手。修改Views/Home/Index.cshtml文件中应用视图组件。

@addTagHelper *,MyWebApp
@section Summary {
    <div class="bg-info text-white m-2 p-2"
       <vc:city-summary />
    </div>
}

4 理解视图组件结果

  视图组件通过让Invoke方法返回一个实现了IViewComponentResult接口的对象,可以返回更复杂的效果。3个内置类实现了此接口,这些类以及ViewComponent基类提供了创建的方法。

  如下三个内置类:

  • ViewViewComponentResult:用于手动指定一个Razor视图,包括可选视图模型数据。使用View方法可创建其实例。
  • ContentViewComponentResult:用于指定文本结果,该文本结果将被安全编码。使用Content方法可创建其实例。
  • HtmlContentViewComponentResult:用于指定一个Html片段,该片段不会被编码,直接包含到html中。没有ViewComponent方法可创建其实例。

4.1 返回一个分部视图

  最有用的响应是ViewViewComponentResult,它告诉Razor渲染一个分部视图,并在父视图中包含渲染结果。ViewComponent基类提供了View方法用于创建ViewViewComponentResult对像。
  在Models文件夹下添加CityViewModel.cs视图模型类。修改CitySummary视图组件的Invoke方法。

    public class CityViewModel
    {
        public int Cities { get; set; }
        public int Population { get; set; }
    }
        public IViewComponentResult Invoke()
        {
            var model = new CityViewModel()
            {
                Cities = data.Cities.Count(),
                Population = data.Cities.Sum(x => x.Population),
            };
            return View(model);
        }

  使用浏览器请求http://localhost:5000/home/index/1,因为还没有可供视图组件使用的视图所以会报错,但可以看出使用该视图组件时都搜索了哪些位置。

InvalidOperationException: The view 'Components/CitySummary/Default' 
    was not found. The following locations were searched:
/Views/Home/Components/CitySummary/Default.cshtml
/Views/Shared/Components/CitySummary/Default.cshtml
/Pages/Shared/Components/CitySummary/Default.cshtml

  创建Views/Shared/Components/CitySummary文件夹,添加Default.cshtml的Razor视图。

@model CityViewModel

<table class="table table-sm table-bordered text-white bg-@ViewBag.Theme">
    <thead>
        <tr><th colspan="2">Cities Summary</th></tr>
    </thead>
    <tbody>
        <tr>
            <td>Cities:</td>
            <td class="text-right">
                @Model.Cities
            </td>
        </tr>
        <tr>
            <td>Population:</td>
            <td class="text-right">
                @Model.Population.ToString("#,###")
            </td>
        </tr>
    </tbody>
</table>

  使用浏览器请求http://localhost:5000/home/index/1和http://localhost:5000/data。

4.2 返回Html片段

  ContentViewComponentResult类用于不使用视图情况下,在父视图中包含Html片段。使用从ViewComponent基类继承的Content方法,可以创建其实例。
修改如下。

        public IViewComponentResult Invoke()
        {
            return Content("这是一个<h3><i>string</i></h3>");
        }

  使用浏览器请求http://localhost:5000/data,会看到返回的结果并没有解释为html元素,Content方法是编码的。要想把<h3><i>标签转换成html显示,则必须创建HtmlContentViewComponentResult对象,为其构造函数提供一个HtmlString对象。

using Microsoft.AspNetCore.Mvc.ViewComponents;
using Microsoft.AspNetCore.Html;
        public IViewComponentResult Invoke()
        {
            var htmlString = new HtmlString("这是一个<h3><i>string</i></h3>");
            return new HtmlContentViewComponentResult(htmlString);
        }

5 获取上下文数据

  通过ViewComponent基类定义的一些属性,将关于当前请求和父视图的详细信息提供给视图组件。
  在Component/CitySummary.cs中使用请求数据。

        public string Invoke()
        {
            if (RouteData.Values["controller"] != null)
            {
                return "controller请求";
            }
            else
            {
                return "Razor Pages请求";
            }
        }

5.1 使用实参提供父视图的上下文

  父视图能够为视图组件提供额外的上下文数据,包括应该生成的内容的数据。

        public IViewComponentResult Invoke(string themeName)
        {
            ViewBag.Theme = themeName;

            return View(new CityViewModel
            {
                Cities = data.Cities.Count(),
                Population = data.Cities.Sum(c => c.Population)
            });
        }

  在Views/Shared/Components/CitySummar/Default.cshtml文件中设置内容样式。

<table class="table table-sm table-bordered text-white bg-@ViewBag.Theme">
......

  这时必须为视图组件的Invoke方法提供参数值。修改Views/Home/Index.cshtml。

<vc:city-summary theme-name="secondary" />

  修改Pages/Data.cshtml。

<vc:city-summary theme-name="danger" />  

  也可以使用组件助手提供值。

@await Component.InvokeAsync("CitySummary",new { themmeName = "danger"})

5.2 创建异步视图组件

  可通过定义一个返回Task的InvokeAsync方法,创建异步视图组件。

  在Components文件夹下添加PageSize.cs的类文件。

    public class PageSize : ViewComponent
    {
        public async Task<IViewComponentResult> InvokeAsync()
        {
            HttpClient client = new HttpClient();
            HttpResponseMessage response
                = await client.GetAsync("http://apress.com");
            return View(response.Content.Headers.ContentLength);
        }
    }

  创建Views/Shared/Components/文件夹,添加Default.cshtml。

@model long
<div class="m-1 p-1 bg-light text-dark">Page size: @Model</div>

  在Views/Home/Index.cshtml中使用异步组件。

<vc:page-size />

6 创建视图组件类

  视图组件是提供功能的一个汇总或快照,由控制器或Razor Pages来深入处理功能。

  在Pages文件夹Cities.cshtml.cs中添加如下。

    [ViewComponent(Name = "CitiesPageHybrid")]
    public class CitiesModel : PageModel
    {
        public CitiesModel(CitiesData cdata)
        {
            Data = cdata;
        }

        public CitiesData Data { get; set; }

        [ViewComponentContext]
        public ViewComponentContext Context { get; set; }

        public IViewComponentResult Invoke()
        {
            return new ViewViewComponentResult()
            {
                ViewData = new ViewDataDictionary<CityViewModel>(
                    Context.ViewData,
                    new CityViewModel
                    {
                        Cities = Data.Cities.Count(),
                        Population = Data.Cities.Sum(c => c.Population)
                    })
            };
        }
    }

  这个页面模型使用了ViewComponent从而允许它用作一个视图组件,Name参数制定了名称。因为此类不能再继承ViewComponent基类,所以使用ViewComponentContext特性来装饰ViewComponentContext属性,这表明在调用Invoke方法之前,应该把值赋给Context.ViewData,View方法不可用,所以创建了ViewViewComponentResult,它依赖于通过被装饰的属性收到上下文对象。

  在Cities.cshtml中更新视图。

@page
@model MyWebApp.Pages.CitiesModel

<div class="m-2">
    <table class="table table-sm table-striped table-bordered">
        <tbody>
            @foreach (var item in Model.Data.Cities)
            {
                <tr>
                    <td>@item.Name</td>
                    <td>@item.Country</td>
                    <td>@item.Population</td>
                </tr>
            }
        </tbody>
    </table>
</div>

  为创建混合视图组件的视图,需创建Pages/Shared/Components/CitiesPageHybrid文件夹,添加Default.cshtml。

@model CityViewModel

<table class="table table-sm table-bordered text-white bg-dark">
    <thead><tr><th colspan="2">Hybrid Page Summary</th></tr></thead>
    <tbody>
        <tr>
            <td>Cities:</td>
            <td class="text-right">@Model.Cities</td>
        </tr>
        <tr>
            <td>Population:</td>
            <td class="text-right">
                @Model.Population.ToString("#,###")
            </td>
        </tr>
    </tbody>
</table>

  在Pages/Data.cshtml中使用组件。

<div class="bg-info text-white m-2 p-2">
    <vc:cities-page-hybrid />
</div>

  请求http://localhost:5000/cities和http://localhost:5000/data。

posted @ 2024-05-19 11:43  一纸年华  阅读(29)  评论(0编辑  收藏  举报