基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(五)

系列文章

  1. 使用 abp cli 搭建项目

  2. 给项目瘦身,让它跑起来

  3. 完善与美化,Swagger登场

  4. 数据访问和代码优先

  5. 自定义仓储之增删改查

  6. 统一规范API,包装返回模型

  7. 再说Swagger,分组、描述、小绿锁

  8. 接入GitHub,用JWT保护你的API

  9. 异常处理和日志记录

  10. 使用Redis缓存数据

  11. 集成Hangfire实现定时任务处理

  12. 用AutoMapper搞定对象映射

  13. 定时任务最佳实战(一)

  14. 定时任务最佳实战(二)

  15. 定时任务最佳实战(三)

  16. 博客接口实战篇(一)

  17. 博客接口实战篇(二)

  18. 博客接口实战篇(三)

  19. 博客接口实战篇(四)

  20. 博客接口实战篇(五)

  21. Blazor实战系列(一)

  22. Blazor实战系列(二)

  23. Blazor实战系列(三)

  24. 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; }/// <summary>
/// URL
/// </summary>
private string url => $"/{year}/{(month >= 10 ? month.ToString() : $"0{month}")}/{(day >= 10 ? day.ToString() : $"0{day}")}/{name}/";/// <summary>
/// 文章详情数据
/// </summary>
private ServiceResult<PostDetailDto> post;

然后在初始化方法OnInitializedAsync()中请求数据。

/// <summary>
/// 初始化
/// </summary>
protected override async Task OnInitializedAsync()
{// 获取数据post = await Http.GetFromJsonAsync<ServiceResult<PostDetailDto>>($"/blog/post?url={url}");
}

现在拿到了post数据,然后在HTML中绑定即可。

@if (post == null)
{<Loading />
}
else
{@if (post.Success){var _post = post.Result;<article class="post-wrap"><header class="post-header"><h1 class="post-title">@_post.Title</h1><div class="post-meta">Author: <a itemprop="author" rel="author" href="javascript:;">@_post.Author</a><span class="post-time">Date: <a href="javascript:;">@_post.CreationTime</a></span><span class="post-category">Category:<a href="/category/@_post.Category.DisplayName/">@_post.Category.CategoryName</a></span></div></header><div class="post-content" id="content">@((MarkupString)_post.Html)</div><p class="post-copyright"><p class="copyright-item"><span>Author:</span><span>@_post.Author</span></p><p class="copyright-item"><span>Permalink:</span><span><a href="/post@_post.Url">https://meowv.com/post@_post.Url</a></span></p><p class="copyright-item"><span>License:</span><span>本文采用<a target="_blank" href="http://creativecommons.org/licenses/by-nc-nd/4.0/"> 知识共享 署名-非商业性使用-禁止演绎(CC BY-NC-ND)国际许可协议 </a>进行许可</span></p></p><p class="post-tags"><div><span>Tag(s):</span><span class="tag">@if (_post.Tags.Any()){@foreach (var tag in _post.Tags){<a href="/tag/@tag.DisplayName/"># @tag.TagName</a>}}</span></div><div><a @onclick="async () => await Common.BaskAsync()">back</a><span>· </span><a href="/">home</a></div></p><p class="post-nav">@if (_post.Previous != null){<a class="prev"rel="prev"@onclick="@(async () => await Common.NavigateTo($"/post{_post.Previous.Url}, true))"href="/post@_post.Previous.Url">@_post.Previous.Title</a>}@if (_post.Next != null){<a class="next"rel="next"@onclick="@(async () => await Common.NavigateTo($"/post{_post.Next.Url}", true))"href="/post@_post.Next.Url">@_post.Next.Title</a>}</p></article>}else{<ErrorTip />}
}

其中有几个地方需要注意一下:

我们从post对象中取到的文章内容HTML,直接显示是不行了,需要将其解析为HTML标签,需要用到MarkupString

然后页面上有一个后退按钮,这里我在Common.cs中写了一个方法来实现。

/// <summary>
/// 后退
/// </summary>
/// <returns></returns>
public async Task BaskAsync()
{await InvokeAsync("window.history.back");
}

还有就是上一篇和下一篇的问题,将具体的URL传递给NavigateTo()方法,然后跳转过去即可。

Common.cs中将之前文章创建RenderPage()方法修改成NavigateTo()。这个命名更好一点。

/// <summary>
/// 跳转指定URL
/// </summary>
/// <param name="uri"></param>
/// <param name="forceLoad">true,绕过路由刷新页面</param>
/// <returns></returns>
public async Task NavigateTo(string url, bool forceLoad = false)
{_navigationManager.NavigateTo(url, forceLoad);await Task.CompletedTask;
}

现在数据算是绑定完了,但是遇到了一个大问题,就是详情页面的样式问题,因为用到了Markdown,所以之前是加载了许多JS文件来处理的。那么现在肯定行不通了,所以关于详情页的样式问题暂时搁浅,让我寻找一下好多解决方式。

现在显示是没有问题了,就是不太好看,还有关于添加文章的功能,不知道有什么好的 Markdown 编辑器可以推荐我使用。

到这里Blazor的前端展示页面已经全部弄完了,接下来开始写后台相关的页面。

后台首页

关于后台管理的所有页面都放在Admin文件夹下,在Pages文件夹下新建Admin文件夹,然后先添加两个组件页面:Admin.razorAuth.razor

Admin.razor为后台管理的首页入口,我们在里面直接添加几个预知的链接并设置其路由。

@page "/admin"<div class="post-wrap"><h2 class="post-title">-&nbsp;博客内容管理&nbsp;-</h2><ul><li><a href="/admin/post"><h3>????~~~ 新增文章 ~~~????</h3></a></li><li><a href="/admin/posts"><h3>????~~~ 文章管理 ~~~????</h3></a></li><li><a href="/admin/categories"><h3>????~~~ 分类管理 ~~~????</h3></a></li><li><a href="/admin/tags"><h3>????~~~ 标签管理 ~~~????</h3></a></li><li><a href="/admin/friendlinks"><h3>????~~~ 友链管理 ~~~????</h3></a></li></ul>
</div>

里面的a标签所对应的页面还没有添加,等做到的时候再加,先手动访问这个页面看看,当成功授权后就跳到这个页面来。

认证授权

关于授权,因为之前在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

默认还是显示加载中的组件:<Loading />

然后在@code{}中编写代码,添加页面初始化函数。

/// <summary>
/// 初始化
/// </summary>
/// <returns></returns>
protected override async Task OnInitializedAsync()
{// localStorage中access_token值var access_token = await Common.GetStorageAsync("access_token");// access_token有值if (!string.IsNullOrEmpty(access_token)){// 获取tokenvar _token = await Http.GetFromJsonAsync<ServiceResult<string>>($"/auth/token?access_token={access_token}");if (_token.Success){// 将token存入localStorageawait 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()的代码。

/// <summary>
/// 验证流程
/// </summary>
/// <returns></returns>
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<string>>($"/auth/access_token?code={code}");if (access_token.Success){// 将access_token存入localStorageawait Common.SetStorageAsync("access_token", access_token.Result);var token = await Http.GetFromJsonAsync<ServiceResult<string>>($"/auth/token?access_token={access_token.Result}");if (token.Success){// 将token存入localStorageawait 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<string>>("/auth/url");// 跳转到登录页面await Common.NavigateTo(loginAddress.Result);}
}

验证流程的逻辑先获取当前URI对象,判断URI中是否携带了code参数,从而可以知道当前页面是回调的过来的还是直接请求的,获取当前URI对象放在Common.cs中。

/// <summary>
/// 获取当前URI对象
/// </summary>
/// <returns></returns>
public async Task<Uri> CurrentUri()
{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,成功授权可以跳到后台管理首页了。

其中如果有任何一个环节出现问题,直接跳转到网站首页去。如果授权不成功肯定是你在瞎搞(不接受任何反驳????????),赶紧回到首页去吧。

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

GitHub在国内的情况大家知道,有时候慢甚至打不开,有时候还是挺快的,还好今天没掉链子,我遇到过好几次压根打不开的情况,获取可以针对网络不好的时候我们换成其它的验证方式,这个以后有机会再优化吧。

验证组件

这个时候会发现,其实我们压根不需要打开/auth走验证流程,直接访问/admin就可以进来管理首页,这是极其不合理的。那岂不是谁知道地址谁都能进来瞎搞了。所以我们可以在 Shared 文件夹下添加一个权限验证的组件:AdminLayout.razor。用来判断是否真的登录了。

新建一个bool类型的变量 isLogin。默认肯定是false,此时可以让页面转圈圈,使用<Loading />组件。当isLogin = true的时候我们才展示具体的HTML内容。

那么就需要用到服务端组件RenderFragment,他有一个固定的参数名称ChildContent

判断是否登录的方法可以写在初始化方法中,这里还少了一个API,就是判断当前token的值是否合法,合法就表示已经成功执行了验证流程了。token不存在或者不合法,直接拒绝请求返回到首页去吧。

整个代码如下:

@if (!isLogin)
{<Loading />
}
else
{@ChildContent
}@code {/// <summary>/// 展示内容/// </summary>[Parameter]public RenderFragment ChildContent { get; set; }/// <summary>/// 是否登录/// </summary>private bool isLogin { get; set; }/// <summary>/// 初始化/// </summary>/// <returns></returns>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"<AdminLayout><div class="post-wrap"><h2 class="post-title">-&nbsp;博客内容管理&nbsp;-</h2><ul><li><a href="/admin/post"><h3>????~~~ 新增文章 ~~~????</h3></a></li><li><a href="/admin/posts"><h3>????~~~ 文章管理 ~~~????</h3></a></li><li><a href="/admin/categories"><h3>????~~~ 分类管理 ~~~????</h3></a></li><li><a href="/admin/tags"><h3>????~~~ 标签管理 ~~~????</h3></a></li><li><a href="/admin/friendlinks"><h3>????~~~ 友链管理 ~~~????</h3></a></li></ul></div>
</AdminLayout>

现在清除掉浏览器缓存,去请求/admin试试。

完美,比较简单的实现了验证是否登录的组件。其中还有许多地方可以优化,就交给大家去自行完成了????。

开源地址:https://github.com/Meowv/Blog/tree/blog_tutorial

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/309289.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[JavaWeb-Servlet]概述与快速入门

Servlet&#xff1a; server applet * 概念&#xff1a;运行在服务器端的小程序* Servlet就是一个接口&#xff0c;定义了Java类被浏览器访问到(tomcat识别)的规则。* 将来我们自定义一个类&#xff0c;实现Servlet接口&#xff0c;复写方法。* 快速入门&#xff1a;1. 创建Ja…

01tire+洛谷P4551 最长异或路径

题目&#xff1a; 给定一棵n个点的带权树&#xff0c;结点下标从1开始到N。寻找树中找两个结点&#xff0c;求最长的异或路径。 异或路径指的是指两个结点之间唯一路径上的所有边权的异或。 输入格式 第一行一个整数NN&#xff0c;表示点数。 接下来 n−1 行&#xff0c;给…

C#9.0 终于来了,带你一起解读 nint 和 Pattern matching 两大新特性玩法

一&#xff1a;背景1. 讲故事上一篇C#9.0 终于来了&#xff0c;您还学的动吗&#xff1f; 带上VS一起解读吧&#xff01;跟大家聊到了Target-typed new 和 Lambda discard parameters&#xff0c;看博客园和公号里的阅读量都达到了新高&#xff0c;甚是欣慰&#xff0c;不管大家…

[JavaWeb-Tomcat]web服务器软件_Tomcat介绍

Tomcat&#xff1a;web服务器软件 1. 下载&#xff1a;http://tomcat.apache.org/2. 安装&#xff1a;解压压缩包即可。* 注意&#xff1a;安装目录建议不要有中文和空格3. 卸载&#xff1a;删除目录就行了4. 启动&#xff1a;* bin/startup.bat ,双击运行该文件即可* 访问&…

软件设计模式期末大作业——可乐商城管理系统

文章目录设计模式大作业软 件 设 计 模 式 任 务 书设计要求&#xff1a;学生应完成的工作&#xff1a;1. 应用场景描述2. 设计模式选择3. 实现语言与工具参考文献阅读&#xff1a;工作计划&#xff1a;一、系统目标1. 设计目的2. 需求描述二、 系统模式选择1.需求分析2.选用设…

深度解读Microsoft Build 2020:提升开发效率,优化开发环境

Microsoft Build 2020在众多新产品与技术发布中圆满落幕但身为开发技术人深知技术世界的更迭、求索却从未止步唯有不断提升自身技能栈创新方能从技术浮沉中获得更多养分让技术予力世界更有温度Microsoft Build 2020大会后&#xff0c; Visual Studio Family 和 .NET 成为众多开…

[JavaWeb]web相关概念回顾

web相关概念回顾 1. 软件架构1. C/S&#xff1a;客户端/服务器端2. B/S&#xff1a;浏览器/服务器端2. 资源分类1. 静态资源&#xff1a;所有用户访问后&#xff0c;得到的结果都是一样的&#xff0c;称为静态资源.静态资源可以直接被浏览器解析* 如&#xff1a; html,css,Jav…

Github 数据洞察之复杂信息网络

摘要&#xff1a;开源协作是数字化时代的必备技能&#xff0c;而背后的开发行为分析&#xff0c;能够让你看到一幅开源世界的全景图&#xff1b;2020年2月&#xff0c;X-lab 开放实验室联合InfoQ发布了《GitHub 2019 数字年报》&#xff0c;收到了业界的一致好评。数据科学与工…

Java解决x的平方根问题

Java解决x的平方根问题 01 题目 给你一个非负整数 x &#xff0c;计算并返回 x 的 算术平方根 。 由于返回类型是整数&#xff0c;结果只保留 整数部分 &#xff0c;小数部分将被 舍去 。 **注意&#xff1a;**不允许使用任何内置指数函数和算符&#xff0c;例如 pow(x, 0.…

[JavaWeb-Servlet]Servlet相关配置

Servlet相关配置 1. urlpartten:Servlet访问路径1. 一个Servlet可以定义多个访问路径 &#xff1a; WebServlet({"/d4","/dd4","/ddd4"})2. 路径定义规则&#xff1a;1. /xxx&#xff1a;路径匹配2. /xxx/xxx:多层路径&#xff0c;目录结构3. *.…

number one

alibab 第一天&#xff1a; 领了个联想电脑&#xff0c;之前还一直担心会不适应MAC&#xff0c;我想多了&#xff0c;不过转正之后还是会换成MAC。。。 下载安装包&#xff1a; idea&#xff1a;开始以为要专业版&#xff0c;结果用社区版的就行&#xff0c;在阿里郎上直接获…

2020年,.NET Core起飞在即,最强日志分析ELK还不会?

近期.NET社区迎来一系列利好&#xff1a;1 Build2020&#xff0c;微软发布多款产品赋能.NET开发者&#xff0c;比以往任何时候都更贴近开发者&#xff1b;2 TechEmpower第19轮编程语言框架性能排行榜&#xff0c;Asp.Net Core成为主流Web框架第一&#xff1b;3 Stackoverflow 2…

[JavaWeb-HTTP]request对象和response对象的原理

request对象和response对象的原理 1. request和response对象是由服务器创建的。我们来使用它们2. request对象是来获取请求消息&#xff0c;response对象是来设置响应消息

number two

前言 来到Alibaba工作两天后&#xff0c;迎来我的第一个周末&#xff0c;想着以后参与项目后&#xff0c;可能就不能有心情和精力来分享了&#xff0c;哈哈哈&#xff08;其实主要是杭州下雨&#xff0c;就呆在酒店打发时间喽&#xff09;. 先说下本人的一些情况&#xff1a;…

时间序列神器之争:prophet VS lstm

一、需求背景我们福禄网络致力于为广大用户提供智能化充值服务&#xff0c;包括各类通信充值卡&#xff08;比如移动、联通、电信的话费及流量充值&#xff09;、游戏类充值卡&#xff08;比如王者荣耀、吃鸡类点券、AppleStore充值、Q币、斗鱼币等&#xff09;、生活服务类&am…

maven知识提炼总结

文章目录Maven 功能构建文档生成报告依赖SCMs发布分发邮件列表Maven 特点Maven 功能 Maven 能够帮助开发者完成以下工作&#xff1a; 构建 文档生成 报告 依赖 SCMs 发布 分发 邮件列表 Maven 特点 项目设置遵循统一的规则。任意工程中共享。依赖管理包括自动更新。…

[JavaWeb-HTTP]request对象继承体系结构

request对象继承体系结构&#xff1a; ServletRequest -- 接口| 继承HttpServletRequest -- 接口| 实现org.apache.catalina.connector.RequestFacade 类(tomcat)

docker registry 镜像同步

docker registry 镜像同步Intro之前我们的 docker 镜像是保存在 Azure 的 Container Registry 里的&#xff0c;最近我们自己搭建了一个 docker registry&#xff0c;我们想把之前保存的 Azure 的 Container Registry 的 docker 镜像同步到我们自己的 docker registry 里实现思…

maven POM

文章目录概念&#xff1a;在创建 POM 之前&#xff0c;我们首先需要描述项目组 (groupId), 项目的唯一ID。父&#xff08;Super&#xff09;POMPOM 标签大全详解概念&#xff1a; POM( Project Object Model&#xff0c;项目对象模型 ) 是 Maven 工程的基本工作单元&#xff0…

[数据结构-严蔚敏版]P37定义一个带头结点的线性链表

大家如果发现代码有错误&#xff0c;麻烦评论告知一下!!! 代码我已经发现存在部分错误&#xff0c;等有时间我再进行修正。目前存在错误:mergeList_L函数有问题! 代码如下: #include <iostream> using namespace std;typedef struct LNode {int data;LNode *next; }*L…