基于 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,一经查实,立即删除!

相关文章

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

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

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

文章目录设计模式大作业软 件 设 计 模 式 任 务 书设计要求&#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;收到了业界的一致好评。数据科学与工…

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对象是来设置响应消息

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

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

docker registry 镜像同步

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

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

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

.NET Core加解密实战系列之——RSA非对称加密算法

简介加解密现状&#xff0c;编写此项目的背景&#xff1a;需要考虑系统环境兼容性问题&#xff08;Linux、Windows&#xff09;语言互通问题&#xff08;如C#、Java&#xff09;网上资料版本不一、不全面.NET官方库密码算法提供不全面&#xff0c;很难针对其他语言&#xff08;…

[数据结构-严蔚敏版]P42多项式Polynomial的实现

大家如果发现代码有错误&#xff0c;麻烦评论告知一下!!! 下面的代码多项式相乘的算法实现并不是书上那种&#xff0c;书上那种我实在是看不懂&#xff0c;所以用了自己的方法写了相乘的算法&#xff0c;就是模拟手算过程&#xff0c;一个一个相乘。 代码如下: #include <…

number five

一、外单履行域业务熟悉 申请了各种权限 看了部分三号项目 熟悉测试流程&#xff0c;例如履行的一些工具&#xff0c;只是熟悉了平台&#xff0c;因为部分权限未申请&#xff0c;没有操作过测试。 对于三号项目概念还没成体系&#xff0c;但了解了相关测试任务。 中间件hsf…

.Net Core实战之基于角色的访问控制的设计

前言上个月&#xff0c;我写了两篇微服务的文章&#xff1a;《.Net微服务实战之技术架构分层篇》与《.Net微服务实战之技术选型篇》&#xff0c;微服务系列原有三篇&#xff0c;当我憋第三篇的内容时候一直没有灵感&#xff0c;因此先打算放一放。本篇文章与源码原本打算实在去…

[数据结构-严蔚敏版]P46栈的顺序存储表示

大家如果发现代码有错误&#xff0c;麻烦评论告知一下!!! 代码如下: #include <iostream> using namespace std;const int STACK_INIT_SIZE 100; const int STACKINCREMENT 10;typedef int ElemType;typedef struct {ElemType *base;ElemType *top;int stacksize; }S…

ProcessOn使用

文章目录1. 拥有一个账号;2.思维导图&#xff1a;快速添加主题&#xff1a;移动主题位置和排序用格式刷来复制主题样式善用右键菜单快捷键后续使用时再补充 ^ ^1. 拥有一个账号; 2.思维导图&#xff1a; 快速添加主题&#xff1a; 移动主题位置和排序 用格式刷来复制主题样式…

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

系列文章使用 abp cli 搭建项目给项目瘦身&#xff0c;让它跑起来完善与美化&#xff0c;Swagger登场数据访问和代码优先自定义仓储之增删改查统一规范API&#xff0c;包装返回模型再说Swagger&#xff0c;分组、描述、小绿锁接入GitHub&#xff0c;用JWT保护你的API异常处理和…

[数据结构-严蔚敏版]P48栈的链式表示

代码如下: #include <iostream> using namespace std;typedef int ElemType;typedef struct SNode {ElemType data;SNode *next; }SNode;typedef struct {SNode *top; }SqStack;bool initStack(SqStack &s) {s.top nullptr;return true; }bool destroyStack(SqStac…

还有多少人不会用K8s?.NET高级高薪岗,开始要求了!

毫无疑问&#xff0c;Kubernetes已经成为容器编排事实标准。除了已经拥抱Kubernetes的Google、BAT、京东、奇虎360等巨头大厂外&#xff0c;更多的企业也都在向Kubernetes迁移。容器技术大势所趋&#xff0c;是互联网企业目前急需的技术人才之一&#xff0c;已成为运维工程师、…

[数据结构-严蔚敏版]P61ADT Queue的表示与实现(单链队列-队列的链式存储结构)

代码如下: #include <iostream> using namespace std;typedef int ElemType;typedef struct QNode {ElemType data;QNode *next; }QNode ,*QueuePtr;typedef struct {QueuePtr front;QueuePtr rear; }LinkQueue;bool initQueue(LinkQueue &q) {q.front q.rear new…