基于 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实战系列(二)


上一篇完成了博客的主题切换,菜单和二维码的显示与隐藏功能,本篇继续完成分页查询文章列表的数据展示。

添加页面

现在点击页面上的链接,都会提示错误消息,因为没有找到对应的路由地址。先在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">-&nbsp;Apps&nbsp;-</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>

在里面添加了一个友情链接的入口,和一个 腾讯兔小巢 ( https://support.qq.com/products/75616 ) 的链接,欢迎大家吐槽留言噢。

现在可以运行一下看看,点击所有的链接都不会提示错误,只要路由匹配正确就会出现加载中的圈圈了。

文章列表

在做文章列表的数据绑定的时候遇到了大坑,有前端开发经验的都知道,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

细心的可以发现,当我点击头部组件的Postsa 标签菜单时候,页面没有发生变化,只是路由改变了。

思来想去,我决定使用NavigationManager这个URI和导航状态帮助程序来解决,当点击头部的Postsa 标签菜单直接刷新页面得了。

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>

总算是搞定,完成了分页查询文章列表的数据绑定,今天就到这里吧,未完待续...

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

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

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

相关文章

计蒜客 逃生+动态规划

题目&#xff1a; 蒜头君在玩一款逃生的游戏。在一个 nmn \times mnm 的矩形地图上&#xff0c;蒜头位于其中一个点。地图上每个格子有加血的药剂&#xff0c;和掉血的火焰&#xff0c;药剂的药效不同&#xff0c;火焰的大小也不同&#xff0c;每个格子上有一个数字&#xff0…

[XML-Jsoup]Jsoup_对象的使用(Jsoup工具类,Document,Elements,Element,Node)

对象的使用&#xff1a; 1. Jsoup&#xff1a;工具类&#xff0c;可以解析html或xml文档&#xff0c;返回Document* parse&#xff1a;解析html或xml文档&#xff0c;返回Document* parse​(File in, String charsetName)&#xff1a;解析xml或html文件的。* parse​(String ht…

别了,Docker Swarm !你好,K8s !

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

01背包+概率问题 计蒜客 offer

题目&#xff1a; 蒜头君很早就想出国&#xff0c;现在他已经考完了所有需要的考试&#xff0c;准备了所有要准备的材料&#xff0c;于是&#xff0c;便需要去申请学校了。要申请国外的任何大学&#xff0c;你都要交纳一定的申请费用&#xff0c;这可是很惊人的。蒜头君没有多…

[JavaWeb-XML]XML_解析_解析方式

解析&#xff1a;操作xml文档&#xff0c;将文档中的数据读取到内存中 * 操作xml文档1. 解析(读取)&#xff1a;将文档中的数据读取到内存中2. 写入&#xff1a;将内存中的数据保存到xml文档中。持久化的存储* 解析xml的方式&#xff1a;1. DOM&#xff1a;将标记语言文档一次性…

【翻译】.NET 5 Preview5发布

今天&#xff0c;发布了.NET 5.0 Preview5。主要对它进行了一小部分新功能和性能的改进。.NET 5.0 Preview 4包含了一些计划和.NET 5.0要交付的内容。现在&#xff0c;大多数的功能都已经包含在里面&#xff0c;但是有许多功能还未到最终状态。预计这个版本在Preview 7中完善。…

构造前缀贪心+ 计蒜客 子矩阵求和

题目&#xff1a; 给出一个 nn 行 mm 列的矩阵&#xff0c;矩阵的每个位置有一个非负整数 a[i][j]&#xff0c;有 qq 次询问&#xff0c;每次询问求一个左上角为 (a,b)&#xff0c;右下角为 (c,d) 的子矩阵的所有数之和。 输入格式 第一行两个整数 n,m&#xff0c;表示矩阵的…

[JavaWeb-XML]约束(DTD,Schema)

DTD&#xff1a; * 引入dtd文档到xml文档中* 内部dtd&#xff1a;将约束规则定义在xml文档中* 外部dtd&#xff1a;将约束的规则定义在外部的dtd文件中* 本地&#xff1a;<!DOCTYPE 根标签名 SYSTEM "dtd文件的位置">* 网络&#xff1a;<!DOCTYPE 根标签名 …

[跨平台系列三Docker篇]:ASP.NET Core应用

如果你是老张的忠实读者的话&#xff0c;如果是从博客园就开始看我的文章的话&#xff0c;如果后期也一直看我公众号的话&#xff0c;应该就知道其实我一直在根据一条无形的教学线路来讲解的&#xff0c;&#xff0c;如果你真的是想好好学的话&#xff0c;请好好看看我之前的文…

2020牛客国庆集训派对day2 F题 Java大数处理

题目&#xff1a; 链接&#xff1a;https://ac.nowcoder.com/acm/contest/16913/F 来源&#xff1a;牛客网 The following code snippet calculates the sum of the areas of all the sub rectangular grid in a rectangular grid from (0,0) to (N,N)\ .(N,N) . Find an eff…

[JavaWeb-XML]XML_快捷查询方式(selector选择器,XPath)

快捷查询方式&#xff1a; 1. selector:选择器* 使用的方法&#xff1a;Elements select​(String cssQuery)* 语法&#xff1a;参考Selector类中定义的语法2. XPath&#xff1a;XPath即为XML路径语言&#xff0c;它是一种用来确定XML&#xff08;标准通用标记语言的子集&#…

[壹刊]Azure AD(四)知识补充-服务主体

一&#xff0c;引言又到了新的一周了&#xff0c;也到了我新的分享的时间了&#xff0c;还记得上一周立得Flag&#xff0c;其中 “保证每周输出一篇文章” &#xff0c;让我特别“在意”&#xff08;这里用词不太恰当&#xff09;。主要是我的一个大学舍友&#xff0c;他突然问…

前缀和优化+计蒜客 泡咖啡

题目: 蒜头君想泡咖啡喝。现在他得到了 nn 份菜谱&#xff0c;每个菜谱上都给了两个数l,rl,rl,r代表温度区间在[l,r][l,r][l,r] 度泡出来的咖啡会很好喝。 可是菜谱太多了&#xff0c;蒜头君一度感到疑惑。经过思考之后他得出了一个结论&#xff1a;某个温度只要有 k 份或者更…

[JavaWeb-Servlet]Servlet_执行原理

执行原理&#xff1a; 1. 当服务器接受到客户端浏览器的请求后&#xff0c;会解析请求URL路径&#xff0c;获取访问的Servlet的资源路径2. 查找web.xml文件&#xff0c;是否有对应的<url-pattern>标签体内容。3. 如果有&#xff0c;则在找到对应的<servlet-class>全…

分享我在前后端分离项目中Gitlab-CI的经验

之前我分享了为ASP.NET Core后端搭建Gitlab-CI/CD实践&#xff0c;今天继续聊一聊为前后端分离搭建Gitlab-CI的额外经验。BeforeGitlab-ci是Gitlab提供的CI/CD特性&#xff0c;结合Gitlab简单友好的配置界面&#xff0c;能愉悦的在Gitlab界面查看管道执行流程&#xff0c;并自然…

最短路(遍历边)计蒜客—DD坐地铁

题目&#xff1a; C 城有 n 个站点&#xff0c; m 条双向地铁&#xff0c;每条地铁有一个 companyicompany_icompanyi​表示它的公司&#xff0c;如果连续乘坐同一家公司的地铁只要花 1元钱就好。DD现在想出门找萨摩耶玩&#xff0c;但是 DD 是贫困人口&#xff0c; 她想知道最…

[JavaWeb-Servlet]Servlet中的生命周期方法(init,service,destroy)

Servlet中的生命周期方法&#xff1a; Override public void init(ServletConfig servletConfig) throws ServletException {}1. 被创建&#xff1a;执行init方法&#xff0c;只执行一次* Servlet什么时候被创建&#xff1f;* 默认情况下&#xff0c;第一次被访问时&#xff0…

lin-cms-dotnetcore.是如何方法级别的权限控制(API级别)的

方法级别的权限控制&#xff08;API级别&#xff09;Lin的定位在于实现一整套 CMS的解决方案&#xff0c;它是一个设计方案&#xff0c;提供了不同的后端&#xff0c;不同的前端&#xff0c;而且也支持不同的数据库目前官方团队维护 lin-cms-vue,lin-cms-spring-boot,lin-cms-k…

线段树维护区间最大值+第 45 届(ICPC)亚洲区域赛(昆明)L题Simone and Graph Coloring

题意&#xff1a; 给你n个数的序列&#xff0c;当满足i<ji<ji<j andandand ai>aja_i>a_jai​>aj​时&#xff0c;这两个点之间有一条边&#xff0c;现在对点染色&#xff0c;要求每个点相邻的点颜色不同&#xff0c;问如何染色使得不同颜色数量最小。 题目…

[JavaWeb-Servlet]Servlet3.0注解配置

Servlet3.0&#xff1a; * 好处&#xff1a;* 支持注解配置。可以不需要web.xml了。* 步骤&#xff1a;1. 创建JavaEE项目&#xff0c;选择Servlet的版本3.0以上&#xff0c;可以不创建web.xml2. 定义一个类&#xff0c;实现Servlet接口3. 复写方法4. 在类上使用WebServlet注解…