基于.NetCore开发博客项目 StarBlog - (6) 页面开发之博客文章列表

系列文章

  • 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客?

  • 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目

  • 基于.NetCore开发博客项目 StarBlog - (3) 模型设计

  • 基于.NetCore开发博客项目 StarBlog - (4) markdown博客批量导入

  • 基于.NetCore开发博客项目 StarBlog - (5) 开始搭建Web项目

  • 基于.NetCore开发博客项目 StarBlog - (6) 页面开发之博客文章列表

  • ...

前言

前一篇文章把Web项目搭起来了,现在开始来写页面~

本文记录博客文章列表的开发,包括参数、分类过滤、分页、搜索、排序等内容。

ORM

本项目的ORM使用FreeSQL,前面「博客批量导入」的文章中有初步涉及到了,不过没有介绍太多,这里再讲一下几个关键的地方。

不同于网上比较常见的EF Core,FreeSQL设计完模型之后不需要进行迁移操作,在开发模式下开启自动结构同步(AutoSyncStructure)就能自动创建、修改数据表。

还有比较方便的一点是FreeSQL自带了简单的仓储模式,不用再自己封装一套,可以减少开发时的代码量~

不过局限性也是有的,不封装仓储层的话,意味着service层代码跟ORM绑定,以后如果切换ORM会带来额外的重构成本。

打开StarBlog.Data项目,我们来写一个扩展方法,新增Extensions目录,在里面新增ConfigureFreeSql.cs

