SHOEISHA iD

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

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

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

.NET MAUIアプリ構築で、より安全なプロキシ方式を実装する

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

 本連載では、.NET 9で登場した新しいソリューションテンプレートである「.NET MAUI Blazor HybridアプリとWebアプリ」について解説します。前回は、.NET MAUIのネイティブ機能を活用して、生成した画像をデバイスに保存する機能を実装しました。今回は、より安全な方式として、サーバー側でOpenAI APIを呼び出し、クライアントには結果だけを返す「プロキシ方式」を実装します。この方式では、APIキーはサーバー側で管理され、クライアントに漏れることはありません。また最後に、本連載では詳細まで踏み込めなかったWebアプリのレンダリングモードについて、補足としてまとめておきます。

はじめに

 昨年からお届けしてきた本連載も、いよいよ最終回です。前回は、APIキーをサーバーから取得し、SecureStorageに保存する方式を実装しました。しかし、この方式にはまだ課題があります。APIキーがモバイルアプリに渡されるため、通信の傍受やアプリの解析によってキーが漏洩するリスクが残っています。

 今回は、より安全な方式として、サーバー側でOpenAI APIを呼び出し、クライアントには結果だけを返す「プロキシ方式」を実装します。この方式では、APIキーはサーバー側で管理され、クライアントに漏れることはありません。また最後に、本連載では詳細まで踏み込めなかったWebアプリのレンダリングモードについて、補足としてまとめておきます。

今回の変更内容

 アーキテクチャを次のように変更します。

プロキシ方式のアーキテクチャ
プロキシ方式のアーキテクチャ

 MAUI版では、サーバーに画像生成をリクエストし、サーバーがOpenAI APIを呼び出して結果を返します。Web版は、すでにOpenAIImageServiceをサーバーサイドで実行する形になっているため、構成の変更は必要ありません。

画像生成APIエンドポイントの作成

 まず、サーバー側に画像生成用のAPIエンドポイントを作成します。

リクエスト・レスポンスDTOの定義

 ImageGenerator.SharedプロジェクトにModelsフォルダを作成し、APIのリクエスト・レスポンスとエラー応答を表す3つのDTOクラスを定義します。

 DTO(Data Transfer Object)とは、データをやり取りするためだけのシンプルなデータクラスのことです。いずれも同じエンドポイントに関連するので、1つのファイルにまとめて作成します。

[リスト1]ImageGenerationDtos.cs
// 画像生成のリクエスト(プロンプト、品質、サイズ)
public record ImageGenerationRequest(string Prompt, int Quality, int Size);

// 画像生成の成功応答(生成された画像のURL)
public record ImageGenerationResponse(string ImageUrl);

// エラー応答(ASP.NET Core標準のProblemDetailsからdetailだけを受け取る)
public record ProblemResponse(string? Detail);

 これらは、Sharedプロジェクトに配置することで、サーバー側とMAUI側の両方から参照できます。

 ImageGenerationRequestはリクエスト用で、前回までにUIから指定できるようにしたプロンプト、品質、サイズをセットします。ImageGenerationResponseは成功時の応答用で、画像URLのみを返します(本連載ではDALL-E 3をURL形式で利用しています)。

 エラーの有無はHTTPステータスコードで判断します。エラー時の応答は、サーバーがASP.NET Core標準のProblemDetails形式で返すので、クライアント側では、そのうちのdetailプロパティだけをProblemResponseオブジェクトにセットするようにします。

 なお、ここで使っているrecord型はC# 9以降の機能です。record型なら、コンストラクタと読み取り専用プロパティを持つデータ用クラスをシンプルな形で定義できます。

エンドポイントの実装とサービス登録

 ImageGenerator.WebプロジェクトのProgram.csに、画像生成サービスをDIコンテナに登録し、画像生成エンドポイントを追加します。

[リスト2]Program.csの一部
// 画像生成サービスの登録 (1)
builder.Services.AddSingleton<IImageGenerationService, OpenAIImageService>();

// 画像生成APIエンドポイントの追加 (2)
app.MapPost("/api/generate-image", async (
    ImageGenerationRequest request, IImageGenerationService imageService,
    ILogger<Program> logger) =>
{
    // プロンプトの検証 (3)
    if (string.IsNullOrWhiteSpace(request.Prompt))
    {
        return Results.Problem(
            detail: "プロンプトを入力してください",
            statusCode: StatusCodes.Status400BadRequest);
    }

    try
    {
        // OpenAI APIを呼び出して画像を生成 (4)
        var imageUrl = await imageService.GenerateImageAsync(
             request.Prompt, request.Quality, request.Size);
        return Results.Ok(new ImageGenerationResponse(imageUrl));
    }
    catch (Exception ex)
    {
        //  詳細はサーバーログに残し、クライアントには汎用メッセージを返す (5)
        logger.LogError(ex, "画像生成中にエラーが発生しました");
        return Results.Problem(
            detail: "画像の生成中にエラーが発生しました",
            statusCode: StatusCodes.Status500InternalServerError);
    }
});

 まず、IImageGenerationServiceとしてOpenAIImageServiceをDIコンテナに登録します(1)。この登録は第3回から同様です。エンドポイント内では、この登録を通じてサービスを注入して利用します。

 MapPostメソッドで、POSTリクエストを受け付けるエンドポイントを定義します(2)。リクエストボディのJSONは、自動的にImageGenerationRequest型にバインドされます。

 リクエストのプロンプト文が空の場合は、Results.Problemメソッドでステータスコード400(Bad Request)のエラー応答を返します(3)。Results.Problemは、ASP.NET Coreのヘルパーメソッドで、ProblemDetails形式のエラー応答を生成します。レスポンスボディとして、detail、status、titleといったプロパティを含むJSONが返されます。

 正常な場合は、DIで注入されたIImageGenerationServiceを使って画像を生成します。このとき、プロンプトだけでなく、品質とサイズの指定もOpenAIImageServiceに渡します(4)。Results.Okメソッドは、ステータスコード200を返し、レスポンスボディに、ImageGenerationResponseオブジェクトをJSON形式でセットします。

 OpenAI APIの呼び出し中に例外が発生した場合は、サーバー側のログには詳細を記録しつつ、クライアントには汎用的なメッセージだけを500 Internal Server Errorで返します(5)。例外メッセージをそのまま外部に表示しないことで、APIキー無効や課金エラーといった内部情報の漏洩を防ぎます。

次のページ
MAUI用プロキシサービスの実装

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

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

もっと読む

この記事の著者

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

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。 2026年時点での登録メンバは約50名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 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本格入門」「これからはじめるLaravel実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

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

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

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/24585 2026/06/26 09:00

おすすめ

アクセスランキング

アクセスランキング

イベント

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

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

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

メールバックナンバー

アクセスランキング

アクセスランキング