第六章:文章管理与 Markdown 支持
博客v1.0系列教程(Csharp)博客 v1.0 系列教程 (C#)
6.1 文章服务
public class ArticleService : IArticleService
{
private readonly IRepository<ArticleModel> _articleRepo;
public async Task<PageResult<ArticleModel>> GetArticlesAsync(
int page, int pageSize, string? category, string? keyword)
{
var query = _articleRepo.AsQueryable()
.WhereIF(!string.IsNullOrEmpty(category),
a => a.Category == category)
.WhereIF(!string.IsNullOrEmpty(keyword),
a => a.Title.Contains(keyword) || a.Content.Contains(keyword))
.OrderBy(a => a.CreateTime, OrderByType.Descending);
var total = await query.CountAsync();
var items = await query.Skip((page - 1) * pageSize)
.Take(pageSize).ToListAsync();
return new PageResult<ArticleModel>(items, total, page, pageSize);
}
}
6.2 Markdown 渲染
使用 HtmlAgilityPack 处理 Markdown 内容:
public class MarkdownHelper
{
public static string RenderToHtml(string markdown)
{
// 使用 Markdown 解析库将 Markdown 转为 HTML
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
return Markdig.Markdown.ToHtml(markdown, pipeline);
}
public static string ExtractAbstract(string markdown, int length = 160)
{
var plainText = Regex.Replace(markdown, @"[#*`\[\]]", "");
return plainText.Length > length
? plainText[..length] + "..."
: plainText;
}
}
6.3 文章端点
public static void MapArticleEndpoints(this WebApplication app)
{
var group = app.MapGroup("/api/article");
// 获取文章列表(分页)
group.MapGet("/", async (IArticleService service,
int page = 1, int pageSize = 10,
string? category = null, string? keyword = null) =>
{
return await service.GetArticlesAsync(page, pageSize, category, keyword);
});
// 获取文章详情
group.MapGet("/{id}", async (IArticleService service, long id) =>
{
var article = await service.GetArticleByIdAsync(id);
return article is null ? Results.NotFound() : Results.Ok(article);
});
// 创建文章
group.MapPost("/", async (IArticleService service,
CreateArticleDto dto) =>
{
var id = await service.CreateArticleAsync(dto);
return Results.Created($"/api/article/{id}", new { Id = id });
}).AddEndpointFilter<AuthorizeFilter>();
// 更新文章
group.MapPut("/", async (IArticleService service,
UpdateArticleDto dto) =>
{
await service.UpdateArticleAsync(dto);
return Results.Ok();
}).AddEndpointFilter<AuthorizeFilter>();
// 删除文章
group.MapDelete("/{id}", async (IArticleService service, long id) =>
{
await service.DeleteArticleAsync(id);
return Results.Ok();
}).AddEndpointFilter<AuthorizeFilter>();
}
6.4 双源文章系统
支持从数据库和 Markdown 文件两种来源加载文章:
public interface IArticleProvider
{
Task<ArticleModel?> GetArticleAsync(long id);
Task<List<ArticleModel>> GetArticlesAsync();
}
public class DatabaseArticleProvider : IArticleProvider { /* ... */ }
public class MarkdownArticleProvider : IArticleProvider
{
private readonly string _articlesDir;
public MarkdownArticleProvider(string articlesDir)
{
_articlesDir = articlesDir;
}
public async Task<List<ArticleModel>> GetArticlesAsync()
{
var articles = new List<ArticleModel>();
var files = Directory.GetFiles(_articlesDir, "*.md",
SearchOption.AllDirectories);
foreach (var file in files)
{
var content = await File.ReadAllTextAsync(file);
var (meta, body) = ParseFrontmatter(content);
articles.Add(new ArticleModel
{
Id = GenerateStableId(file),
Title = meta["title"],
Content = body
});
}
return articles;
}
}
6.5 浏览历史
app.MapPost("/api/article/history", async (
IArticleService service,
HistoryDto dto) =>
{
await service.RecordHistoryAsync(dto.ArticleId);
return Results.Ok();
}).AddEndpointFilter<AuthorizeFilter>();
下一章将实现评论系统与敏感词过滤。
csharpmarkdowncrudarticle