using FreeSql;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;namespace StarBlog.Data.Extensions;public static class ConfigureFreeSql {public static void AddFreeSql(this IServiceCollection services, IConfiguration configuration) {var freeSql = new FreeSqlBuilder().UseConnectionString(DataType.Sqlite, configuration.GetConnectionString("SQLite")).UseAutoSyncStructure(true).Build();services.AddSingleton(freeSql);// 仓储模式支持services.AddFreeRepository();}
}

然后编辑StarBlog.Web项目下的Program.cs,注册一下FreeSQL的服务,用我们刚才写的扩展方法。

using StarBlog.Data.Extensions;builder.Services.AddFreeSql(builder.Configuration);

在要用的地方注入就行了,比如

IBaseRepository<Post> _postRepo;// 获取全部文章
_postRepo.Select.ToList()

就很方便了,开箱即用~

Service

因为我们的后端既要渲染页面,又要做RESTFul接口,所以要把业务逻辑抽象出来放在service层,避免在Controller里重复。

StarBlog.Web项目的Services目录里新增PostService.cs,我们要在这封装跟文章有关的逻辑~

首先依赖注入,把需要用到的服务注入进来

public class PostService {private readonly IBaseRepository<Post> _postRepo;private readonly IBaseRepository<Category> _categoryRepo;public PostService(IBaseRepository<Post> postRepo,IBaseRepository<Category> categoryRepo) {_postRepo = postRepo;_categoryRepo = categoryRepo;}
}

写一个获取全部文章的方法

public List<Post> GetAll() {return _postRepo.Select.ToList();
}

这样就初步搞定了,接下来要来写Controller

Controller

StarBlog.Web项目的Controllers目录下,新增BlogController.cs,用来实现跟博客有关的接口。

注入刚刚写好的 PostService

public class BlogController : Controller {private readonly PostService _postService;public BlogController(PostService postService) {_postService = postService;}
}

写文章列表“接口”(MVC也算接口吧)

public IActionResult List() {return View(_postService.GetAll());
}

View

根据AspNetCore MVC项目的约定,要把网页模板放在Views目录下,按Controller分类

这个文章列表页面,按照约定的路径是:Views/Blog/List.cshtml,创建这个文件

@model List<Post>
@{ViewData["Title"] = "博客列表";
}
<div class="container px-4 py-3">@foreach (var post in Model) {<div class="card mb-3"><div class="card-header">@Model.Category.Name</div><div class="card-body"><h5 class="card-title">@Model.Title</h5><p class="card-text">@Model.Summary</p><a class="btn btn-outline-secondary stretched-link"asp-controller="Blog" asp-action="Post" asp-route-id="@Model.Id">查看全文</a></div></div>}
</div>

这样简单的文章列表就完成了

试试效果

运行项目,打开浏览器,输入地址http://127.0.0.1:5038/Blog/List,可以看到文章列表如下,很简单(简陋),而且全部文章都显示出来了,页面很长,这很明显并不是我们想要的最终效果。

58ef2fe8c6c6212992b523a819c5324c.png
image

不急,接下来慢慢来优化。

分页

首先是页面把全部文章都显示出来的问题,我们需要引入分页功能

分页可以自己实现,也可以用第三方组件,我们用的FreeSQL也支持分页的API,这里我直接掏出之前做项目用过的X.PagedList,它封装了分页取数据和前端的分页部件,比较方便。

直接nuget里安装这两个包就行:

  • X.PagedList

  • X.PagedList.Mvc.Core

使用很简单,X.PagedList组件定义了List类型的扩展方法,直接在ORM读取出来的List上用就行

_postRepo.Select.ToList().ToPagedList(pageNumber, pageSize);

返回类型是IPagedList<T>,除了当前页面的数据,还包含有分页的信息(当前页面、总页面数量、页面大小、总数据量等),可以直接当List用。

然后X.PagedList组件还封装了MVC模板上的HTML组件,使用也很简单:

<nav aria-label="Page navigation example">@Html.PagedListPager(Model.Posts, page => Url.Action(RazorHelper.GetCurrentActionName(ViewContext), new {page, categoryId = Model.CurrentCategoryId}),new PagedListRenderOptions {LiElementClasses = new[] {"page-item"},PageClasses = new[] {"page-link"},UlElementClasses = new[] {"pagination justify-content-center"}})
</nav>

前端我要使用bootstrap的分页组件,所以把bootstrap的class传进去,如果是其他前端组件库的话,只需要传对应的class名称就行。

渲染出来的页面代码是这样的:

<div class="pagination-container"><ul class="pagination justify-content-center"><li class="active page-item"><span class="page-link">1</span></li><li class="page-item"><a class="page-link" href="/Blog/List?page=2&amp;categoryId=0">2</a></li><li class="page-item"><a class="page-link" href="/Blog/List?page=3&amp;categoryId=0">3</a></li><li class="page-item"><a class="page-link" href="/Blog/List?page=4&amp;categoryId=0">4</a></li><li class="page-item"><a class="page-link" href="/Blog/List?page=5&amp;categoryId=0">5</a></li><li class="page-item"><a class="page-link" href="/Blog/List?page=6&amp;categoryId=0">6</a></li><li class="page-item"><a class="page-link" href="/Blog/List?page=7&amp;categoryId=0">7</a></li><li class="page-item"><a class="page-link" href="/Blog/List?page=8&amp;categoryId=0">8</a></li><li class="page-item"><a class="page-link" href="/Blog/List?page=9&amp;categoryId=0">9</a></li><li class="page-item"><a class="page-link" href="/Blog/List?page=10&amp;categoryId=0">10</a></li><li class="PagedList-ellipses page-item"><a class="PagedList-skipToNext page-link" href="/Blog/List?page=11&amp;categoryId=0" rel="next">…</a></li><li class="PagedList-skipToNext page-item"><a class="page-link" href="/Blog/List?page=2&amp;categoryId=0" rel="next">&gt;</a></li><li class="PagedList-skipToLast page-item"><a class="page-link" href="/Blog/List?page=64&amp;categoryId=0">&gt;&gt;</a></li></ul>
</div>

显示效果:

51babda73c16f3a2cb03e31f7fa21c7b.png
image

请求参数封装

前面介绍的分页需要在访问页面时传入请求参数,这样我们Controller的Action方法就需要加上pageNumberpageSize这两个参数,后面还要加文章分类筛选和搜索排序什么的,这样参数太多了,全都写在Action方法的参数里不优雅,好在AspNetCore提供了class作为参数的写法。

StarBlog.Web/ViewModels目录下新建QueryFilters目录,用来存不同接口的请求参数。

有些参数属于不同接口都有的,合理利用面向对象,先写个基类:QueryParameters.cs

public class QueryParameters {/// <summary>/// 最大页面条目/// </summary>public const int MaxPageSize = 50;private int _pageSize = 10;/// <summary>/// 页面大小/// </summary>public int PageSize {get => _pageSize;set => _pageSize = (value > MaxPageSize) ? MaxPageSize : value;}/// <summary>/// 当前页码/// </summary>public int Page { get; set; } = 1;/// <summary>/// 搜索关键词/// </summary>public string? Search { get; set; }/// <summary>/// 排序字段/// </summary>public string? SortBy { get; set; }
}

文章请求参数在此基础上还增加了状态、分类等,从上面这个基类派生一个新类就好:PostQueryParameters.cs

public class PostQueryParameters : QueryParameters {/// <summary>/// 仅请求已发布文章/// </summary>public bool OnlyPublished { get; set; } = false;/// <summary>/// 文章状态/// </summary>public string? Status { get; set; }/// <summary>/// 分类ID/// </summary>public int CategoryId { get; set; } = 0;/// <summary>/// 排序字段/// </summary>public new string? SortBy { get; set; } = "-LastUpdateTime";
}

service改造

我们的核心逻辑都是在service中实现的,请求参数肯定也要传入给service来使用。

依然是先前的GetPagedList方法,给其加上各种筛选条件之后是这样:

public IPagedList<Post> GetPagedList(PostQueryParameters param) {var querySet = _postRepo.Select;// 是否发布if (param.OnlyPublished) {querySet = _postRepo.Select.Where(a => a.IsPublish);}// 状态过滤if (!string.IsNullOrEmpty(param.Status)) {querySet = querySet.Where(a => a.Status == param.Status);}// 分类过滤if (param.CategoryId != 0) {querySet = querySet.Where(a => a.CategoryId == param.CategoryId);}// 关键词过滤if (!string.IsNullOrEmpty(param.Search)) {querySet = querySet.Where(a => a.Title.Contains(param.Search));}// 排序if (!string.IsNullOrEmpty(param.SortBy)) {// 是否升序var isAscending = !param.SortBy.StartsWith("-");var orderByProperty = param.SortBy.Trim('-');querySet = querySet.OrderByPropertyName(orderByProperty, isAscending);}return querySet.Include(a => a.Category).ToList().ToPagedList(param.Page, param.PageSize);
}

根据传入的参数,可以实现状态过滤、分类过滤、关键词过滤、排序和分页功能。

ViewModel

一个MVC页面只能指定一个Model,虽然可以用弱类型的ViewBag或者ViewData,但是弱类型不好维护,我们来定义一个ViewModel给页面使用。

先确定要在文章列表页面显示哪些内容,例如显示当前选择的文章分类、所有分类列表。

StarBlog.WebViewModels目录下,新建BlogListViewModel.cs,根据我们要展示的内容,定义模型如下

using StarBlog.Data.Models;
using X.PagedList;namespace StarBlog.Web.ViewModels; public class BlogListViewModel {public Category CurrentCategory { get; set; }public int CurrentCategoryId { get; set; }public IPagedList<Post> Posts { get; set; }public List<Category> Categories { get; set; }
}

搞定。

controller改造

经过前面的铺垫,controller这里就简单了,不过还有要注意的地方,本项目是包含后端渲染和RESTFul接口两部分的,因此controller要写两个,service只要一个就行。

RESTFul接口我后面再具体介绍,可以先看看改造后的RESTFul接口controller的代码:

[AllowAnonymous]
[HttpGet]
public ApiResponsePaged<Post> GetList([FromQuery] PostQueryParameters param) {var pagedList = _postService.GetPagedList(param);return new ApiResponsePaged<Post> {Message = "Get posts list",Data = pagedList.ToList(),Pagination = pagedList.ToPaginationMetadata()};
}

代码很简单,这个获取文章列表的接口,就单纯只需要给分页和过滤后的列表数据就行。

而MVC的接口就没这么简单,要显示在页面上的东西,全都要在后端做渲染,包括我们在前面说的要显示当前分类、所有分类列表。

代码长这样:

public IActionResult List(int categoryId = 0, int page = 1, int pageSize = 5) {var categories = _categoryRepo.Where(a => a.Visible).IncludeMany(a => a.Posts).ToList();categories.Insert(0, new Category { Id = 0, Name = "All", Posts = _postRepo.Select.ToList() });return View(new BlogListViewModel {CurrentCategory = categoryId == 0 ? categories[0] : categories.First(a => a.Id == categoryId),CurrentCategoryId = categoryId,Categories = categories,Posts = _postService.GetPagedList(new PostQueryParameters {CategoryId = categoryId,Page = page,PageSize = pageSize,OnlyPublished = true})});
}

传入参数只需要三个:

  • 分类ID

  • 当前页面

  • 页面大小

这个接口要做的事比较多

  • 获取所有分类

  • 判断当前分类

  • 获取文章列表

最终返回我们前面定义的BlogListViewModel

然后在页面模板里就可以用了。

View改造

第一件事把model换成BlogListViewModel

然后就是根据ViewModel里的数据进行页面渲染,都是Bootstrap提供的页面组件,代码比较长我就不贴了,页面模板的完整代码可以在这看到:https://github.com/Deali-Axy/StarBlog/blob/master/StarBlog.Web/Views/Blog/List.cshtml

最终效果

截了个长图,最终的页面效果就是这样了~

b5c004cc0ba7f4b5811682489e83269e.png
image

小结

如果你看到了这里,说明你是个有耐心的人 O(∩_∩)O哈哈~,同时对本项目是比较感兴趣的,先感谢大家的支持~

本文一不小心就写得比较长了,本来是想以那种每篇文章比较短的形式做一个连载,这样读起来不会有太大的压力,没想到稍微一展开讲就涉及到很多内容,接下来的文章我得优化优化~

最近一段时间,公众号后台、微信都有收到朋友的催更,或者是抱怨我更新得太慢,实在是抱歉,最近被工作上的事情搞得有点晕头转向的,下班回家后晚上就只想看会书或者玩一下游戏放松,懈怠了,看到有这么多大佬在关注我的项目,顿时又充满动力了!冲冲冲,接下来争取每两天更新一篇,欢迎继续关注1ef1de0b2ce7d9449dfb20a1a9c10aa3.png~

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

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

相关文章

时间序列matlab代码,MATLAB在时间序列建模预测及程序代码.pdf

第二十四章 时间序列模型时间序列是按时间顺序排列的、随时间变化且相互关联的数据序列。分析时间序列的方法构成数据分析的一个重要领域&#xff0c;即时间序列分析。时间序列根据所研究的依据不同&#xff0c;可有不同的分类。1&#xff0e;按所研究的对象的多少分&#xff0…

Android之安卓8.0版本以上手机开启热点提示Caller already has an active LocalOnlyHotspot request

1 问题 在Android8.0手机开启了热点,关闭页面再次打开热点, /*** 开启Android8.0版本手机以上的热点,热点名字和密码都是随机的。*/fun initWifiApGreaterThanEight(context: Context) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {var wifiManager:WifiMana…

JavaScript基础和js概括

js内容概括&#xff1a; Html 结构化 CSS 样式 JavaScript 行为交互 01.JavaScript基础 02.JavaScript操作BOM对象 03.JavaScript操作DOM对象 ***** 04.JavaScript的面向对象 -------------------------------------- 05.jQuery (js) .css .js 06.jQuery选择器 …

《看聊天记录都学不会C语言?太菜了吧》(12)循环有多容易?你看一眼就怀...

若是大一学子或者是真心想学习刚入门的小伙伴可以私聊我&#xff0c;若你是真心学习可以送你书籍&#xff0c;指导你学习&#xff0c;给予你目标方向的学习路线&#xff0c;无套路&#xff0c;博客为证。 本系列文章将会以通俗易懂的对话方式进行教学&#xff0c;对话中将涵盖…

***ECharts图表入门和最佳实践

ECharts数据图表系统&#xff1f; 5分钟上手&#xff01; 【ECharts简介】 ECharts开源来自百度商业前端数据可视化团队&#xff0c;基于html5 Canvas&#xff0c;是一个纯Javascript图表库&#xff0c;提供直观&#xff0c;生动&#xff0c;可交互&#xff0c;可个性化定制的数…

【经典回放】多种语言系列数据结构线性表之一:顺序表

一、实验任务描述 建立线性表的ADT后,编程完成: 1 用C语言完成一个顺序表结构; 2 为顺序表完成追加、删除、插入、查找的功能; 3 建立一个链表的结点; 4 完成链表的追加、删除、插入功能; 5* 用C#完成一个结点的类; 6* C#完成链表的追加、删除、插入功能的类; 7* C#编写…

原型继承+原型链 + 对象继承发展

一、原型继承&#xff1a; &#xff11;、说起原型继承&#xff0c;就要先由构造函数创造对象说起&#xff0c;首先了解构造函数内部基本原理&#xff1a; &#xff08;&#xff11;&#xff09;.在函数体最前面隐式的加上this {} &#xff08;&#xff12;&#xff09;.执行 …

Kotlin之?和!!最简单的理解

一、? 1&#xff09;、?在声明对象时&#xff0c;把它跟在类名后面&#xff0c;表示这个类允许为null var list: ArrayList<String>? null 2&#xff09;、调用对象时&#xff0c;把它跟在对象后面&#xff0c;表示如果为null程序就会视而不见&#xff0c;比如我们…

WPF|一个比较简单带点设计的登录界面

阅读目录效果展示准备简单说明 源码结尾&#xff08;视频及源码仓库&#xff09;1. 效果展示欣赏效果&#xff1a;2. 准备创建一个WPF工程&#xff0c;比如站长使用 .NET 7[1] 创建名为 Login5 的WPF项目。找一张图片做为装饰&#xff0c;放登录表单左侧&#xff1a;添加Nuget…

Android插件基础之类加载器学习

记录学习java 加载器学习所获心得&#xff0c;逐步记录了解java加载器的过程。为了知悉android 插件化的实现原理&#xff0c;从而需要从头了解android加载apk&#xff0c;以及基础的java类加载的加载过程情况&#xff0c;为方便记录和记忆&#xff0c;故此将学习了解的过程记录…

php多个文件上传代码,PHP单文件上传类或多文件上传类源码

以下为引用的内容&#xff1a;php文件:代码://如果收到表单传来的参数&#xff0c;则进行上传处理&#xff0c;否则显示表单if(isset($_FILES[uploadinput])){//建目录函数&#xff0c;其中参数$directoryName最后没有"/"&#xff0c;//要是有的话&#xff0c;以/打散…

《看聊天记录都学不会C语言?太菜了吧》(13)(9*9 乘法表)寻找电脑中的盲盒彩蛋

若是大一学子或者是真心想学习刚入门的小伙伴可以私聊我&#xff0c;若你是真心学习可以送你书籍&#xff0c;指导你学习&#xff0c;给予你目标方向的学习路线&#xff0c;无套路&#xff0c;博客为证。 本系列文章将会以通俗易懂的对话方式进行教学&#xff0c;对话中将涵盖…

Fragment使用--文章集锦

android使用Fragment实现底部菜单使用show()和hide()来切换以保持Fragment状态Android Fragment 真正的完全解析&#xff08;上&#xff09;Android Fragment实践(一)纠正对Fragment Transaction BackStack的误解多个Fragment 切换时不重新实例化Fragment详解之四——管理Fragm…

Android之在在EditText的xml里面配置了相关属性依然没有显示光标问题

1 问题 在EditText的xml里面配置了 android:cursorVisible"true" android:focusable"true" android:focusableInTouchMode"true" 依然没有光标显示 2 解决办法 直接在代码层控制&#xff0c;平且拉起键盘 fun showSoftInputFromWindow(activ…

【经典回放】多种语言系列数据结构线性表之二:链表

目录 1 链表结构设计 2 简单的链表测试 2 链表的组织和ADT设计 3 初始化有头结点链表 4 有头结点链表中追加一个结点(一行数据)

使用 Postman 实现 API 自动化测试

1背景介绍 相信大部分开发人员和测试人员对 postman 都十分熟悉&#xff0c;对于开发人员和测试人员而言&#xff0c;使用 postman 来编写和保存测试用例会是一种比较方便和熟悉的方式。但 postman 本身是一个图形化软件&#xff0c;相对较难或较麻烦&#xff08;如使用 …

php json -gt;访问,【转】Php+ajax+jsonp解决ajax跨域问题

首先&#xff1a;jsonp是json用来跨域的一个东西。原理是通过script标签的跨域特性来绕过同源策略。发送端&#xff1a;$.ajax({type : "post",url : "ajax.php",dataType : "jsonp",jsonp: "callback",//传递给请求处理程序或页面的&…

《看聊天记录都学不会Python到游戏实战?太菜了吧》(1)加载Python神器!亮剑!

本系列文章将会以通俗易懂的对话方式进行教学&#xff0c;对话中将涵盖了新手在学习中的一般问题。此系列将会持续更新&#xff0c;包括别的语言以及实战都将使用对话的方式进行教学&#xff0c;基础编程语言教学适用于零基础小白&#xff0c;之后实战课程也将会逐步更新。 若…

little tricks(持续更新)【python】

python中的list有reverse方法&#xff0c;但是字符串却没有&#xff0c;怎么办呢&#xff1f; csdn博主有列了几个方法&#xff0c;我觉得第一个最简单直接&#xff0c;str[::-1]搞定。 当然也有一种怪异的方法&#xff0c;我把字符串转为序列reverse一下&#xff0c;再转回来不…

Codeigniter 3 拓展HMVC

2019独角兽企业重金招聘Python工程师标准>>> 在Codeiniter&#xff08;以下统称CI&#xff09; 2.X版本中&#xff0c;我们就通过拓展核心类库实现了HMVC&#xff0c;但是同样的代码&#xff0c;拿到CI 3中&#xff0c;就很有可能不好用了。 ###拓展核心类库方式 官…