Web:画像ダウンロードの実装(JS連携)
次に、Webプロジェクトの実装です。Web環境では、ローカルにファイルを保存する場合、ブラウザの機能を使う必要があります。
IJSRuntimeとは
今回は、ブラウザの機能を使うためにJavaScriptを利用します。BlazorからJavaScript関数を呼び出すには、IJSRuntimeインターフェイスを使用します。InvokeVoidAsyncメソッドで、戻り値のないJavaScript関数を、InvokeAsyncメソッドで、戻り値のある関数を非同期で呼び出せます。
Blazor ServerでのIJSRuntime利用時の注意
Blazor ServerアプリケーションでIJSRuntimeを使用する際は、注意が必要です。IJSRuntimeはSignalR接続に紐づいており、サービスクラスのコンストラクタでインジェクションすると、接続のライフサイクルとの不整合でエラーが発生することがあります。
この問題を回避するため、今回の実装では、IJSRuntimeをサービスのコンストラクタではなく、メソッドの引数として渡す設計にしています。RazorコンポーネントでインジェクションしたIJSRuntimeをメソッド呼び出し時に渡すことで、適切なタイミングでJavaScriptを実行できます。
実装クラスの作成
ImageGenerator.WebプロジェクトのServicesフォルダに、WebImageSaveServiceクラスを新規作成します。
using Microsoft.JSInterop;
public class WebImageSaveService(IHttpClientFactory httpClientFactory) : IImageSaveService
{
public string ActionLabel => "ダウンロード"; // UI文言の設定 (5)
public async Task<bool> SaveImageAsync(string imageUrl, string fileName, object? jsRuntime = null)
{
// jsRuntimeがnullまたはIJSRuntimeでない場合はエラー (1)
if (jsRuntime is not IJSRuntime js)
{
throw new ArgumentException( "Web環境ではIJSRuntimeが必要です");
}
try
{
// C#側で画像をダウンロード(CORS制限を回避)(2)
using var httpClient = httpClientFactory.CreateClient();
var imageBytes = await httpClient.GetByteArrayAsync(imageUrl);
// Base64に変換 (3)
var base64 = Convert.ToBase64String(imageBytes);
var dataUrl = $"data:image/png;base64,{base64}";
// Base64データURLをJavaScriptに渡してダウンロード (4)
await js.InvokeVoidAsync("downloadImageFromDataUrl", dataUrl, fileName);
return true;
}
catch (Exception ex)
{
Console.WriteLine($"画像ダウンロードエラー: {ex.Message}");
return false;
}
}
}
SaveImageAsyncメソッドの引数として受け取ったjsRuntimeを、is構文を使って型チェックとIJSRuntime型として変数宣言を行っています(1)。
Web環境ではIJSRuntimeが必須のため、nullや異なる型の場合は例外をスローします。IJSRuntime型の場合のみ、変数jsが定義されます。そして、IHttpClientFactoryを通じて画像データをダウンロードします(2)。
ここで重要なのは、JavaScriptの関数ではなく、C#のHttpClientを使っている点です。OpenAI APIから返される画像URLに対して、JavaScriptから直接fetchするとCORS(Cross-Origin Resource Sharing)制限によりエラーが発生します。サーバーサイドのC#コードで画像を取得することで、この制限を回避できます。
取得した画像データはBase64文字列に変換し(3)、データURL形式でJavaScriptに渡してダウンロードを実行します(4)。また、UIの文言も定義しておきます(5)。
JavaScript関数の追加
ImageGenerator.WebプロジェクトのComponents/App.razorに、ダウンロード用のJavaScript関数を追加します。</body>タグの直前に記述してください。
<script>
// データURLから直接ダウンロードリンクを作成
function downloadImageFromDataUrl(dataUrl, fileName) {
const a = document.createElement('a');
a.href = dataUrl;
a.download = fileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
</script>
</body>
この関数は、Base64形式のデータURLを受け取り、動的に作成したアンカー要素を使ってダウンロードを実行します。fetchを使用しないため、CORS制限の影響を受けません。
