[鐵人賽Day13] ASP.Net Core MVC 進化之路 - View(3) / Partial View及View Component

本篇文章將介紹ASP.Net Core中Partial ViewView Component的使用方式。

 

Partial View

PartialView中文翻成部分顯示局部顯示

可將功能錯雜的畫面切割成較小的元件,

適當使用可避免產生過多重複的HTML。

 

在過去HTML Helper中使用Partial View

可依同步方式及串流輸出方式分為@Html.Partial@Html.PartialAsync@Html.RenderPartial@Html.RenderPartialAsync四種方式。

ASP.Net Core MVC保有原始Partial View的特性,

可透過Html HelperTag Helper呼叫Partial View

Tag HelperPartial View本身就直接是Async

使用上也較容易閱讀,

所以筆者比較推薦使用Tag Helper(ASP.Net Core的Tag Helper很強大!),

命名的話可參考官方建議檔名以 _開頭Partial結尾,

_PokemonPartial.cshtml

 

Response夾帶資料回到View時,

View在呼叫Partial View時會將data指定給Partial View

此時Partial View會自動繼承View所擁有的modelviewdata

筆者個人習慣以「父子關係」來形容ViewPartialView的關係。

請注意,由於Partial ViewView是發生在同一條Request-Response上,

所以Partial View本身並不會直接與Response互動,

換句話說,Partial View僅負責接收資料後呈現畫面的工作而已。

有關Partial Viewlifecycle可參考下圖,

 

下面使用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中還可設定許多參數

最常用的是namemodel

for的設計用途是給Razor Page用的,

因此不能與model同時使用。

本系列是學習ASP.Net Core MVC

使用model較單純也較直覺。

如果需依不同PartialView傳遞自訂的ViewData,

則可使用view-data屬性

 

View Component

最後要介紹的是View Component

通常講到Partial View就會搭配Child Action(ASP.Net MVC5)一起介紹。

前面提到Partial View是用來拆解View中重複性較高的區塊,

但如果前端邏輯較複雜Partial View就不適用了,

在過去我們會使用Child Action將畫面上可獨立的功能抽取出來,

不過也因為它擁有獨立的生命週期導致效能較為不佳。

ASP.Net Core MVC移除了Child Action後推出了View Component

官方還很貼心地提供適用情境

  • 功能列表(Navigation Menu Bar)
  • 標籤雲(Tag Cloud)
  • 購物車(Shopping Cart)
  • 廣告(Advertisement)
  • 側欄資訊看板(Sidebar Content)

 

接著介紹View Component的特性:

  • 通常會在View中進行呼叫,也可將其作為Controller回傳的ActionResult
  • 被叫用時是以方法形式呼叫,HTTP形式傳輸(無法被HTTP端點叫用)。
  • 使用匿名類別方式注入參數,而Model Binding
  • 一個View Component需對應一個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類別,

官方提供了三種實作方式:

  • 繼承ViewComponent類別。
  • 在類別上面套用[ViewComponent] ,或繼承擁有[ViewComponent] 的類別。
  • 建立名稱結尾為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在組裝時只會尋找以下可能途徑):

  • /Views/<controller_name>/Components/<view_component_name>/<view_name>
  • /Views/Shared/Components/<view_component_name>/<view_name>

<view_component_name>命名規則與Controller相同,

例 : 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 ViewView 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