Created
July 2, 2021 11:02
-
-
Save warrenbuckley/1bb92a37f7b75d5ab9c9204406e6ed6b to your computer and use it in GitHub Desktop.
Automatically Generate Open Graph Social Images in Umbraco V9
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@using Umbraco.Cms.Web.Common.PublishedModels; | |
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<ContentModels.BlogPost> | |
@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels; | |
@{ | |
Layout = null; | |
} | |
<html> | |
<head> | |
<meta property="og:title" content="@Model.Header" /> | |
<meta property="og:image" content="/img/og/@(Model.Key).png" /> | |
</head> | |
<body> | |
<h1>@Model.Header</h1> | |
@Model.BlogContent | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using PuppeteerSharp; | |
using System.IO; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using Umbraco.Cms.Core.Events; | |
using Umbraco.Cms.Core.Logging; | |
using Umbraco.Cms.Core.Models.PublishedContent; | |
using Umbraco.Cms.Core.Notifications; | |
using Umbraco.Cms.Core.Routing; | |
namespace Umbraco.OpenGraph.Images | |
{ | |
public class GenerateOpenGraph : INotificationAsyncHandler<ContentPublishingNotification> | |
{ | |
private IPublishedUrlProvider _publishedUrlProvider; | |
private IProfilingLogger _profilingLogger; | |
public GenerateOpenGraph(IPublishedUrlProvider publishedUrlProvider, IProfilingLogger profilingLogger) | |
{ | |
_publishedUrlProvider = publishedUrlProvider; | |
_profilingLogger = profilingLogger; | |
} | |
public async Task HandleAsync(ContentPublishingNotification notification, CancellationToken cancellationToken) | |
{ | |
using (System.Threading.ExecutionContext.SuppressFlow()) | |
{ | |
foreach (var node in notification.PublishedEntities) | |
{ | |
var filename = node.Key.ToString(); | |
var pageUrl = _publishedUrlProvider.GetUrl(node.Key, UrlMode.Absolute); | |
var altTemplateUrl = $"{pageUrl}?altTemplate=opengraph"; | |
// Fire & forget | |
Task.Run(() => GenerateImagesAsync(filename, altTemplateUrl)); | |
} | |
} | |
} | |
public async Task GenerateImagesAsync(string filename, string pageUrl) | |
{ | |
// Puppetter - Will fetch Chrome | |
// Will reuse Chrome binary once downloaded first time | |
using (_profilingLogger.DebugDuration<GenerateOpenGraph>("Downloading Chrome with Puppeteer")) | |
{ | |
// EEK first download is about 22seconds | |
// Once downloaded it uses one from on disk to save time | |
var browserFetcher = new BrowserFetcher(); | |
await browserFetcher.DownloadAsync(); | |
} | |
await using var browser = await Puppeteer.LaunchAsync( | |
new LaunchOptions { Headless = true } | |
); | |
using (_profilingLogger.DebugDuration<GenerateOpenGraph>("Generating Open Graph Image for {PageUrl}", pageUrl)) | |
{ | |
// Generate an image on disk | |
// Use convention and store image URL at /img/og/{key}.png | |
// Use puppetter (headless chrome) to view the published page (alternative template) | |
// /my-blog-post?altTemplate=opengraph | |
// Get Puppetter to take a screenshot of the page and save as PNG | |
if(Directory.Exists("img/og") == false) | |
Directory.CreateDirectory("img/og"); | |
var screenshotFileName = "img/og/" + filename + ".png"; | |
using var page = await browser.NewPageAsync(); | |
await page.GoToAsync(pageUrl); | |
await page.ScreenshotAsync(screenshotFileName); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@using Umbraco.Cms.Web.Common.PublishedModels; | |
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<ContentModels.BlogPost> | |
@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels; | |
@{ | |
Layout = null; | |
} | |
<html> | |
<head> | |
<link rel="preconnect" href="https://fonts.googleapis.com"> | |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@100;400;900&display=swap" rel="stylesheet"> | |
<style> | |
body { | |
font-family: 'Montserrat', sans-serif; | |
background: #3644B1; | |
padding:15px; | |
} | |
h1 { | |
font-size:50px; | |
fontw-weight:900; | |
color:white; | |
margin:0; | |
} | |
h2 { | |
margin:0; | |
color:#F1BEBB; | |
font-weight:400; | |
} | |
</style> | |
</head> | |
<body> | |
<div> | |
<h1>@Model.Header</h1> | |
<h2>5 minute read</h2> | |
</div> | |
</body> | |
</html> | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.AspNetCore.Builder; | |
using Microsoft.AspNetCore.Hosting; | |
using Microsoft.Extensions.Configuration; | |
using Microsoft.Extensions.DependencyInjection; | |
using Microsoft.Extensions.Hosting; | |
using System; | |
using Umbraco.Cms.Core.DependencyInjection; | |
using Umbraco.Cms.Core.Notifications; | |
using Umbraco.Extensions; | |
using Umbraco.OpenGraph.Images; | |
namespace SocialPuppet | |
{ | |
public class Startup | |
{ | |
private readonly IWebHostEnvironment _env; | |
private readonly IConfiguration _config; | |
/// <summary> | |
/// Initializes a new instance of the <see cref="Startup"/> class. | |
/// </summary> | |
/// <param name="webHostEnvironment">The Web Host Environment</param> | |
/// <param name="config">The Configuration</param> | |
/// <remarks> | |
/// Only a few services are possible to be injected here https://github.com/dotnet/aspnetcore/issues/9337 | |
/// </remarks> | |
public Startup(IWebHostEnvironment webHostEnvironment, IConfiguration config) | |
{ | |
_env = webHostEnvironment ?? throw new ArgumentNullException(nameof(webHostEnvironment)); | |
_config = config ?? throw new ArgumentNullException(nameof(config)); | |
} | |
/// <summary> | |
/// Configures the services | |
/// </summary> | |
/// <remarks> | |
/// This method gets called by the runtime. Use this method to add services to the container. | |
/// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 | |
/// </remarks> | |
public void ConfigureServices(IServiceCollection services) | |
{ | |
#pragma warning disable IDE0022 // Use expression body for methods | |
services.AddUmbraco(_env, _config) | |
.AddBackOffice() | |
.AddWebsite() | |
.AddComposers() | |
.AddNotificationAsyncHandler<ContentPublishingNotification, GenerateOpenGraph>() // Added this to register this with Umbraco | |
.Build(); | |
#pragma warning restore IDE0022 // Use expression body for methods | |
} | |
/// <summary> | |
/// Configures the application | |
/// </summary> | |
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) | |
{ | |
if (env.IsDevelopment()) | |
{ | |
app.UseDeveloperExceptionPage(); | |
} | |
app.UseUmbraco() | |
.WithMiddleware(u => | |
{ | |
u.WithBackOffice(); | |
u.WithWebsite(); | |
}) | |
.WithEndpoints(u => | |
{ | |
u.UseInstallerEndpoints(); | |
u.UseBackOfficeEndpoints(); | |
u.UseWebsiteEndpoints(); | |
}); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Proof of Concept
This works in the same approach as how GitHub is creating their new OpenGraph social images by using Puppetter to use a headless browser to take a screenshot/image of a HTML page that contains the information
https://github.com/puppeteer/puppeteer/
As Puppeteer is an NPM package and a bit of googling and there is an upto date/maintained C# library of this
https://github.com/hardkoded/puppeteer-sharp
Idea
This works by doing the following: