天天看點

基于 abp vNext 和 .NET Core 開發部落格項目 - Blazor 實戰系列(五)

上一篇完成了分類标簽友鍊的清單查詢頁面資料綁定,還剩下一個文章詳情頁的資料沒有綁,現在簡單的解決掉。

文章詳情

之前已經添加了四個參數:year、month、day、name,用來組成我們最終的URL,繼續添加一個參數用來接收API傳回的資料。

[Parameter]
public int year { get; set; }

[Parameter]
public int month { get; set; }

[Parameter]
public int day { get; set; }

[Parameter]
public string name { get; set; }

////// URL
///private string url => $"/{year}/{(month >= 10 ? month.ToString() : $"0{month}")}/{(day >= 10 ? day.ToString() : $"0{day}")}/{name}/";

////// 文章詳情資料
///private ServiceResultpost;      

然後在初始化方法OnInitializedAsync()中請求資料。

////// 初始化
///protected override async Task OnInitializedAsync()
{
    // 擷取資料
    post = await Http.GetFromJsonAsync<ServiceResult>($"/blog/post?url={url}");
}      

現在拿到了post資料,然後在HTML中綁定即可。

@if (post == null)
{}
else
{
    @if (post.Success)
    {
        var _post = post.Result;@_post.TitleAuthor: @_post.Author
                    
                        Date: @_post.CreationTime
                    
                    
                        Category:@_post.Category.CategoryName
                    
                @((MarkupString)_post.Html)Author:
                    @_post.Author
                Permalink:
                    https://meowv.com/post@_post.Url
                License:
                    本文采用 知識共享 署名-非商業性使用-禁止演繹(CC BY-NC-ND)國際許可協定 進行許可
                Tag(s):
                    
                        @if (_post.Tags.Any())
                        {
                            @foreach (var tag in _post.Tags)
                            {                                # @tag.TagName
                            }
                        }                    
                back
                    · 
                    home
                @if (_post.Previous != null)
                {                     await Common.NavigateTo($"/post{_post.Next.Url}", true))"
                       href="/post@_post.Next.Url">
                        @_post.Next.Title                    
                }}
    else
    {}
}      

其中有幾個地方需要注意一下:

我們從post對象中取到的文章内容HTML,直接顯示是不行了,需要将其解析為HTML标簽,需要用到MarkupString。

然後頁面上有一個後退按鈕,這裡我在Common.cs中寫了一個方法來實作。

////// 後退
//////public async Task BaskAsync()
{
    await InvokeAsync("window.history.back");
}      

還有就是上一篇和下一篇的問題,将具體的URL傳遞給NavigateTo()方法,然後跳轉過去即可。

在Common.cs中将之前文章建立RenderPage()方法修改成NavigateTo()。這個命名更好一點。

////// 跳轉指定URL
/////////true,繞過路由重新整理頁面///public async Task NavigateTo(string url, bool forceLoad = false)
{
    _navigationManager.NavigateTo(url, forceLoad);

    await Task.CompletedTask;
}      

現在資料算是綁定完了,但是遇到了一個大問題,就是詳情頁面的樣式問題,因為用到了Markdown,是以之前是加載了許多JS檔案來處理的。那麼現在肯定行不通了,是以關于詳情頁的樣式問題暫時擱淺,讓我尋找一下好多解決方式。

現在顯示是沒有問題了,就是不太好看,還有關于添加文章的功能,不知道有什麼好的 Markdown 編輯器可以推薦我使用。

基于 abp vNext 和 .NET Core 開發部落格項目 - Blazor 實戰系列(五)

到這裡Blazor的前端展示頁面已經全部弄完了,接下來開始寫背景相關的頁面。

背景首頁

關于背景管理的所有頁面都放在Admin檔案夾下,在Pages檔案夾下建立Admin檔案夾,然後先添加兩個元件頁面:Admin.razor、Auth.razor。

Admin.razor為背景管理的首頁入口,我們在裡面直接添加幾個預知的連結并設定其路由。

