サービスの登録(DI)
それぞれのプロジェクトで、DIコンテナにサービスを登録します。
MAUI(MauiProgram.cs)
ImageGeneratorプロジェクトのMauiProgram.csには、次のように追加します。HttpClientFactoryを先に登録し、その後、MauiImageSaveServiceを登録します。
builder.Services.AddHttpClient(); // HttpClientFactoryの登録 builder.Services.AddSingleton<IImageSaveService, MauiImageSaveService>();
Web(Program.cs)
ImageGenerator.WebプロジェクトのProgram.csには、次のように追加します。
builder.Services.AddHttpClient(); // HttpClientFactoryの登録 builder.Services.AddSingleton<IImageSaveService, WebImageSaveService>();
Web側でもHttpClientFactoryを登録する必要があります。WebImageSaveServiceは、画像のダウンロードにHttpClientを使用するためです。
UIへの保存ボタン追加
画像保存サービスの実装が完了したら、UIに保存ボタンを追加します。ImageGenerator.SharedプロジェクトのComponents/Pages/ImageGeneration.razorを更新します。
サービスのインジェクション追加
ファイル冒頭のインジェクション部分に、IImageSaveServiceとIJSRuntimeを追加します。
@inject IImageGenerationService ImageService @inject IImageSaveService SaveService @inject IJSRuntime JSRuntime
IJSRuntimeは、Web環境での画像保存時にSaveServiceに渡すために必要です。MAUI環境では使用されませんが、共通のRazorコンポーネントとして定義するため、両環境でインジェクションしておきます。
HTML部分の更新
画像表示部分に保存ボタンを追加します。既存の画像表示部分を次のように変更します。
@if (!string.IsNullOrEmpty(imageUrl))
{
<div class="mt-3">
<img src="@imageUrl" class="img-fluid" alt="生成された画像" />
<div class="mt-2">
<button class="btn btn-success" @onclick="SaveImage" disabled="@isSaving">
@if (isSaving)
{
<span class="spinner-border spinner-border-sm"></span>
<span>@(SaveService.ActionLabel)中...</span>
}
else
{
<span>画像を@(SaveService.ActionLabel)</span>
}
</button>
</div>
@if (!string.IsNullOrEmpty(saveMessage))
{
<div class="alert @saveMessageClass mt-2">@saveMessage</div>
}
</div>
}
@(SaveService.ActionLabel)を使って、環境に応じた文言をボタンに設定します。また、保存(またはダウンロード)中はボタンを無効化し、スピナーを表示します。完了後は、結果に応じたメッセージを表示します。
C#コード部分の更新
@code部分に、今回の処理用の変数とメソッドを追加します。
private bool isSaving = false; // 処理中か否か
private string saveMessage = "";
private string saveMessageClass = "";
private async Task SaveImage()
{
if (string.IsNullOrEmpty(imageUrl)) return; // 画像が生成されていない場合は何もしない
isSaving = true;
saveMessage = "";
try
{
// タイムスタンプを使ったファイル名を生成 (1)
var fileName = $"generated_{DateTime.Now:yyyyMMdd_HHmmss}.png";
// IJSRuntimeを第3引数に渡す(Web環境で使用、MAUI環境では無視される)
var result = await SaveService.SaveImageAsync(imageUrl, fileName, JSRuntime);
if (result)
{
saveMessage = $"画像を{SaveService.ActionLabel}しました";
saveMessageClass = "alert-success";
}
else
{
saveMessage = $"画像の{SaveService.ActionLabel}に失敗しました";
saveMessageClass = "alert-danger";
}
}
catch (Exception ex)
{
saveMessage = $"エラー: {ex.Message}";
saveMessageClass = "alert-danger";
}
finally
{
isSaving = false;
}
}
ファイル名は、タイムスタンプを使って一意になるように生成しています(1)。SaveImageAsyncメソッドの第3引数にJSRuntimeを渡すことで、Web環境ではJavaScriptを使ったダウンロードが実行されます。MAUI環境では、この引数は無視され、FileSaverを使った保存処理が実行されます。また、環境に応じたメッセージとなるように、SaveService.ActionLabelプロパティを表示します。
動作確認
すべての実装が完了したら、各プラットフォームで動作を確認してみましょう。ボタンの文言や、完了メッセージも環境に応じた表示になっているはずです。
最後に
今回は、.NET MAUI Blazor Hybridアプリでのネイティブ機能連携として、画像保存機能を実装しました。.NET MAUIでは、このようにプラットフォーム固有の機能を、共通のインターフェイスで抽象化することで、コードの再利用性を高めながら、各プラットフォームの特性を活かした実装が可能です。
次回は、APIキーをソースコードに埋め込むのではなく、サーバーから安全に取得する方式について解説する予定です。
