diff --git a/OliverBooth.Blog/Data/BlogContext.cs b/OliverBooth.Blog/Data/BlogContext.cs
index cb3ea4e..54512ff 100644
--- a/OliverBooth.Blog/Data/BlogContext.cs
+++ b/OliverBooth.Blog/Data/BlogContext.cs
@@ -25,6 +25,12 @@ internal sealed class BlogContext : DbContext
/// The collection of blog posts.
public DbSet BlogPosts { get; private set; } = null!;
+ ///
+ /// Gets the collection of templates in the database.
+ ///
+ /// The collection of templates.
+ public DbSet Templates { get; private set; } = null!;
+
///
/// Gets the collection of users in the database.
///
@@ -43,6 +49,7 @@ internal sealed class BlogContext : DbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new BlogPostConfiguration());
+ modelBuilder.ApplyConfiguration(new TemplateConfiguration());
modelBuilder.ApplyConfiguration(new UserConfiguration());
}
}
diff --git a/OliverBooth.Blog/Data/Configuration/TemplateConfiguration.cs b/OliverBooth.Blog/Data/Configuration/TemplateConfiguration.cs
new file mode 100644
index 0000000..e72ccb4
--- /dev/null
+++ b/OliverBooth.Blog/Data/Configuration/TemplateConfiguration.cs
@@ -0,0 +1,19 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace OliverBooth.Blog.Data.Configuration;
+
+///
+/// Represents the configuration for the entity.
+///
+internal sealed class TemplateConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("Template");
+ builder.HasKey(e => e.Name);
+
+ builder.Property(e => e.Name).IsRequired();
+ builder.Property(e => e.FormatString).IsRequired();
+ }
+}
diff --git a/OliverBooth.Blog/Data/Template.cs b/OliverBooth.Blog/Data/Template.cs
new file mode 100644
index 0000000..c20b9ce
--- /dev/null
+++ b/OliverBooth.Blog/Data/Template.cs
@@ -0,0 +1,76 @@
+using OliverBooth.Common.Data;
+
+namespace OliverBooth.Blog.Data;
+
+///
+/// Represents a MediaWiki-style template.
+///
+public sealed class Template : ITemplate, IEquatable
+{
+ ///
+ public string FormatString { get; internal set; } = string.Empty;
+
+ ///
+ public string Name { get; private set; } = string.Empty;
+
+ ///
+ /// Returns a value indicating whether two instances of are equal.
+ ///
+ /// The first instance of to compare.
+ /// The second instance of to compare.
+ ///
+ /// if and are equal; otherwise,
+ /// .
+ ///
+ public static bool operator ==(Template? left, Template? right) => Equals(left, right);
+
+ ///
+ /// Returns a value indicating whether two instances of are not equal.
+ ///
+ /// The first instance of to compare.
+ /// The second instance of to compare.
+ ///
+ /// if and are not equal; otherwise,
+ /// .
+ ///
+ public static bool operator !=(Template? left, Template? right) => !(left == right);
+
+ ///
+ /// Returns a value indicating whether this instance of is equal to another
+ /// instance.
+ ///
+ /// An instance to compare with this instance.
+ ///
+ /// if is equal to this instance; otherwise,
+ /// .
+ ///
+ public bool Equals(Template? other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return Name == other.Name;
+ }
+
+ ///
+ /// Returns a value indicating whether this instance is equal to a specified object.
+ ///
+ /// An object to compare with this instance.
+ ///
+ /// if is an instance of and
+ /// equals the value of this instance; otherwise, .
+ ///
+ public override bool Equals(object? obj)
+ {
+ return ReferenceEquals(this, obj) || obj is Template other && Equals(other);
+ }
+
+ ///
+ /// Gets the hash code for this instance.
+ ///
+ /// The hash code.
+ public override int GetHashCode()
+ {
+ // ReSharper disable once NonReadonlyMemberInGetHashCode
+ return Name.GetHashCode();
+ }
+}
diff --git a/OliverBooth.Blog/Program.cs b/OliverBooth.Blog/Program.cs
index b7bb215..d2013a7 100644
--- a/OliverBooth.Blog/Program.cs
+++ b/OliverBooth.Blog/Program.cs
@@ -3,6 +3,7 @@ using OliverBooth.Blog.Middleware;
using OliverBooth.Blog.Services;
using OliverBooth.Common;
using OliverBooth.Common.Extensions;
+using OliverBooth.Common.Services;
using Serilog;
using X10D.Hosting.DependencyInjection;
@@ -20,6 +21,7 @@ builder.Services.ConfigureOptions();
builder.Services.AddDbContextFactory();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
+builder.Services.AddSingleton();
builder.Services.AddRazorPages().AddRazorRuntimeCompilation();
builder.Services.AddControllersWithViews();
diff --git a/OliverBooth.Common/Data/ITemplate.cs b/OliverBooth.Common/Data/ITemplate.cs
new file mode 100644
index 0000000..9adcaa3
--- /dev/null
+++ b/OliverBooth.Common/Data/ITemplate.cs
@@ -0,0 +1,18 @@
+namespace OliverBooth.Common.Data;
+
+///
+/// Represents a template.
+///
+public interface ITemplate
+{
+ ///
+ /// Gets or sets the format string.
+ ///
+ /// The format string.
+ string FormatString { get; }
+
+ ///
+ /// Gets the name of the template.
+ ///
+ string Name { get; }
+}
diff --git a/OliverBooth/Formatting/DateFormatter.cs b/OliverBooth.Common/Formatting/DateFormatter.cs
similarity index 90%
rename from OliverBooth/Formatting/DateFormatter.cs
rename to OliverBooth.Common/Formatting/DateFormatter.cs
index d70d970..ce4a430 100644
--- a/OliverBooth/Formatting/DateFormatter.cs
+++ b/OliverBooth.Common/Formatting/DateFormatter.cs
@@ -1,12 +1,12 @@
using System.Globalization;
using SmartFormat.Core.Extensions;
-namespace OliverBooth.Formatting;
+namespace OliverBooth.Common.Formatting;
///
/// Represents a SmartFormat formatter that formats a date.
///
-internal sealed class DateFormatter : IFormatter
+public sealed class DateFormatter : IFormatter
{
///
public bool CanAutoDetect { get; set; } = true;
diff --git a/OliverBooth/Formatting/MarkdownFormatter.cs b/OliverBooth.Common/Formatting/MarkdownFormatter.cs
similarity index 88%
rename from OliverBooth/Formatting/MarkdownFormatter.cs
rename to OliverBooth.Common/Formatting/MarkdownFormatter.cs
index 860188e..077c68b 100644
--- a/OliverBooth/Formatting/MarkdownFormatter.cs
+++ b/OliverBooth.Common/Formatting/MarkdownFormatter.cs
@@ -1,12 +1,13 @@
using Markdig;
+using Microsoft.Extensions.DependencyInjection;
using SmartFormat.Core.Extensions;
-namespace OliverBooth.Formatting;
+namespace OliverBooth.Common.Formatting;
///
/// Represents a SmartFormat formatter that formats markdown.
///
-internal sealed class MarkdownFormatter : IFormatter
+public sealed class MarkdownFormatter : IFormatter
{
private readonly IServiceProvider _serviceProvider;
diff --git a/OliverBooth/Markdown/Template/TemplateExtension.cs b/OliverBooth.Common/Markdown/TemplateExtension.cs
similarity index 77%
rename from OliverBooth/Markdown/Template/TemplateExtension.cs
rename to OliverBooth.Common/Markdown/TemplateExtension.cs
index 523c797..ab26721 100644
--- a/OliverBooth/Markdown/Template/TemplateExtension.cs
+++ b/OliverBooth.Common/Markdown/TemplateExtension.cs
@@ -1,21 +1,21 @@
using Markdig;
using Markdig.Renderers;
-using OliverBooth.Services;
+using OliverBooth.Common.Services;
-namespace OliverBooth.Markdown.Template;
+namespace OliverBooth.Common.Markdown;
///
/// Represents a Markdown extension that adds support for MediaWiki-style templates.
///
-internal sealed class TemplateExtension : IMarkdownExtension
+public sealed class TemplateExtension : IMarkdownExtension
{
- private readonly TemplateService _templateService;
+ private readonly ITemplateService _templateService;
///
/// Initializes a new instance of the class.
///
/// The template service.
- public TemplateExtension(TemplateService templateService)
+ public TemplateExtension(ITemplateService templateService)
{
_templateService = templateService;
}
diff --git a/OliverBooth/Markdown/Template/TemplateInline.cs b/OliverBooth.Common/Markdown/TemplateInline.cs
similarity index 95%
rename from OliverBooth/Markdown/Template/TemplateInline.cs
rename to OliverBooth.Common/Markdown/TemplateInline.cs
index 34be9ad..4c4565d 100644
--- a/OliverBooth/Markdown/Template/TemplateInline.cs
+++ b/OliverBooth.Common/Markdown/TemplateInline.cs
@@ -1,6 +1,6 @@
using Markdig.Syntax.Inlines;
-namespace OliverBooth.Markdown.Template;
+namespace OliverBooth.Common.Markdown;
///
/// Represents a Markdown inline element that represents a MediaWiki-style template.
diff --git a/OliverBooth/Markdown/Template/TemplateInlineParser.cs b/OliverBooth.Common/Markdown/TemplateInlineParser.cs
similarity index 99%
rename from OliverBooth/Markdown/Template/TemplateInlineParser.cs
rename to OliverBooth.Common/Markdown/TemplateInlineParser.cs
index 4c3d7b5..88731aa 100644
--- a/OliverBooth/Markdown/Template/TemplateInlineParser.cs
+++ b/OliverBooth.Common/Markdown/TemplateInlineParser.cs
@@ -2,7 +2,7 @@ using Cysharp.Text;
using Markdig.Helpers;
using Markdig.Parsers;
-namespace OliverBooth.Markdown.Template;
+namespace OliverBooth.Common.Markdown;
///
/// Represents a Markdown inline parser that handles MediaWiki-style templates.
diff --git a/OliverBooth/Markdown/Template/TemplateRenderer.cs b/OliverBooth.Common/Markdown/TemplateRenderer.cs
similarity index 78%
rename from OliverBooth/Markdown/Template/TemplateRenderer.cs
rename to OliverBooth.Common/Markdown/TemplateRenderer.cs
index dbad29b..4df9edd 100644
--- a/OliverBooth/Markdown/Template/TemplateRenderer.cs
+++ b/OliverBooth.Common/Markdown/TemplateRenderer.cs
@@ -1,21 +1,21 @@
using Markdig.Renderers;
using Markdig.Renderers.Html;
-using OliverBooth.Services;
+using OliverBooth.Common.Services;
-namespace OliverBooth.Markdown.Template;
+namespace OliverBooth.Common.Markdown;
///
/// Represents a Markdown object renderer that handles elements.
///
internal sealed class TemplateRenderer : HtmlObjectRenderer
{
- private readonly TemplateService _templateService;
+ private readonly ITemplateService _templateService;
///
/// Initializes a new instance of the class.
///
/// The .
- public TemplateRenderer(TemplateService templateService)
+ public TemplateRenderer(ITemplateService templateService)
{
_templateService = templateService;
}
diff --git a/OliverBooth.Common/Services/ITemplateService.cs b/OliverBooth.Common/Services/ITemplateService.cs
new file mode 100644
index 0000000..eacb31f
--- /dev/null
+++ b/OliverBooth.Common/Services/ITemplateService.cs
@@ -0,0 +1,19 @@
+using OliverBooth.Common.Markdown;
+
+namespace OliverBooth.Common.Services;
+
+///
+/// Represents a service that renders MediaWiki-style templates.
+///
+public interface ITemplateService
+{
+ ///
+ /// Renders the specified template with the specified arguments.
+ ///
+ /// The template to render.
+ /// The rendered template.
+ ///
+ /// is .
+ ///
+ string RenderTemplate(TemplateInline templateInline);
+}
diff --git a/OliverBooth/Data/Web/Configuration/ArticleTemplateConfiguration.cs b/OliverBooth/Data/Web/Configuration/TemplateConfiguration.cs
similarity index 53%
rename from OliverBooth/Data/Web/Configuration/ArticleTemplateConfiguration.cs
rename to OliverBooth/Data/Web/Configuration/TemplateConfiguration.cs
index d4a4f6e..9cf5d8b 100644
--- a/OliverBooth/Data/Web/Configuration/ArticleTemplateConfiguration.cs
+++ b/OliverBooth/Data/Web/Configuration/TemplateConfiguration.cs
@@ -4,13 +4,13 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace OliverBooth.Data.Web.Configuration;
///
-/// Represents the configuration for the entity.
+/// Represents the configuration for the entity.
///
-internal sealed class ArticleTemplateConfiguration : IEntityTypeConfiguration
+internal sealed class TemplateConfiguration : IEntityTypeConfiguration
{
- public void Configure(EntityTypeBuilder builder)
+ public void Configure(EntityTypeBuilder builder)
{
- builder.ToTable("ArticleTemplate");
+ builder.ToTable("Template");
builder.HasKey(e => e.Name);
builder.Property(e => e.Name).IsRequired();
diff --git a/OliverBooth/Data/Web/ArticleTemplate.cs b/OliverBooth/Data/Web/Template.cs
similarity index 61%
rename from OliverBooth/Data/Web/ArticleTemplate.cs
rename to OliverBooth/Data/Web/Template.cs
index bffd5ab..83677c6 100644
--- a/OliverBooth/Data/Web/ArticleTemplate.cs
+++ b/OliverBooth/Data/Web/Template.cs
@@ -1,45 +1,42 @@
+using OliverBooth.Common.Data;
+
namespace OliverBooth.Data.Web;
///
/// Represents a MediaWiki-style template.
///
-public sealed class ArticleTemplate : IEquatable
+public sealed class Template : ITemplate, IEquatable
{
- ///
- /// Gets or sets the format string.
- ///
- /// The format string.
- public string FormatString { get; set; } = string.Empty;
+ ///
+ public string FormatString { get; internal set; } = string.Empty;
- ///
- /// Gets the name of the template.
- ///
+ ///
public string Name { get; private set; } = string.Empty;
///
- /// Returns a value indicating whether two instances of are equal.
+ /// Returns a value indicating whether two instances of are equal.
///
- /// The first instance of to compare.
- /// The second instance of to compare.
+ /// The first instance of to compare.
+ /// The second instance of to compare.
///
/// if and are equal; otherwise,
/// .
///
- public static bool operator ==(ArticleTemplate? left, ArticleTemplate? right) => Equals(left, right);
+ public static bool operator ==(Template? left, Template? right) => Equals(left, right);
///
- /// Returns a value indicating whether two instances of are not equal.
+ /// Returns a value indicating whether two instances of are not equal.
///
- /// The first instance of to compare.
- /// The second instance of to compare.
+ /// The first instance of to compare.
+ /// The second instance of to compare.
///
/// if and are not equal; otherwise,
/// .
///
- public static bool operator !=(ArticleTemplate? left, ArticleTemplate? right) => !(left == right);
+ public static bool operator !=(Template? left, Template? right) => !(left == right);
///
- /// Returns a value indicating whether this instance of is equal to another
+ /// Returns a value indicating whether this instance of is equal to another
/// instance.
///
/// An instance to compare with this instance.
@@ -47,7 +44,7 @@ public sealed class ArticleTemplate : IEquatable
/// if is equal to this instance; otherwise,
/// .
///
- public bool Equals(ArticleTemplate? other)
+ public bool Equals(Template? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
@@ -59,12 +56,12 @@ public sealed class ArticleTemplate : IEquatable
///
/// An object to compare with this instance.
///
- /// if is an instance of and
+ /// if is an instance of and
/// equals the value of this instance; otherwise, .
///
public override bool Equals(object? obj)
{
- return ReferenceEquals(this, obj) || obj is ArticleTemplate other && Equals(other);
+ return ReferenceEquals(this, obj) || obj is Template other && Equals(other);
}
///
diff --git a/OliverBooth/Data/WebContext.cs b/OliverBooth/Data/WebContext.cs
index 5791d92..eb52e95 100644
--- a/OliverBooth/Data/WebContext.cs
+++ b/OliverBooth/Data/WebContext.cs
@@ -20,18 +20,18 @@ public sealed class WebContext : DbContext
_configuration = configuration;
}
- ///
- /// Gets the set of article templates.
- ///
- /// The set of article templates.
- public DbSet ArticleTemplates { get; private set; } = null!;
-
///
/// Gets the set of site configuration items.
///
/// The set of site configuration items.
public DbSet SiteConfiguration { get; private set; } = null!;
+ ///
+ /// Gets the collection of templates in the database.
+ ///
+ /// The collection of templates.
+ public DbSet Templates { get; private set; } = null!;
+
///
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
@@ -42,7 +42,7 @@ public sealed class WebContext : DbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
- modelBuilder.ApplyConfiguration(new ArticleTemplateConfiguration());
+ modelBuilder.ApplyConfiguration(new TemplateConfiguration());
modelBuilder.ApplyConfiguration(new SiteConfigurationConfiguration());
}
}
diff --git a/OliverBooth/Program.cs b/OliverBooth/Program.cs
index 47c9341..0a77b61 100644
--- a/OliverBooth/Program.cs
+++ b/OliverBooth/Program.cs
@@ -1,8 +1,8 @@
using Markdig;
using OliverBooth.Common;
using OliverBooth.Common.Extensions;
+using OliverBooth.Common.Services;
using OliverBooth.Data;
-using OliverBooth.Markdown.Template;
using OliverBooth.Markdown.Timestamp;
using OliverBooth.Services;
using Serilog;
@@ -18,11 +18,11 @@ builder.Logging.ClearProviders();
builder.Logging.AddSerilog();
builder.Services.ConfigureOptions();
-builder.Services.AddSingleton();
+builder.Services.AddSingleton();
builder.Services.AddSingleton(provider => new MarkdownPipelineBuilder()
.Use()
- .Use(new TemplateExtension(provider.GetRequiredService()))
+ .Use(new TemplateExtension(provider.GetRequiredService()))
.UseAdvancedExtensions()
.UseBootstrap()
.UseEmojiAndSmiley()
diff --git a/OliverBooth/Services/TemplateService.cs b/OliverBooth/Services/TemplateService.cs
index 1d93466..66365d1 100644
--- a/OliverBooth/Services/TemplateService.cs
+++ b/OliverBooth/Services/TemplateService.cs
@@ -1,11 +1,10 @@
using System.Buffers.Binary;
-using Markdig;
-using Markdig.Syntax;
using Microsoft.EntityFrameworkCore;
+using OliverBooth.Common.Formatting;
+using OliverBooth.Common.Markdown;
+using OliverBooth.Common.Services;
using OliverBooth.Data;
using OliverBooth.Data.Web;
-using OliverBooth.Formatting;
-using OliverBooth.Markdown.Template;
using SmartFormat;
using SmartFormat.Extensions;
@@ -14,10 +13,9 @@ namespace OliverBooth.Services;
///
/// Represents a service that renders MediaWiki-style templates.
///
-public sealed class TemplateService
+internal sealed class TemplateService : ITemplateService
{
private static readonly Random Random = new();
- private readonly IServiceProvider _serviceProvider;
private readonly IDbContextFactory _webContextFactory;
private readonly SmartFormatter _formatter;
@@ -34,27 +32,16 @@ public sealed class TemplateService
_formatter.AddExtensions(new DateFormatter());
_formatter.AddExtensions(new MarkdownFormatter(serviceProvider));
- _serviceProvider = serviceProvider;
_webContextFactory = webContextFactory;
- Current = this;
}
- public static TemplateService Current { get; private set; } = null!;
-
- ///
- /// Renders the specified template with the specified arguments.
- ///
- /// The template to render.
- /// The rendered template.
- ///
- /// is .
- ///
+ ///
public string RenderTemplate(TemplateInline templateInline)
{
if (templateInline is null) throw new ArgumentNullException(nameof(templateInline));
using WebContext webContext = _webContextFactory.CreateDbContext();
- ArticleTemplate? template = webContext.ArticleTemplates.Find(templateInline.Name);
+ Template? template = webContext.Templates.Find(templateInline.Name);
if (template is null)
{
return $"{{{{{templateInline.Name}}}}}";