@page "/admin"- 部落格内容管理 -????~~~ 新增文章 ~~~????
        ????~~~ 文章管理 ~~~????
        ????~~~ 分類管理 ~~~????
        ????~~~ 标簽管理 ~~~????
        ????~~~ 友鍊管理 ~~~????      

裡面的a标簽所對應的頁面還沒有添加,等做到的時候再加,先手動通路這個頁面看看,當成功授權後就跳到這個頁面來。

基于 abp vNext 和 .NET Core 開發部落格項目 - Blazor 實戰系列(五)

認證授權

關于授權,因為之前在API中已經完成了基于Github的JWT模式的認證授權模式,是以這裡我想做一個無感的授權功能,為什麼說無感呢,因為在我使用GitHub登入的過程中,如果之前已經登入過且沒有清除浏覽器cookie資料,下次再登入的時候會預設直接登入成功,進而達到無感的。

實作邏輯其實也很簡單,我這裡用到了Common.cs中之前添加的公共方法設定和擷取localStorage的方法,我會将token等資訊放入localStorage中。

我設定的路由是:/auth。這個路由需要和 GitHub OAuth App 的回調位址一緻,當登入成功,會回調跳到配置的頁面并攜帶code參數。

在擷取請求參數這塊需要引用一個包:Microsoft.AspNetCore.WebUtilities,添加好後在_Imports.razor添加引用:@using Meowv.Blog.BlazorApp.Shared。

預設還是顯示加載中的元件:。

然後在@code{}中編寫代碼,添加頁面初始化函數。

////// 初始化
//////protected override async Task OnInitializedAsync()
{
    // localStorage中access_token值
    var access_token = await Common.GetStorageAsync("access_token");

    // access_token有值
    if (!string.IsNullOrEmpty(access_token))
    {
        // 擷取token
        var _token = await Http.GetFromJsonAsync<ServiceResult>($"/auth/token?access_token={access_token}");
        if (_token.Success)
        {
            // 将token存入localStorage
            await Common.SetStorageAsync("token", _token.Result);

            // 跳轉至背景首頁
            await Common.NavigateTo("/admin");
        }
        else
        {
            // access_token失效,或者請求失敗的情況下,重新執行一次驗證流程
            await AuthProcessAsync();
        }
    }
    else //access_token為空
    {
        await AuthProcessAsync();
    }
}      

先去擷取localStorage中的access_token值,肯定會有兩種情況,有或者沒有,然後分别去走不同的邏輯。

當access_token有值,就可以直接拿access_token去取token的值,理想情況請求成功拿到了token,這時候可以将token存到浏覽器中,然後正常跳轉至背景管理首頁,還有就是取token失敗了,失敗了就有可能是access_token過期了或者出現異常情況,這時候我們不去提示錯誤,直接抛棄所有,重新來一遍認證授權的流程,放在一個單獨的方法中AuthProcessAsync()。

而當access_token沒值那就好辦了,也去來一遍認證授權的流程即可。

驗證流程AuthProcessAsync()的代碼。

////// 驗證流程
//////private async Task AuthProcessAsync()
{
    // 目前URI對象
    var uri = await Common.CurrentUri();

    // 是否回調攜帶了code參數
    bool hasCode = QueryHelpers.ParseQuery(uri.Query).TryGetValue("code", out Microsoft.Extensions.Primitives.StringValues code);

    if (hasCode)
    {
        var access_token = await Http.GetFromJsonAsync<ServiceResult>($"/auth/access_token?code={code}");
        if (access_token.Success)
        {
            // 将access_token存入localStorage
            await Common.SetStorageAsync("access_token", access_token.Result);

            var token = await Http.GetFromJsonAsync<ServiceResult>($"/auth/token?access_token={access_token.Result}");
            if (token.Success)
            {
                // 将token存入localStorage
                await Common.SetStorageAsync("token", token.Result);

                // 成功認證授權,跳轉至背景管理首頁
                await Common.NavigateTo("/admin");
            }
            else
            {
                // 沒有權限的人,回到首頁去吧
                await Common.NavigateTo("/");

                // 輸出提示資訊
                Console.WriteLine(token.Message);
            }
        }
        else
        {
            // 出錯了,回到首頁去吧
            await Common.NavigateTo("/");

            // 輸出提示資訊
            Console.WriteLine(access_token.Message);
        }
    }
    else
    {
        // 擷取第三方登入位址
        var loginAddress = await Http.GetFromJsonAsync<ServiceResult>("/auth/url");

        // 跳轉到登入頁面
        await Common.NavigateTo(loginAddress.Result);
    }
}      

驗證流程的邏輯先擷取目前URI對象,判斷URI中是否攜帶了code參數,進而可以知道目前頁面是回調的過來的還是直接請求的,擷取目前URI對象放在Common.cs中。

////// 擷取目前URI對象
//////public async TaskCurrentUri()
{
    var uri = _navigationManager.ToAbsoluteUri(_navigationManager.Uri);

    return await Task.FromResult(uri);
}      

在剛才添加的包Microsoft.AspNetCore.WebUtilities中為我們封裝好了解析URI參數的方法。

使用QueryHelpers.ParseQuery(...)擷取code參數的值。

當沒有值的時候,直接取請求登入位址,然後如果登入成功就會跳轉到攜帶code參數的回調頁面。這樣流程就又回到了 驗證流程 開始的地方了。

登入成功,此時code肯定就有值了,那麼直接根據code擷取access_token,存入localStorage,正常情況拿到access_token就去生成token,然後也存入localStorage,成功授權可以跳到背景管理首頁了。

其中如果有任何一個環節出現問題,直接跳轉到網站首頁去。如果授權不成功肯定是你在瞎搞(不接受任何反駁????????),趕緊回到首頁去吧。

現在流程走完,去看看效果。

基于 abp vNext 和 .NET Core 開發部落格項目 - Blazor 實戰系列(五)

GitHub在國内的情況大家知道,有時候慢甚至打不開,有時候還是挺快的,還好今天沒掉鍊子,我遇到過好幾次壓根打不開的情況,擷取可以針對網絡不好的時候我們換成其它的驗證方式,這個以後有機會再優化吧。

驗證元件

這個時候會發現,其實我們壓根不需要打開/auth走驗證流程,直接通路/admin就可以進來管理首頁,這是極其不合理的。那豈不是誰知道位址誰都能進來瞎搞了。是以我們可以在 Shared 檔案夾下添加一個權限驗證的元件:AdminLayout.razor。用來判斷是否真的登入了。

建立一個bool類型的變量 isLogin。預設肯定是false,此時可以讓頁面轉圈圈,使用元件。當isLogin = true的時候我們才展示具體的HTML内容。

那麼就需要用到服務端元件RenderFragment,他有一個固定的參數名稱ChildContent。

判斷是否登入的方法可以寫在初始化方法中,這裡還少了一個API,就是判斷目前token的值是否合法,合法就表示已經成功執行了驗證流程了。token不存在或者不合法,直接拒絕請求傳回到首頁去吧。

整個代碼如下:

@if (!isLogin)
{}
else
{
    @ChildContent
}

@code {
    ////// 展示内容
    ///[Parameter]
    public RenderFragment ChildContent { get; set; }

    ////// 是否登入
    ///private bool isLogin { get; set; }

    ////// 初始化
    //////protected override async Task OnInitializedAsync()
    {
        var token = await Common.GetStorageAsync("token");

        if (string.IsNullOrEmpty(token))
        {
            isLogin = false;

            await Common.NavigateTo("/");
        }
        else
        {
            // TODO:判斷token是否合法,先預設都是正确的
            isLogin = true;
        }
    }
}      

使用這個元件也很友善了,我們背景所有頁面都引用AdminLayout,将展示内容傳遞給就行了,成功驗證後就會展示HTM内容。

在Admin.razor中使用。

@page "/admin"- 部落格内容管理 -????~~~ 新增文章 ~~~????
            ????~~~ 文章管理 ~~~????
            ????~~~ 分類管理 ~~~????
            ????~~~ 标簽管理 ~~~????
            ????~~~ 友鍊管理 ~~~????      

現在清除掉浏覽器緩存,去請求/admin試試。

繼續閱讀