SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

マルチターゲットアプリ開発の新しいアプローチ~.NET 9 新テンプレートの基本~

.NET MAUI Blazorで生成した画像を保存する。ネイティブ機能との連携

マルチターゲットアプリ開発の新しいアプローチ~.NET 9 新テンプレートの基本~ 第5回

サービスの登録(DI)

 それぞれのプロジェクトで、DIコンテナにサービスを登録します。

MAUI(MauiProgram.cs)

 ImageGeneratorプロジェクトのMauiProgram.csには、次のように追加します。HttpClientFactoryを先に登録し、その後、MauiImageSaveServiceを登録します。

[リスト7]MauiProgram.csの一部
builder.Services.AddHttpClient();  // HttpClientFactoryの登録
builder.Services.AddSingleton<IImageSaveService, MauiImageSaveService>();

Web(Program.cs)

 ImageGenerator.WebプロジェクトのProgram.csには、次のように追加します。

[リスト8]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を追加します。

[リスト9]ImageGeneration.razorの一部
@inject IImageGenerationService ImageService
@inject IImageSaveService SaveService
@inject IJSRuntime JSRuntime

 IJSRuntimeは、Web環境での画像保存時にSaveServiceに渡すために必要です。MAUI環境では使用されませんが、共通のRazorコンポーネントとして定義するため、両環境でインジェクションしておきます。

HTML部分の更新

 画像表示部分に保存ボタンを追加します。既存の画像表示部分を次のように変更します。

[リスト10]ImageGeneration.razorの一部
@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部分に、今回の処理用の変数とメソッドを追加します。

[リスト11]ImageGeneration.razorの一部
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プロパティを表示します。

動作確認

 すべての実装が完了したら、各プラットフォームで動作を確認してみましょう。ボタンの文言や、完了メッセージも環境に応じた表示になっているはずです。

Webブラウザでの画像ダウンロード
Webブラウザでの画像ダウンロード
Androidでの画像保存
Androidでの画像保存

最後に

 今回は、.NET MAUI Blazor Hybridアプリでのネイティブ機能連携として、画像保存機能を実装しました。.NET MAUIでは、このようにプラットフォーム固有の機能を、共通のインターフェイスで抽象化することで、コードの再利用性を高めながら、各プラットフォームの特性を活かした実装が可能です。

 次回は、APIキーをソースコードに埋め込むのではなく、サーバーから安全に取得する方式について解説する予定です。

この記事は参考になりましたか?

マルチターゲットアプリ開発の新しいアプローチ~.NET 9 新テンプレートの基本~連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 高江 賢(タカエ ケン)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/23143 2026/02/03 08:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング