本篇文章將介紹ASP.Net Core中Partial View及View Component的使用方式。
同步發表於個人點部落 - [鐵人賽Day13] ASP.Net Core MVC 進化之路 - View(3) / Partial View及View Component
Partial View中文翻成部分顯示或局部顯示,
可將功能錯雜的畫面切割成較小的元件,
適當使用可避免產生過多重複的HTML。
在過去HTML Helper中使用Partial View,
可依同步方式及串流輸出方式分為@Html.Partial、@Html.PartialAsync、@Html.RenderPartial、@Html.RenderPartialAsync四種方式。
而ASP.Net Core MVC保有原始Partial View的特性,
可透過Html Helper及Tag Helper呼叫Partial View。
但Tag Helper的Partial View本身就直接是Async,
使用上也較容易閱讀,
所以筆者比較推薦使用Tag Helper的方式(ASP.Net Core的Tag Helper很強大!),
命名的話可參考官方建議檔名以_開頭Partial結尾,
如_PokemonPartial.cshtml。
當Response夾帶資料回到View時,
View在呼叫Partial View時會將data指定給Partial View,
此時Partial View會自動繼承View所擁有的model與viewdata,
筆者個人習慣以「父子關係」來形容View與Partial View的關係。
請注意,由於Partial View與View是發生在同一條Request-Response上,
所以Partial View本身並不會直接與Response互動,
換句話說,Partial View僅負責接收資料後呈現畫面的工作而已。
有關Partial View的lifecycle可參考下圖,
下面使用Partial View方式設計Pokemon的列表。IronmentController.cs
public IActionResult Index()
{
    var pokemons = new List<Pokemon>()
    {
        new Pokemon()
        {
            Id = 1,
            Name = "水箭龜",
            Property = "水系"
        },
        new Pokemon()
        {
            Id = 2,
            Name = "噴火龍",
            Property = "火系"
        },
        new Pokemon()
        {
            Id = 3,
            Name = "妙蛙花",
            Property = "草系"
        }
    };  
    return View(pokemons);
}
Index.cshtml
@model IEnumerable<IronmenMvcWeb.Models.Pokemon>
@foreach (var item in Model)
{
    <partial name="_PokemonDetailPartial" model="item" />
}
_PokemonDetailPartial.cshtml
@model IronmenMvcWeb.Models.Pokemon
<div>
    <hr />
    <dl class="dl-horizontal">
        <dt>編號</dt>
        <dd>@Model.Id</dd>
        <dt>屬性</dt>
        <dd>
            @Model.Property
        </dd>
        <dt>名稱</dt>
        <dd>@Model.Name</dd>
    </dl>
</div>
輸出結果
PartialTagHelper中還可設定許多參數,
最常用的是name跟model,
而for的設計用途是給Razor Page用的,
因此不能與model同時使用。
本系列是學習ASP.Net Core MVC,
使用model較單純也較直覺。
如果需依不同Partial View傳遞自訂的ViewData,
則可使用view-data屬性。
最後要介紹的是View Component,
通常講到Partial View就會搭配Child Action(ASP.Net MVC5)一起介紹。
前面提到Partial View是用來拆解View中重複性較高的區塊,
但如果前端邏輯較複雜時Partial View就不適用了,
在過去我們會使用Child Action將畫面上可獨立的功能抽取出來,
不過也因為它擁有獨立的生命週期導致效能較為不佳。
而ASP.Net Core MVC移除了Child Action後推出了View Component,
官方還很貼心地提供適用情境:
接著介紹View Component的特性:
Controller回傳的ActionResult。Default.cshtml(名稱可另外指定)。@Component.InvokeAsync()
有關View Component生命週期可參考下圖。
從圖中我們可以發現,
Result Filter只有在回傳View Result時,
才會叫用View Engine將View行轉譯(將.cshtml組裝成.html),
而轉譯過程中若發現@Component.InvokeAsync("component_name", new { eid = "3"}),
指定名稱後可透過匿名類別注入參數(key-value),
接著叫用對應的View Component,
並將渲染後的結果(HTML)回傳給View Engine。
以下簡單實作一個天氣看板,
我們先建立待會需要用到的類別。Weather.cs
public class Weather
{
    //為方便實作故統一使用string
    public string Location { get; set; }
    public string Temperature { get; set; }
    public string Humidity { get; set; }
    public string RainProbability { get; set; }
    public string Status { get; set; }
}
WeatherService.cs
public class WeatherService
{
    public  Weather GetWeather(string location, DateTime date)
    {
        var data = this.GetFakeWeatherData();
        return data.SingleOrDefault(x => x.Location == location
            && x.Date == date);
    }
    private List<Weather> GetFakeWeatherData()
    {
        var fakeData = new List<Weather>()
        {
            new Weather()
            {
                Date = new DateTime(2018, 10, 1),
                Location = "台北市",
                Humidity = "38%",
                Temperature = "24-28度C",
                RainProbability = "40%",
                Status = "晴天"
            },
            new Weather()
            {
                Date = new DateTime(2018, 10, 1),
                Location = "桃園市",
                Temperature = "24-30度C",
                Humidity = "35%",
                RainProbability = "20%",
                Status = "晴天"
            },
            new Weather()
            {
                Date = new DateTime(2018, 10, 1),
                Location = "宜蘭縣",
                Temperature = "24-28度C",
                Humidity = "80%",
                RainProbability = "65%",
                Status = "雨天"
            }
        };
        return fakeData;
    }
}
接著建立ViewComponent類別,
官方提供了三種實作方式:
以下使用第一種繼承的方式示範。WeatherBoardViewComponent.cs
public class WeatherBoardViewComponent : ViewComponent
{
    private WeatherService weather;
    public WeatherBoardViewComponent(WeatherService _weather)
    {
        weather = _weather;
    }
        
    public async Task<IViewComponentResult> InvokeAsync(string location, DateTime date)
    {
        var weather = weather.GetWeather(location, date);
        return View(weather);
    }
}
接著我們需建立對應的View,
而View擺放的位置是有限制的(因為View Engine在組裝時只會尋找以下可能途徑):
WeatherBoardViewComponent的<view_component_name>為WeatherBoard。<view_name>可使用預設的Default.cshtml,
如果要自訂名稱記得在InvokeAsync()中return View()時設定。
public async Task<IViewComponentResult> InvokeAsync(string location, DateTime date)
{
    var weather = weatherService.GetWeather(location, date);
    return View("MyWeatherBoard", weather);
}
我們使用Default.cshtml實作。
@model IronmenMvcWeb.Models.Weather
<div class="alert alert-success">
    <dl class="dl-horizontal">
        <dt>日期</dt>
        <dd>@Model.Date.ToShortDateString()</dd>
        <dt>地區</dt>
        <dd>
            @Model.Location
        </dd>
        <dt>溫度</dt>
        <dd>@Model.Temperature</dd>
        <dt>濕度</dt>
        <dd>@Model.Humidity</dd>
        <dt>天氣</dt>
        <dd>@Model.Status</dd>
    </dl>
</div>
最後在Index.cshtml中呼叫ViewComponent。
@model IEnumerable<IronmenMvcWeb.Models.Pokemon>
<h2>View Component</h2>
@await Component.InvokeAsync("WeatherBoard", new { location = "台北市", date = new DateTime(2018, 10, 1) })
執行結果。
Partial View及View Component的篇幅就介紹到這邊,
若內容有問題歡迎討論指教。
https://docs.microsoft.com/zh-tw/aspnet/core/mvc/views/partial?view=aspnetcore-2.1
https://docs.microsoft.com/zh-tw/aspnet/core/mvc/views/tag-helpers/built-in/partial-tag-helper?view=aspnetcore-2.1