diff --git a/src/TerribleDev.Blog.Web/Controllers/HomeController.cs b/src/TerribleDev.Blog.Web/Controllers/HomeController.cs index 7ab444b..7b081c4 100644 --- a/src/TerribleDev.Blog.Web/Controllers/HomeController.cs +++ b/src/TerribleDev.Blog.Web/Controllers/HomeController.cs @@ -12,9 +12,10 @@ namespace TerribleDev.Blog.Web.Controllers { public class HomeController : Controller { - static List postsAsList = new BlogFactory().GetAllPosts().OrderByDescending(a=>a.PublishDate).ToList(); - static IDictionary posts = postsAsList.ToDictionary(a=>a.Url); - static IDictionary> postsByPage = postsAsList.Aggregate(new Dictionary>() { [1] = new List() }, (accum, item) => + public static List postsAsList = new BlogFactory().GetAllPosts().OrderByDescending(a=>a.PublishDate).ToList(); + public static HashSet totalTags = postsAsList.Where(a=>a.tags != null).SelectMany(a => a.tags).ToHashSet(); + public static IDictionary posts = postsAsList.ToDictionary(a=>a.Url); + public static IDictionary> postsByPage = postsAsList.Aggregate(new Dictionary>() { [1] = new List() }, (accum, item) => { var highestPage = accum.Keys.Max(); var current = accum[highestPage].Count; @@ -57,6 +58,7 @@ namespace TerribleDev.Blog.Web.Controllers } [Route("{postUrl}")] + [OutputCache(Duration = 31536000, VaryByParam = "postUrl")] [ResponseCache(Duration = 180)] public IActionResult Post(string postUrl) { diff --git a/src/TerribleDev.Blog.Web/Controllers/SeoController.cs b/src/TerribleDev.Blog.Web/Controllers/SeoController.cs new file mode 100644 index 0000000..5c03122 --- /dev/null +++ b/src/TerribleDev.Blog.Web/Controllers/SeoController.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Serialization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.SyndicationFeed; +using Microsoft.SyndicationFeed.Rss; +using TerribleDev.Blog.Web.Models; + +namespace TerribleDev.Blog.Web.Controllers +{ + public class SeoController : Controller + { + public static DateTimeOffset publishDate = DateTimeOffset.UtcNow; // keep publish date in memory so we just return when the server was kicked + public static IEnumerable postsToSyndication = HomeController.postsAsList.Select(a => a.ToSyndicationItem()).ToList(); + [Route("/rss")] + [Route("/rss.xml")] + [ResponseCache(Duration = 7200)] + [OutputCache(Duration = 86400)] + public async Task Rss() + { + Response.StatusCode = 200; + Response.ContentType = "text/xml"; + using (XmlWriter xmlWriter = XmlWriter.Create(this.Response.Body, new XmlWriterSettings() { Async = true, Indent = false, Encoding = Encoding.UTF8 })) + { + var writer = new RssFeedWriter(xmlWriter); + await writer.WriteTitle("The Ramblings of TerribleDev"); + await writer.WriteValue("link", "https://blog.terribledev.io"); + await writer.WriteDescription("My name is Tommy Parnell. I usually go by TerribleDev on the internets. These are just some of my writings and rants about the software space."); + + foreach (var item in postsToSyndication) + { + await writer.Write(item); + } + + + await xmlWriter.FlushAsync(); + } + } + [Route("/sitemap.xml")] + [ResponseCache(Duration = 7200)] + [OutputCache(Duration = 86400)] + public void SiteMap() + { + Response.StatusCode = 200; + Response.ContentType = "text/xml"; + var ser = new XmlSerializer(typeof(SiteMapRoot)); + var sitemap = new SiteMapRoot() + { + Urls = HomeController.postsAsList.Select(a => new SiteMapItem() { LastModified = DateTime.UtcNow, Location = $"https://blog.terribledev.io/{a.Url}" }).ToList() + }; + ser.Serialize(this.Response.Body, sitemap); + } + } +} \ No newline at end of file diff --git a/src/TerribleDev.Blog.Web/Extensions/IPostExtensions.cs b/src/TerribleDev.Blog.Web/Extensions/IPostExtensions.cs new file mode 100644 index 0000000..e2efd32 --- /dev/null +++ b/src/TerribleDev.Blog.Web/Extensions/IPostExtensions.cs @@ -0,0 +1,23 @@ +using Microsoft.SyndicationFeed; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using TerribleDev.Blog.Web.Models; + +namespace TerribleDev.Blog.Web +{ + public static class IPostExtensions + { + public static SyndicationItem ToSyndicationItem(this IPost x) + { + return new SyndicationItem() + { + Title = x.Title, + Description = x.ContentPlain, + Id = $"https://blog.terribledev.io/{x.Url}", + Published = x.PublishDate + }; + } + } +} diff --git a/src/TerribleDev.Blog.Web/Extensions/StringExtension.cs b/src/TerribleDev.Blog.Web/Extensions/StringExtension.cs index 77d9159..4ec11d8 100644 --- a/src/TerribleDev.Blog.Web/Extensions/StringExtension.cs +++ b/src/TerribleDev.Blog.Web/Extensions/StringExtension.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace TerribleDev.Blog.Web.Extensions +namespace TerribleDev.Blog.Web { public static class StringExtension { diff --git a/src/TerribleDev.Blog.Web/Factories/BlogFactory.cs b/src/TerribleDev.Blog.Web/Factories/BlogFactory.cs index 42a0210..b133454 100644 --- a/src/TerribleDev.Blog.Web/Factories/BlogFactory.cs +++ b/src/TerribleDev.Blog.Web/Factories/BlogFactory.cs @@ -6,7 +6,6 @@ using System.IO; using TerribleDev.Blog.Web.Models; using YamlDotNet.Serialization; using Microsoft.AspNetCore.Html; -using TerribleDev.Blog.Web.Extensions; using Markdig; namespace TerribleDev.Blog.Web @@ -40,7 +39,7 @@ namespace TerribleDev.Blog.Web var markdownText = string.Join("", splitFile.Skip(1)); var pipeline = new MarkdownPipelineBuilder().UseEmojiAndSmiley().Build(); var postContent = Markdown.ToHtml(markdownText, pipeline); - var postContentPlain = Markdown.ToPlainText(markdownText, pipeline); + var postContentPlain = String.Join("", Markdown.ToPlainText(markdownText, pipeline).Split("")); var postSettings = ParseYaml(ymlRaw); var resolvedUrl = !string.IsNullOrWhiteSpace(postSettings.permalink) ? postSettings.permalink : fileName.Split('.')[0].Replace(' ', '-').WithoutSpecialCharacters(); var summary = postContent.Split("")[0]; diff --git a/src/TerribleDev.Blog.Web/Models/IPost.cs b/src/TerribleDev.Blog.Web/Models/IPost.cs index f239863..ecbbc43 100644 --- a/src/TerribleDev.Blog.Web/Models/IPost.cs +++ b/src/TerribleDev.Blog.Web/Models/IPost.cs @@ -12,7 +12,7 @@ namespace TerribleDev.Blog.Web.Models string Url { get; set; } string Title { get; set; } HtmlString Summary { get; set; } - DateTimeOffset PublishDate { get; set; } + DateTime PublishDate { get; set; } HtmlString Content { get; set; } string ContentPlain { get; set; } string SummaryPlain { get; set; } diff --git a/src/TerribleDev.Blog.Web/Models/Post.cs b/src/TerribleDev.Blog.Web/Models/Post.cs index 0731360..8c84fbe 100644 --- a/src/TerribleDev.Blog.Web/Models/Post.cs +++ b/src/TerribleDev.Blog.Web/Models/Post.cs @@ -8,7 +8,7 @@ namespace TerribleDev.Blog.Web.Models { public string Url { get; set; } public string Title { get; set; } - public DateTimeOffset PublishDate { get; set; } + public DateTime PublishDate { get; set; } public HtmlString Content { get; set; } public HtmlString Summary { get; set; } public string ContentPlain { get; set; } diff --git a/src/TerribleDev.Blog.Web/Models/PostSettings.cs b/src/TerribleDev.Blog.Web/Models/PostSettings.cs index ba5b9fc..2525421 100644 --- a/src/TerribleDev.Blog.Web/Models/PostSettings.cs +++ b/src/TerribleDev.Blog.Web/Models/PostSettings.cs @@ -8,8 +8,8 @@ namespace TerribleDev.Blog.Web.Models public List tags { get; set; } public string title { get; set; } public string permalink { get; set; } - public DateTimeOffset date { get; set; } - public DateTimeOffset updated { get; set; } + public DateTime date { get; set; } + public DateTime updated { get; set; } public string id { get; set; } public string thumbnail_image { get; set; } public string thumbnailImage { get; set; } diff --git a/src/TerribleDev.Blog.Web/Models/SitemapModel.cs b/src/TerribleDev.Blog.Web/Models/SitemapModel.cs new file mode 100644 index 0000000..3bad327 --- /dev/null +++ b/src/TerribleDev.Blog.Web/Models/SitemapModel.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Xml.Serialization; + +namespace TerribleDev.Blog.Web.Models +{ + [XmlRoot("urlset")] + public class SiteMapRoot + { + [XmlElement("url")] + public List Urls { get; set; } + } + + public class SiteMapItem + { + [XmlElement("loc")] + public string Location { get; set; } + [XmlElement("lastmod")] + public DateTime LastModified { get; set; } + + } +} diff --git a/src/TerribleDev.Blog.Web/Startup.cs b/src/TerribleDev.Blog.Web/Startup.cs index 65bde29..d5e65be 100644 --- a/src/TerribleDev.Blog.Web/Startup.cs +++ b/src/TerribleDev.Blog.Web/Startup.cs @@ -39,6 +39,7 @@ namespace TerribleDev.Blog.Web .AddCacheTagHelper() .AddRazorViewEngine() .SetCompatibilityVersion(CompatibilityVersion.Version_2_2); + services.AddOutputCaching(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -80,7 +81,8 @@ namespace TerribleDev.Blog.Web "public,max-age=" + cacheTime; } }); - app.UseRewriter(new Microsoft.AspNetCore.Rewrite.RewriteOptions().AddRedirect("(.*[^/])$", "$1/", 301)); + app.UseRewriter(new Microsoft.AspNetCore.Rewrite.RewriteOptions().AddRedirect("(.*[^/|.xml])$", "$1/", 301)); + app.UseOutputCaching(); app.UseMvc(); } } diff --git a/src/TerribleDev.Blog.Web/TerribleDev.Blog.Web.csproj b/src/TerribleDev.Blog.Web/TerribleDev.Blog.Web.csproj index 1ad857a..de47ac4 100644 --- a/src/TerribleDev.Blog.Web/TerribleDev.Blog.Web.csproj +++ b/src/TerribleDev.Blog.Web/TerribleDev.Blog.Web.csproj @@ -27,6 +27,8 @@ + +