系列文章

前言

前一篇文章把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,可以看到文章列表如下,很简单(简陋),而且全部文章都显示出来了,页面很长,这很明显并不是我们想要的最终效果。

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

分页

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

分页可以自己实现,也可以用第三方组件,我们用的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>

显示效果:

请求参数封装

前面介绍的分页需要在访问页面时传入请求参数,这样我们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

最终效果

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

小结

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

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

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

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

  1. 基于.NetCore开发博客项目 StarBlog - (7) 页面开发之文章详情页面

    系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...

  2. 基于.NetCore开发博客项目 StarBlog - (8) 分类层级结构展示

    系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...

  3. 基于.NetCore开发博客项目 StarBlog - (9) 图片批量导入

    系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...

  4. 基于.NetCore开发博客项目 StarBlog - (10) 图片瀑布流

    系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...

  5. 基于.NetCore开发博客项目 StarBlog - (11) 实现访问统计

    系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...

  6. 基于.NetCore开发博客项目 StarBlog - (12) Razor页面动态编译

    系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...

  7. 基于.NetCore开发博客项目 StarBlog - (13) 加入友情链接功能

    系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...

  8. 基于.NetCore开发博客项目 StarBlog - (14) 实现主题切换功能

    系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...

  9. 基于.NetCore开发博客项目 StarBlog - (15) 生成随机尺寸图片

    系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...

随机推荐

  1. Python学习--Python的了解与安装

    Python简介: Python 是一种解释型.面向对象.动态数据类型的高级程序设计语言. Python 由荷兰人Guido van Rossum 于 1989 年底发明,第一个公开发行版发行于 19 ...

  2. 用纯CSS美化radio和checkbox

    Radio和checkbox需要美化吗?答案是必须的,因为设计风格一直都会变化,原生的样式百年不变肯定满足不了需求. 先看看纯CSS美化过后的radio和checkbox效果:查看. 项目地址:mag ...

  3. 实现自定义的小程序底部tabbar

    背景 诶,当然是为了实现更有温度的代码啦(背后设计师拿着刀对着我) 自带tabbar app.json中配置: tabBar: { backgroundColor: '#fff', borderSty ...

  4. 从0搭建vue后台管理项目到颈椎病康复指南(一)

    网上搜索了很久Vue项目搭建指南,并没有找到写的比较符合心意的,所以打算自己撸一个指南,集合众家之所长(不善于排版,有点逼死强迫症,如果觉得写的有问题,可以留言斧正,觉得写的太差的,可以留言哪里差, ...

  5. CSRF浅析

    概念 CSRF,Cross Site Request Forgery,跨站请求伪造. 为什么跨站的请求需要伪造? 因为浏览器实现了同源策略,这里可以将站和源视为同一个概念. 同源策略 The same ...

  6. 用Python爬取斗鱼网站的一个小案例

    思路解析: 1.我们需要明确爬取数据的目的:为了按热度查看主播的在线观看人数 2.浏览网页源代码,查看我们需要的数据的定位标签 3.在代码中发送一个http请求,获取到网页返回的html(需要注意的是 ...

  7. Tomcat安装(安装版)

    安装Tomcat(安装版) 下载地址https://tomcat.apache.org/ 下载成功,双击进行安装(一路Next). 等待安装结束. 然后打开浏览器输入地址:http://localho ...

  8. python---二维数组的查找

    """ 在一个二维数组中(每个一维数组的长度相同), 每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序. """ # ...

  9. cali1e4a9cee8dc这是什么东西?

    //我们查下k8s node节点,发现很多类似 cali7c620a7a67b 这样的类似网络设备的东西.//这些是什么呢?//k8s集群节点ht10,node网络情况.[root@ht10 cali ...

  10. sqlplus文件查看自带oracle命令的执行过程

    问题描述:看到一篇文章 在$ORACLE_HOME/bin/sqlplus中可以查看到数据库命令的查询语句.可以直接编辑sqlplus文件,查到到我们平时标准系统命令的原脚本,但是自己进行编辑查看却是 ...