系列文章
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 使用 abp cli 搭建專案
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 給專案瘦身,讓它跑起來
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 完善與美化,Swagger登場
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 資料訪問和程式碼優先
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 自定義倉儲之增刪改查
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 統一規範API,包裝返回模型
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 再說Swagger,分組、描述、小綠鎖
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 接入GitHub,用JWT保護你的API
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 異常處理和日誌記錄
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 使用Redis快取資料
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 整合Hangfire實現定時任務處理
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 用AutoMapper搞定物件對映
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 定時任務最佳實戰(一)
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 定時任務最佳實戰(二)
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 定時任務最佳實戰(三)
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 部落格介面實戰篇(一)
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 部落格介面實戰篇(二)
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 部落格介面實戰篇(三)
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 部落格介面實戰篇(四)
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 部落格介面實戰篇(五)
- 基於 abp vNext 和 .NET Core 開發部落格專案 - Blazor 實戰系列(一)
- 基於 abp vNext 和 .NET Core 開發部落格專案 - Blazor 實戰系列(二)
上一篇完成了部落格的主題切換,選單和二維碼的顯示與隱藏功能,本篇繼續完成分頁查詢文章列表的資料展示。
新增頁面
現在點選頁面上的連結,都會提示錯誤訊息,因為沒有找到對應的路由地址。先在Pages下建立五個資料夾:Posts、Categories、Tags、Apps、FriendLinks。
然後在對應的資料夾下新增Razor元件。
- Posts資料夾:文章列表頁面
Posts.razor
、根據分類查詢文章列表頁面Posts.Category.razor
、根據標籤查詢文章列表頁面Posts.Tag.razor
、文章詳情頁Post.razor
- Categories資料夾:分類列表頁面
Categories.razor
- Tags資料夾:標籤列表頁面
Tags.razor
- Apps資料夾:
Apps.razor
準備將友情連結入口放在裡面 - FriendLinks資料夾:友情連結列表頁面
FriendLinks.razor
先分別建立上面這些Razor元件,差不多除了後臺CURD的頁面就這些了,現在來逐個突破。
不管三七二十一,先把所有頁面的路由給確定了,指定頁面路由使用 @page
指令,官方文件說不支援可選引數,但是可以支援多個路由規則。
預設先什麼都不顯示,可以將之前的載入中圈圈寫成一個元件,供每個頁面使用。
在Shared資料夾新增元件Loading.razor
。
<!--Loading.razor-->
<div class="loader"></div>
//Posts.razor
@page "/posts/"
@page "/posts/page/{page:int}"
@page "/posts/{page:int}"
<Loading />
@code {
/// <summary>
/// 當前頁碼
/// </summary>
[Parameter]
public int? page { get; set; }
}
這裡我加了三條,可以匹配沒有page引數,帶page引數的,/posts/page/{page:int}
這個大家可以不用加,我是用來相容目前線上的部落格路由的。總的來說可以匹配到:/posts
、/posts/1
、/posts/page/1
這樣的路由。
//Posts.Category.razor
@page "/category/{name}"
<Loading />
@code {
/// <summary>
/// 分類名稱引數
/// </summary>
[Parameter]
public string name { get; set; }
}
根據分類名稱查詢文章列表頁面,name當作分類名稱引數,可以匹配到類似於:/category/aaa
、/category/bbb
這樣的路由。
//Posts.Tag.razor
@page "/tag/{name}"
<Loading />
@code {
/// <summary>
/// 標籤名稱引數
/// </summary>
[Parameter]
public string name { get; set; }
}
這個根據標籤名稱查詢文章列表頁面和上面差不多一樣,可以匹配到:/tag/aaa
、/tag/bbb
這樣的路由。
//Post.razor
@page "/post/{year:int}/{month:int}/{day:int}/{name}"
<Loading />
@code {
[Parameter]
public int year { get; set; }
[Parameter]
public int month { get; set; }
[Parameter]
public int day { get; set; }
[Parameter]
public string name { get; set; }
}
文章詳情頁面的路由有點點複雜,以/post/開頭,加上年月日和當前文章的語義化名稱組成。分別新增了四個引數年月日和名稱,用來接收URL的規則,使用int來設定路由的約束,最終可以匹配到路由:/post/2020/06/09/aaa
、/post/2020/06/9/bbb
這樣的。
//Categories.razor
@page "/categories"
<Loading />
//Tags.razor
@page "/tags"
<Loading />
//FriendLinks.razor
@page "/friendlinks"
<Loading />
分類、標籤、友情連結都是固定的路由,像上面這樣就不多說了,然後還剩一個Apps.razor
。
//Apps.razor
@page "/apps"
<div class="container">
<div class="post-wrap">
<h2 class="post-title">- Apps -</h2>
<ul>
<li>
<a target="_blank" href="https://support.qq.com/products/75616"><h3>吐個槽_留言板</h3></a>
</li>
<li>
<NavLink href="/friendlinks"><h3>友情連結</h3></NavLink>
</li>
</ul>
</div>
</div>
在裡面新增了一個友情連結的入口,和一個 騰訊兔小巢 的連結,歡迎大家吐槽留言噢。
現在可以執行一下看看,點選所有的連結都不會提示錯誤,只要路由匹配正確就會出現載入中的圈圈了。
文章列表
在做文章列表的資料繫結的時候遇到了大坑,有前端開發經驗的都知道,JavaScript弱型別語言中接收json資料隨便玩,但是在Blazor中我試了下動態接受傳遞過來的JSON資料,一直報錯壓根執行不起來。所以在請求api接收資料的時候需要指定接收物件,那就好辦了我就直接引用API中的.Application.Contracts
就行了啊,但是緊接著坑又來了,目標框架對不上,引用之後也執行不起來,這裡應該是之前沒有設計好。
於是,我就想了一個折中的辦法吧,將API中的返回物件可以用到的DTO先手動拷貝一份到Blazor專案中,後續可以考慮將公共的返回模型做成Nuget包,方便使用。
那麼,最終就是在Blazor中新增一個Response資料夾,用來放接收物件,裡面的內容看圖:
有點傻,先這樣解決,後面在做進一步的優化吧。
將我們複製進來的東東,在_Imports.razor
中新增引用。
//_Imports.razor
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Meowv.Blog.BlazorApp.Shared
@using Response.Base
@using Response.Blog
@inject HttpClient Http
@inject Commons.Common Common
@inject HttpClient Http
:注入HttpClient
,用它來請求API資料。
現在有了接收物件,接下來就好辦了,來實現分頁查詢文章列表吧。
先新增三個私有變數,限制條數,就是一次載入文章的數量,總頁碼用來計算分頁,還有就是API的返回資料的接收型別引數。
/// <summary>
/// 限制條數
/// </summary>
private int Limit = 15;
/// <summary>
/// 總頁碼
/// </summary>
private int TotalPage;
/// <summary>
/// 文章列表資料
/// </summary>
private ServiceResult<PagedList<QueryPostDto>> posts;
然後當頁面初始化的時候,去載入資料,渲染頁面,因為page引數可能存在為空的情況,所以要考慮進去,當為空的時候給他一個預設值1。
/// <summary>
/// 初始化
/// </summary>
protected override async Task OnInitializedAsync()
{
// 設定預設值
page = page.HasValue ? page : 1;
await RenderPage(page);
}
/// <summary>
/// 點選頁碼重新渲染資料
/// </summary>
/// <param name="page"></param>
/// <returns></returns>
private async Task RenderPage(int? page)
{
// 獲取資料
posts = await Http.GetFromJsonAsync<ServiceResult<PagedList<QueryPostDto>>>($"/blog/posts?page={page}&limit={Limit}");
// 計算總頁碼
TotalPage = (int)Math.Ceiling((posts.Result.Total / (double)Limit));
}
在初始化方法中設定預設值,呼叫RenderPage(...)
獲取到API返回來的資料,並根據返回資料計算出頁碼,這樣就可以繫結資料了。
@if (posts == null)
{
<Loading />
}
else
{
<div class="post-wrap archive">
@if (posts.Success && posts.Result.Item.Any())
{
@foreach (var item in posts.Result.Item)
{
<h3>@item.Year</h3>
@foreach (var post in item.Posts)
{
<article class="archive-item">
<NavLink href="@("/post" + post.Url)">@post.Title</NavLink>
<span class="archive-item-date">@post.CreationTime</span>
</article>
}
}
<nav class="pagination">
@for (int i = 1; i <= TotalPage; i++)
{
var _page = i;
if (page == _page)
{
<span class="page-number current">@_page</span>
}
else
{
<a class="page-number" @onclick="@(() => RenderPage(_page))" href="/posts/@_page">@_page</a>
}
}
</nav>
}
else
{
<ErrorTip />
}
</div>
}
在載入資料的時候肯定是需要一個等待時間的,因為不可抗拒的原因資料還沒載入出來的時候,可以讓它先轉一會圈圈,當posts
不為空的時候,再去繫結資料。
在繫結資料,for迴圈頁碼的時候我又遇到了一個坑?,這裡不能直接去使用變數i,必須新建一個變數去接受它,不然我傳遞給RenderPage(...)
的引數就會是錯的,始終會取到最後一次迴圈的i值。
當判斷資料出錯或者沒有資料的時候,在把錯誤提示<ErrorTip />
扔出來顯示。
做到這裡,可以去執行看看了,肯定會報錯,因為還有一個重要的東西沒有改,就是我們介面的BaseAddress
,在Program.cs
中,預設是當前Blazor專案的執行地址。
我們需要先將API專案執行起來,拿到地址配置在Program.cs
中,因為現在還是本地開發,有多種辦法可以解決,可以將.HttpApi.Hosting
設為啟動專案直接執行起來,也可以使用命令直接dotnet run
。
我這裡為了方便,直接釋出在IIS中,後續只要電腦開啟就可以訪問了,你甚至選擇其它任何你能想到的方式。
關於如何釋出這裡先不做展開,有機會的話寫一篇將.net core開發的專案釋出到 Windows、Linux、Docker 的教程吧。
所以我的Program.cs
中配置如下:
//Program.cs
using Meowv.Blog.BlazorApp.Commons;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace Meowv.Blog.BlazorApp
{
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
var baseAddress = "https://localhost";
if (builder.HostEnvironment.IsProduction())
baseAddress = "https://api.meowv.com";
builder.Services.AddTransient(sp => new HttpClient
{
BaseAddress = new Uri(baseAddress)
});
builder.Services.AddSingleton(typeof(Common));
await builder.Build().RunAsync();
}
}
}
baseAddress
預設為本地開發地址,使用builder.HostEnvironment.IsProduction()
判斷是否為線上正式生產環境,改變baseAddress
地址。
現在可以看到已經可以正常獲取資料,並且翻頁也是OK的,然後又出現了一個新的BUG?。
解決BUG
細心的可以發現,當我點選頭部元件的Posts
a 標籤選單時候,頁面沒有發生變化,只是路由改變了。
思來想去,我決定使用NavigationManager
這個URI和導航狀態幫助程式來解決,當點選頭部的Posts
a 標籤選單直接重新整理頁面得了。
在Common.cs
中使用建構函式注入NavigationManager
,然後新增一個跳轉指定URL的方法。
/// <summary>
/// 跳轉指定URL
/// </summary>
/// <param name="uri"></param>
/// <param name="forceLoad">true,繞過路由重新整理頁面</param>
/// <returns></returns>
public async Task RenderPage(string url, bool forceLoad = true)
{
_navigationManager.NavigateTo(url, forceLoad);
await Task.CompletedTask;
}
當forceLoad = true
的時候,將會繞過路由直接強制重新整理頁面,如果forceLoad = false
,則不會重新整理頁面。
緊接著在Header.razor
中修改程式碼,新增點選事件。
@*<NavLink class="menu-item" href="posts">Posts</NavLink>*@
<NavLink class="menu-item" href="posts" @onclick="@(async () => await Common.RenderPage("posts"))">Posts</NavLink>
總算是搞定,完成了分頁查詢文章列表的資料繫結,今天就到這裡吧,未完待續...