diff --git a/.vscode/launch.json b/.vscode/launch.json index ed88a45..30e8198 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "build", - "program": "${workspaceFolder}/src/TerribleDev.Blog.Web/bin/Debug/netcoreapp2.2/TerribleDev.Blog.Web.dll", + "program": "${workspaceFolder}/src/TerribleDev.Blog.Web/bin/Debug/netcoreapp3.1/TerribleDev.Blog.Web.dll", "args": [], "cwd": "${workspaceFolder}/src/TerribleDev.Blog.Web", "stopAtEntry": false, @@ -42,4 +42,4 @@ "processId": "${command:pickProcess}" } ] -} \ No newline at end of file +} diff --git a/src/TerribleDev.Blog.Web/Controllers/SeoController.cs b/src/TerribleDev.Blog.Web/Controllers/SeoController.cs index 4f4bc32..e56874c 100644 --- a/src/TerribleDev.Blog.Web/Controllers/SeoController.cs +++ b/src/TerribleDev.Blog.Web/Controllers/SeoController.cs @@ -23,7 +23,7 @@ namespace TerribleDev.Blog.Web.Controllers this.configuration = configuration; this.postCache = postCache; } - public static DateTimeOffset publishDate = DateTimeOffset.UtcNow; // keep publish date in memory so we just return when the server was kicked + public static DateTimeOffset publishDate = DateTimeOffset.UtcNow; // keep publish date in memory so we just return when the server was kicked [Route("/rss")] [Route("/rss.xml")] [ResponseCache(Duration = 7200)] @@ -62,10 +62,10 @@ namespace TerribleDev.Blog.Web.Controllers var ser = new XmlSerializer(typeof(SiteMapRoot)); var sitemap = new SiteMapRoot() { - Urls = postCache.PostsAsLists.Select(a => new SiteMapItem() { LastModified = DateTime.UtcNow, Location = $"https://blog.terrible.dev/{a.Url}/" }).ToList() + Urls = postCache.PostsAsLists.Select(a => new SiteMapItem() { LastModified = DateTime.UtcNow, Location = a.CanonicalUrl }).ToList() }; sitemap.Urls.AddRange(sitewideLinks); ser.Serialize(this.Response.Body, sitemap); } } -} \ No newline at end of file +} diff --git a/src/TerribleDev.Blog.Web/Controllers/TagsController.cs b/src/TerribleDev.Blog.Web/Controllers/TagsController.cs index 58b79a1..b6b2958 100644 --- a/src/TerribleDev.Blog.Web/Controllers/TagsController.cs +++ b/src/TerribleDev.Blog.Web/Controllers/TagsController.cs @@ -23,7 +23,7 @@ namespace TerribleDev.Blog.Web.Controllers } [Route("/tags/{tagName}")] [OutputCache(Duration = 31536000, VaryByParam = "tagName")] - public IActionResult TagPluralRedirect(string tagName) + public IActionResult TagPluralRedirect(string tagName) { if(string.IsNullOrEmpty(tagName)) { @@ -40,8 +40,8 @@ namespace TerribleDev.Blog.Web.Controllers return Redirect($"/404/?from=/tag/{tagName}/"); } { - return View(new Models.GetTagViewModel { Tag = tagName, Posts = models }); + return View(new Models.GetTagViewModel { Tag = tagName, Posts = models, CanonicalUrl = $"https://blog.terrible.dev/tag/{tagName.ToLower()}/" }); } } } -} \ No newline at end of file +} diff --git a/src/TerribleDev.Blog.Web/Dockerfile b/src/TerribleDev.Blog.Web/Dockerfile index 3c6c906..67c1894 100644 --- a/src/TerribleDev.Blog.Web/Dockerfile +++ b/src/TerribleDev.Blog.Web/Dockerfile @@ -1,9 +1,9 @@ -FROM microsoft/dotnet:2.2-aspnetcore-runtime AS base +FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 AS base WORKDIR /app EXPOSE 80 EXPOSE 443 -FROM microsoft/dotnet:2.2-sdk AS build +FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build WORKDIR /src COPY ["./TerribleDev.Blog.Web.csproj", "."] RUN dotnet restore "TerribleDev.Blog.Web.csproj" @@ -17,4 +17,4 @@ RUN dotnet publish "TerribleDev.Blog.Web.csproj" -c Release -o /app FROM base AS final WORKDIR /app COPY --from=publish /app . -ENTRYPOINT ["dotnet", "TerribleDev.Blog.Web.dll"] \ No newline at end of file +ENTRYPOINT ["dotnet", "TerribleDev.Blog.Web.dll"] diff --git a/src/TerribleDev.Blog.Web/Drafts/Hosting-your-blog-on-the-cheap.md b/src/TerribleDev.Blog.Web/Drafts/Hosting-your-blog-on-the-cheap.md index 3dbb902..bd825f7 100644 --- a/src/TerribleDev.Blog.Web/Drafts/Hosting-your-blog-on-the-cheap.md +++ b/src/TerribleDev.Blog.Web/Drafts/Hosting-your-blog-on-the-cheap.md @@ -1,5 +1,5 @@ title: Hosting your blog on the cheap -date: 2018-08-22 04:49:46 +date: 2019-08-17 04:49:46 tags: - cloud @@ -9,13 +9,12 @@ A load of people have been asking me lately how I host my blog. Incase its not a -Since I make no money, on this my strategy is about cutting costs. My grandfather use to say "take care of the pounds, because the pennies will take care of themselves." Now since my grandfather is in England, and their dollar is known as the pound, he was telling me to focus on the bigger picture. +Since I make no money, on this my strategy is about cutting costs. My grandfather use to say "take care of the pounds, let the pennies take care of themselves." Now since my grandfather is in England, and their dollar is known as the pound, he was telling me to focus on the bigger picture. The first big decision for blogs is what "engine" you are going to use, or if you are going to make your own. These usually fall into 2 categories. Static sites, which are usually when blogs are written in text files, and are compiled into static html, or server rendered blogs such as wordpress. When a request is made to blog that has server rendering, the html is dynamically built in time and delivered to the consumer. Static sites, on the other hand are precomputed and thus are just delivered to the browser. -I won't go into the details on what is better for different scenarios. If you are being cheap, then you will want to use static sites. Static sites are precomputed, which essentially means you just need to serve files to the user. There is no dynamic server to host, you won't need a database, etc. There are a few I like. This blog is ran off [Hexo](https://hexo.io) +I won't go into the details on what is better for different scenarios. If you are being cheap, then you will want to use static sites. Static sites are precomputed, which essentially means you just need to serve files to the user. There is no dynamic server to host, you won't need a database, etc. There are a few I like, but my favorite is [gatsbyjs](https://www.gatsbyjs.org/). - - +So I know what you are thinking, static sites are just 'better' for page load time. While this is true, they can lack dynamic features that might be important to you, such as adding new blog posts on a schedule, or limiting ip addresses, or even some kind of login/subscription model. diff --git a/src/TerribleDev.Blog.Web/Extensions/IPostExtensions.cs b/src/TerribleDev.Blog.Web/Extensions/IPostExtensions.cs index 5f1070c..675eab9 100644 --- a/src/TerribleDev.Blog.Web/Extensions/IPostExtensions.cs +++ b/src/TerribleDev.Blog.Web/Extensions/IPostExtensions.cs @@ -11,7 +11,7 @@ namespace TerribleDev.Blog.Web { public static SyndicationItem ToSyndicationItem(this IPost x) { - Uri.TryCreate($"https://blog.terrible.dev/{x.Url}/", UriKind.Absolute, out var url); + Uri.TryCreate(x.CanonicalUrl, UriKind.Absolute, out var url); var syn = new SyndicationItem() { Title = x.Title, diff --git a/src/TerribleDev.Blog.Web/Factories/BlogCacheFactory.cs b/src/TerribleDev.Blog.Web/Factories/BlogCacheFactory.cs index 19ce678..840f3f5 100644 --- a/src/TerribleDev.Blog.Web/Factories/BlogCacheFactory.cs +++ b/src/TerribleDev.Blog.Web/Factories/BlogCacheFactory.cs @@ -28,11 +28,11 @@ namespace TerribleDev.Blog.Web.Factories } return accum; }).ToImmutableSortedDictionary(); - var urlToPosts = posts.ToImmutableDictionary(a => a.Url); - var postsByPage = + var urlToPosts = posts.ToImmutableDictionary(a => a.UrlWithoutPath); + var postsByPage = posts.Aggregate(ImmutableDictionary.Create>(), (accum, item) => { - if(!accum.Keys.Any()) + if(!accum.Keys.Any()) { accum = accum.SetItem(1, ImmutableList.Create()); } @@ -57,4 +57,4 @@ namespace TerribleDev.Blog.Web.Factories }; } } -} \ No newline at end of file +} diff --git a/src/TerribleDev.Blog.Web/Factories/BlogFactory.cs b/src/TerribleDev.Blog.Web/Factories/BlogFactory.cs index e8fa761..7bac3eb 100644 --- a/src/TerribleDev.Blog.Web/Factories/BlogFactory.cs +++ b/src/TerribleDev.Blog.Web/Factories/BlogFactory.cs @@ -58,16 +58,18 @@ namespace TerribleDev.Blog.Web .Build(); var postContent = Markdown.ToHtml(markdownText, pipeline); var postContentPlain = String.Join("", Markdown.ToPlainText(markdownText, pipeline).Split("")); - + var summary = postContent.Split("")[0]; var postSummaryPlain = postContentPlain.Split("")[0]; - + return new Post() { PublishDate = postSettings.date.ToUniversalTime(), tags = postSettings.tags?.Select(a => a.Replace(' ', '-').WithoutSpecialCharacters().ToLower()).ToList() ?? new List(), Title = postSettings.title, - Url = resolvedUrl, + RelativeUrl = $"/{resolvedUrl}/", + CanonicalUrl = $"https://blog.terrible.dev/{resolvedUrl}/", + UrlWithoutPath = resolvedUrl, Content = new HtmlString(postContent), Summary = new HtmlString(summary), SummaryPlain = postSummaryPlain, diff --git a/src/TerribleDev.Blog.Web/Models/GetTagViewModel.cs b/src/TerribleDev.Blog.Web/Models/GetTagViewModel.cs index 0e71623..6408968 100644 --- a/src/TerribleDev.Blog.Web/Models/GetTagViewModel.cs +++ b/src/TerribleDev.Blog.Web/Models/GetTagViewModel.cs @@ -9,5 +9,6 @@ namespace TerribleDev.Blog.Web.Models { public IEnumerable Posts { get; set; } public string Tag { get; set; } + public string CanonicalUrl { get; set; } } } diff --git a/src/TerribleDev.Blog.Web/Models/IPost.cs b/src/TerribleDev.Blog.Web/Models/IPost.cs index 2c4a38a..b02745e 100644 --- a/src/TerribleDev.Blog.Web/Models/IPost.cs +++ b/src/TerribleDev.Blog.Web/Models/IPost.cs @@ -9,7 +9,9 @@ namespace TerribleDev.Blog.Web.Models { public interface IPost { - string Url { get; set; } + string CanonicalUrl { get; set; } + string UrlWithoutPath { get; set; } + string RelativeUrl { get; set; } string Title { get; set; } HtmlString Summary { get; set; } DateTime PublishDate { get; set; } diff --git a/src/TerribleDev.Blog.Web/Models/Post.cs b/src/TerribleDev.Blog.Web/Models/Post.cs index f70eec8..da34077 100644 --- a/src/TerribleDev.Blog.Web/Models/Post.cs +++ b/src/TerribleDev.Blog.Web/Models/Post.cs @@ -8,7 +8,9 @@ namespace TerribleDev.Blog.Web.Models [DebuggerDisplay("{Title}")] public class Post : IPost { - public string Url { get; set; } + public string CanonicalUrl { get; set; } + public string UrlWithoutPath { get; set; } + public string RelativeUrl { get; set; } public string Title { get; set; } public DateTime PublishDate { get; set; } public HtmlString Content { get; set; } diff --git a/src/TerribleDev.Blog.Web/Startup.cs b/src/TerribleDev.Blog.Web/Startup.cs index 647db13..28c9497 100644 --- a/src/TerribleDev.Blog.Web/Startup.cs +++ b/src/TerribleDev.Blog.Web/Startup.cs @@ -12,8 +12,8 @@ using Microsoft.AspNetCore.Rewrite; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Hosting; using Microsoft.Net.Http.Headers; -using HardHat.Middlewares; using HardHat; using TerribleDev.Blog.Web.Models; using TerribleDev.Blog.Web.Factories; @@ -22,14 +22,14 @@ namespace TerribleDev.Blog.Web { public class Startup { - public Startup(IConfiguration configuration, IHostingEnvironment env) + public Startup(IConfiguration configuration, IWebHostEnvironment env) { Configuration = configuration; Env = env; } public IConfiguration Configuration { get; } - public IHostingEnvironment Env { get; } + public IWebHostEnvironment Env { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) @@ -53,15 +53,17 @@ namespace TerribleDev.Blog.Web }) .AddMemoryCache() - .AddMvcCore() + .AddMvcCore(a => { + a.EnableEndpointRouting = false; + }) .AddCacheTagHelper() .AddRazorViewEngine() - .SetCompatibilityVersion(CompatibilityVersion.Version_2_2); + .SetCompatibilityVersion(CompatibilityVersion.Latest); services.AddOutputCaching(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IHostingEnvironment env) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { diff --git a/src/TerribleDev.Blog.Web/Taghelpers/InlineCss.cs b/src/TerribleDev.Blog.Web/Taghelpers/InlineCss.cs index 17722f6..c9fc108 100644 --- a/src/TerribleDev.Blog.Web/Taghelpers/InlineCss.cs +++ b/src/TerribleDev.Blog.Web/Taghelpers/InlineCss.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Hosting; using System.IO; using System.Threading.Tasks; @@ -14,18 +15,18 @@ namespace TerribleDev.Blog.Web.Taghelpers [HtmlAttributeName("href")] public string Href { get; set; } - private IHostingEnvironment HostingEnvironment { get; } + private IWebHostEnvironment HostingEnvironment { get; } private IMemoryCache Cache { get; } - - public InlineStyleTagHelper(IHostingEnvironment hostingEnvironment, IMemoryCache cache) + + public InlineStyleTagHelper(IWebHostEnvironment hostingEnvironment, IMemoryCache cache) { HostingEnvironment = hostingEnvironment; Cache = cache; } - + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { var path = Href; @@ -69,4 +70,4 @@ namespace TerribleDev.Blog.Web.Taghelpers } } } -} \ No newline at end of file +} diff --git a/src/TerribleDev.Blog.Web/TerribleDev.Blog.Web.csproj b/src/TerribleDev.Blog.Web/TerribleDev.Blog.Web.csproj index e469272..86ad53a 100644 --- a/src/TerribleDev.Blog.Web/TerribleDev.Blog.Web.csproj +++ b/src/TerribleDev.Blog.Web/TerribleDev.Blog.Web.csproj @@ -1,7 +1,7 @@  - netcoreapp2.2 + netcoreapp3.1 InProcess Linux 9a1f51b6-f4d9-4df7-a0af-e345176e9927 @@ -24,8 +24,6 @@ - - diff --git a/src/TerribleDev.Blog.Web/Views/Home/Debug.cshtml b/src/TerribleDev.Blog.Web/Views/Home/Debug.cshtml index 5c4a4a1..4cc72a4 100644 --- a/src/TerribleDev.Blog.Web/Views/Home/Debug.cshtml +++ b/src/TerribleDev.Blog.Web/Views/Home/Debug.cshtml @@ -1,4 +1,4 @@ -@inject Microsoft.AspNetCore.Hosting.IHostingEnvironment env +@inject Microsoft.AspNetCore.Hosting.IWebHostEnvironment env @{ ViewData["Title"] = "Debug"; } diff --git a/src/TerribleDev.Blog.Web/Views/Home/Post.cshtml b/src/TerribleDev.Blog.Web/Views/Home/Post.cshtml index 7acadd5..727b67b 100644 --- a/src/TerribleDev.Blog.Web/Views/Home/Post.cshtml +++ b/src/TerribleDev.Blog.Web/Views/Home/Post.cshtml @@ -13,7 +13,7 @@ - + @@ -22,6 +22,7 @@ + @foreach(var image in Model.Images.Take(6)) { diff --git a/src/TerribleDev.Blog.Web/Views/Shared/PostSummary.cshtml b/src/TerribleDev.Blog.Web/Views/Shared/PostSummary.cshtml index d809ca3..5e9d9de 100644 --- a/src/TerribleDev.Blog.Web/Views/Shared/PostSummary.cshtml +++ b/src/TerribleDev.Blog.Web/Views/Shared/PostSummary.cshtml @@ -1,10 +1,10 @@ @model IPost \ No newline at end of file + Continue Reading + diff --git a/src/TerribleDev.Blog.Web/Views/Tags/AllTags.cshtml b/src/TerribleDev.Blog.Web/Views/Tags/AllTags.cshtml index dbe7a35..f24645e 100644 --- a/src/TerribleDev.Blog.Web/Views/Tags/AllTags.cshtml +++ b/src/TerribleDev.Blog.Web/Views/Tags/AllTags.cshtml @@ -12,4 +12,5 @@ @section Head { -} \ No newline at end of file + +} diff --git a/src/TerribleDev.Blog.Web/Views/Tags/GetTag.cshtml b/src/TerribleDev.Blog.Web/Views/Tags/GetTag.cshtml index 2611a3f..b4694f6 100644 --- a/src/TerribleDev.Blog.Web/Views/Tags/GetTag.cshtml +++ b/src/TerribleDev.Blog.Web/Views/Tags/GetTag.cshtml @@ -8,4 +8,10 @@ { } - \ No newline at end of file + + +@section Head { + @if(!String.IsNullOrEmpty(Model.CanonicalUrl)) { + + } +}