iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 13
0

前言

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

同步發表於個人點部落 - [鐵人賽Day13] ASP.Net Core MVC 進化之路 - View(3) / Partial View及View Component

Partial View

Partial View中文翻成部分顯示或局部顯示,
可將功能錯雜的畫面切割成較小的元件,
適當使用可避免產生過多重複的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
筆者個人習慣以「父子關係」來形容ViewPartial View的關係。
請注意,由於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較單純也較直覺。
如果需依不同Partial View傳遞自訂的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 EngineView行轉譯(將.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


上一篇
[鐵人賽Day12] - View(2) / Tag Helper及Layout
下一篇
[鐵人賽Day14] - Controller
系列文
菜鳥練等區-ASP.Net Core MVC進化之路30

尚未有邦友留言

立即登入留言