本篇文章將介紹ASP.Net Core中Partial View及View Component的使用方式。
Partial View
PartialView中文翻成部分顯示或局部顯示,
可將功能錯雜的畫面切割成較小的元件,
適當使用可避免產生過多重複的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與PartialView的關係。
請注意,由於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
較單純也較直覺。
如果需依不同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 View及View Component的篇幅就介紹到這邊,
若內容有問題歡迎討論指教。
參考
https://docs.microsoft.com/zh-tw/aspnet/core/mvc/views/partial?view=aspnetcore-2.1