perf: handle content processing in BlogService

This commit is contained in:
Oliver Booth 2023-08-08 12:46:24 +01:00
parent 983e636635
commit aaafcaf760
Signed by: oliverbooth
GPG Key ID: 725DB725A0D9EE61
5 changed files with 55 additions and 51 deletions

View File

@ -1,6 +1,8 @@
@page "/blog/{year:int}/{month:int}/{day:int}/{slug}" @page "/blog/{year:int}/{month:int}/{day:int}/{slug}"
@using Humanizer @using Humanizer
@using OliverBooth.Services
@model OliverBooth.Pages.Blog.Article @model OliverBooth.Pages.Blog.Article
@inject BlogService BlogService
@if (Model.Post is not { } post) @if (Model.Post is not { } post)
{ {
@ -39,7 +41,7 @@
</p> </p>
<article> <article>
@Html.Raw(Model.SanitizeContent(post.Body)) @Html.Raw(BlogService.GetContent(post))
</article> </article>
<hr> <hr>

View File

@ -1,5 +1,4 @@
using Markdig; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
using OliverBooth.Data.Blog; using OliverBooth.Data.Blog;
using OliverBooth.Services; using OliverBooth.Services;
@ -12,17 +11,14 @@ namespace OliverBooth.Pages.Blog;
public class Article : PageModel public class Article : PageModel
{ {
private readonly BlogService _blogService; private readonly BlogService _blogService;
private readonly MarkdownPipeline _markdownPipeline;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Article" /> class. /// Initializes a new instance of the <see cref="Article" /> class.
/// </summary> /// </summary>
/// <param name="blogService">The <see cref="BlogService" />.</param> /// <param name="blogService">The <see cref="BlogService" />.</param>
/// <param name="markdownPipeline">The <see cref="MarkdownPipeline" />.</param> public Article(BlogService blogService)
public Article(BlogService blogService, MarkdownPipeline markdownPipeline)
{ {
_blogService = blogService; _blogService = blogService;
_markdownPipeline = markdownPipeline;
} }
/// <summary> /// <summary>
@ -45,23 +41,6 @@ public class Article : PageModel
/// <value>The requested blog post.</value> /// <value>The requested blog post.</value>
public BlogPost Post { get; private set; } = null!; public BlogPost Post { get; private set; } = null!;
/// <summary>
/// Sanitizes the content of the blog post.
/// </summary>
/// <param name="content">The content of the blog post.</param>
/// <returns>The sanitized content of the blog post.</returns>
public string SanitizeContent(string content)
{
content = content.Replace("<!--more-->", string.Empty);
while (content.Contains("\n\n"))
{
content = content.Replace("\n\n", "\n");
}
return Markdown.ToHtml(content.Trim(), _markdownPipeline);
}
public IActionResult OnGet(int year, int month, int day, string slug) public IActionResult OnGet(int year, int month, int day, string slug)
{ {
if (!_blogService.TryGetBlogPost(year, month, day, slug, out BlogPost? post)) if (!_blogService.TryGetBlogPost(year, month, day, slug, out BlogPost? post))

View File

@ -48,7 +48,7 @@
} }
</p> </p>
<p>@Html.Raw(Model.SanitizeContent(Model.TrimContent(post.Body, out bool trimmed)))</p> <p>@Html.Raw(BlogService.GetExcerpt(post, out bool trimmed))</p>
<article> <article>
@if (trimmed) @if (trimmed)

View File

@ -1,6 +1,4 @@
using System.Diagnostics; using Microsoft.AspNetCore.Mvc;
using Humanizer;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
using OliverBooth.Data.Blog; using OliverBooth.Data.Blog;
using OliverBooth.Services; using OliverBooth.Services;
@ -16,26 +14,6 @@ public class Index : PageModel
_blogService = blogService; _blogService = blogService;
} }
public string SanitizeContent(string content)
{
content = content.Replace("<!--more-->", string.Empty);
while (content.Contains("\n\n"))
{
content = content.Replace("\n\n", "\n");
}
return Markdig.Markdown.ToHtml(content.Trim());
}
public string TrimContent(string content, out bool trimmed)
{
ReadOnlySpan<char> span = content.AsSpan();
int moreIndex = span.IndexOf("<!--more-->", StringComparison.Ordinal);
trimmed = moreIndex != -1 || span.Length > 256;
return moreIndex != -1 ? span[..moreIndex].Trim().ToString() : content.Truncate(256);
}
public IActionResult OnGet([FromQuery(Name = "pid")] int? postId = null, public IActionResult OnGet([FromQuery(Name = "pid")] int? postId = null,
[FromQuery(Name = "p")] int? wpPostId = null) [FromQuery(Name = "p")] int? wpPostId = null)
{ {

View File

@ -1,4 +1,6 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Humanizer;
using Markdig;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using OliverBooth.Data; using OliverBooth.Data;
using OliverBooth.Data.Blog; using OliverBooth.Data.Blog;
@ -8,14 +10,17 @@ namespace OliverBooth.Services;
public sealed class BlogService public sealed class BlogService
{ {
private readonly IDbContextFactory<BlogContext> _dbContextFactory; private readonly IDbContextFactory<BlogContext> _dbContextFactory;
private readonly MarkdownPipeline _markdownPipeline;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BlogService" /> class. /// Initializes a new instance of the <see cref="BlogService" /> class.
/// </summary> /// </summary>
/// <param name="dbContextFactory">The <see cref="IDbContextFactory{TContext}" />.</param> /// <param name="dbContextFactory">The <see cref="IDbContextFactory{TContext}" />.</param>
public BlogService(IDbContextFactory<BlogContext> dbContextFactory) /// <param name="markdownPipeline">The <see cref="MarkdownPipeline" />.</param>
public BlogService(IDbContextFactory<BlogContext> dbContextFactory, MarkdownPipeline markdownPipeline)
{ {
_dbContextFactory = dbContextFactory; _dbContextFactory = dbContextFactory;
_markdownPipeline = markdownPipeline;
} }
/// <summary> /// <summary>
@ -31,6 +36,34 @@ public sealed class BlogService
} }
} }
/// <summary>
/// Gets the processed content of a blog post.
/// </summary>
/// <param name="post">The blog post.</param>
/// <returns>The processed content of the blog post.</returns>
public string GetContent(BlogPost post)
{
return ProcessContent(post.Body);
}
/// <summary>
/// Gets the processed excerpt of a blog post.
/// </summary>
/// <param name="post">The blog post.</param>
/// <param name="trimmed">
/// When this method returns, contains <see langword="true" /> if the content was trimmed; otherwise,
/// <see langword="false" />.
/// </param>
/// <returns>The processed excerpt of the blog post.</returns>
public string GetExcerpt(BlogPost post, out bool trimmed)
{
ReadOnlySpan<char> span = post.Body.AsSpan();
int moreIndex = span.IndexOf("<!--more-->", StringComparison.Ordinal);
trimmed = moreIndex != -1 || span.Length > 256;
string result = moreIndex != -1 ? span[..moreIndex].Trim().ToString() : post.Body.Truncate(256);
return ProcessContent(result);
}
/// <summary> /// <summary>
/// Attempts to find the author of a blog post. /// Attempts to find the author of a blog post.
/// </summary> /// </summary>
@ -106,4 +139,16 @@ public sealed class BlogService
post = context.BlogPosts.FirstOrDefault(p => p.WordPressId == postId); post = context.BlogPosts.FirstOrDefault(p => p.WordPressId == postId);
return post is not null; return post is not null;
} }
private string ProcessContent(string content)
{
content = content.Replace("<!--more-->", string.Empty);
while (content.Contains("\n\n"))
{
content = content.Replace("\n\n", "\n");
}
return Markdown.ToHtml(content.Trim(), _markdownPipeline);
}
} }