Blazor Server でWeb画面やドキュメントを画像データに変換し、ダウンロードする機能を実装する場合、HTMLのテンプレートファイルを用意し、wkhtmltoimage で画像データに変換してからダウンロードするのがベターです。
ソースコードはGitHubで公開しています。
CoreHtmlToImage Nugetパッケージ について
Windows の場合、wkhtmltoimage の exeをラップしている CoreHtmlToImage Nugetパッケージを使うと簡単にwkhtmltoimageを使用できます。
CoreHtmlToImage NugetパッケージのソースコードはGitHubで公開されている。
CoreHtmlToImage は、wkhtmltoimage.exe を Process.Start() から実行することで画像データを生成している。
こちらで試した限りでは、Linux環境だと CoreHtmlToImage は上手く動かなかったので、Windows環境向けの Nugetパッケージとして使っています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
private static string toolFilename = "wkhtmltoimage"; private static string directory; private static string toolFilepath; static HtmlConverter() { directory = AppContext.BaseDirectory; //Check on what platform we are if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) { toolFilepath = Path.Combine(directory, toolFilename + ".exe"); if (!File.Exists(toolFilepath)) { var assembly = typeof(HtmlConverter).GetTypeInfo().Assembly; var type = typeof(HtmlConverter); var ns = type.Namespace; using (var resourceStream = assembly.GetManifestResourceStream($"{ns}.{toolFilename}.exe")) using (var fileStream = File.OpenWrite(toolFilepath)) { resourceStream.CopyTo(fileStream); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
if (IsLocalPath(url)) { args = $"--quality {quality} --width {width} -f {imageFormat} \"{url}\" \"{filename}\""; } else { args = $"--quality {quality} --width {width} -f {imageFormat} {url} \"{filename}\""; } Process process = Process.Start(new ProcessStartInfo(toolFilepath, args) { WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true, UseShellExecute = false, WorkingDirectory = directory, RedirectStandardError = true }); process.ErrorDataReceived += Process_ErrorDataReceived; process.WaitForExit(); |
wkhtmltoimage について
wkhtmltoimage.exe は wkhtmltopdf サイトのダウンロードページからダウンロードできる。
よく使われるものを赤枠で囲った。
wkhtmltoimage.exe は wkhtmltopdf に含まれる1機能という扱いになっている。
wkhtmltopdf はGitHubでソースコードが公開されている。
wkhtmltoimageが画像データを生成する際のフォントは、OSのフォント(C:\Windows\Fonts)を使っている。
ソースコード構成
Blazor Server でファイルをダウンロードする をベースに、画像データ生成処理、 画像データダウンロード処理を追加しています。
ソースコード変更内容を解説
WebApplication1.csproj
・CoreHtmlToImage Nugetパッケージを追加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net5.0</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="CoreHtmlToImage" Version="1.0.6" /> </ItemGroup> <ItemGroup> <None Update="Download\Question.jpg"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> </ItemGroup> </Project> |
Template/Html/Counter.html
・Counter.razor に記述されているHTML部分をコピーし、HTMLファイルとして利用可能な形にしたテンプレートファイルとして追加。
・{0} はスタイル(CSS)に置換する箇所。
・{1} は「Click me」ボタンがクリックされた値で置換する箇所。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> </head> <body> {0} <h1>Counter</h1> <p>Current count: {1}</p> </body> </html> |
・Counter.razor の一部ソースコードをコピーすることで、 Counter.razor の画面を画像データに変換する際のテンプレートとしている。
1 2 3 4 5 |
<h1>Counter</h1> <p>Current count: @currentCount</p> |
Template/Html/style.html
・site.css をまるコピーし、Counter.html で利用できるよう、<style>で囲っている。
・今回は、Web画面をダウンロードする機能なので、Webサイトの site.css をそのまま使っている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
<style type="text/css"> @import url('open-iconic/font/css/open-iconic-bootstrap.min.css'); html, body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; } a, .btn-link { color: #0366d6; } .btn-primary { color: #fff; background-color: #1b6ec2; border-color: #1861ac; } .content { padding-top: 1.1rem; } .valid.modified:not([type=checkbox]) { outline: 1px solid #26b050; } .invalid { outline: 1px solid red; } .validation-message { color: red; } #blazor-error-ui { background: lightyellow; bottom: 0; box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); display: none; left: 0; padding: 0.6rem 1.25rem 0.7rem 1.25rem; position: fixed; width: 100%; z-index: 1000; } #blazor-error-ui .dismiss { cursor: pointer; position: absolute; right: 0.75rem; top: 0.5rem; } </style> |
SharedData/TemplateData.cs
・テンプレートファイルを読み込んだデータは、Webアプリ全体から参照できるようにstaticデータとしている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
namespace WebApplication1.SharedData { /// <summary> /// アプリケーション全体で共有するパス情報などを纏めるクラス /// </summary> public static class TemplateData { /// <summary> /// Counter画面のHtml /// </summary> public static string CounterHtml; /// <summary> /// HtmlのStyle(CSS) /// </summary> public static string StyleCSS; } } |
Startup.cs
・テンプレートファイルの読み込みはWebアプリ起動時に1回だけ行い、画像変換する度に読み込まいことでパフォーマンスを上げている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using WebApplication1.Data; using WebApplication1.SharedData; namespace WebApplication1 { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // 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 public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); services.AddServerSideBlazor(); services.AddSingleton<WeatherForecastService>(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { // on Windows FolderPath.Download = env.WebRootPath + "\\..\\Download"; FolderPath.TemplateFolderPath_Html = env.WebRootPath + "\\..\\Template\\Html"; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { // on Linux FolderPath.Download = env.WebRootPath + "/../Download"; } else { throw new NotImplementedException(); } // テンプレートファイルをstaticデータとしてアプリ起動時に読込み、キャッシュデータとして使い回すことでパフォーマンスを上げる。 var htmlFileName = "Counter.html"; var styleFileName = "style.html"; TemplateData.CounterHtml = File.ReadAllText(Path.Combine(FolderPath.TemplateFolderPath_Html, htmlFileName)); TemplateData.StyleCSS = File.ReadAllText(Path.Combine(FolderPath.TemplateFolderPath_Html, styleFileName)); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); } app.UseStaticFiles(); app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapBlazorHub(); endpoints.MapFallbackToPage("/_Host"); }); } } } |
Pages/Counter.razor
・「Image file download」ボタンの処理で、HTMLテンプレートファイル内のデータ部を置換し、 置換後のHTMLを画像ファイルに変換し、画像ファイルとしてダウンロードしている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
@page "/counter" @using CoreHtmlToImage @inject IJSRuntime JS <h1>Counter</h1> <p>Current count: @currentCount</p> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> <br> <br> <button class="btn btn-primary" @onclick="DownloadFileExec_Image">Image file download</button> @code { private int currentCount = 0; private static HtmlConverter _HtmlConverter = new HtmlConverter(); private void IncrementCount() { currentCount++; } private async Task DownloadFileExec_Image() { var html = string.Format(TemplateData.CounterHtml, TemplateData.StyleCSS, currentCount); var imageData = _HtmlConverter.FromHtmlString(html, 800, ImageFormat.Jpg, 100); var filename = "Counter.jpg"; await JS.InvokeVoidAsync("saveAsFile", filename, Convert.ToBase64String(imageData)); } } |
コメント