1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【.NET8】MagicOnionでAPIサーバを立てる

Last updated at Posted at 2025-03-09

概要

C#でAPIサーバを立てる際に、どうせクライアントもC#で作るのでC#のインターフェースを共有してうまいこと通信できないだろうかと思う瞬間がきっとあると思います(?)。そんなときに活躍するのがCySharp社が開発しているMagicOnionというライブラリです。
MagicOnionは通信にgRPCを採用しており、非常に高速な通信を行うことができます。また、C#のインターフェースを使用してコーディングするので、protoファイルの管理なども行う必要がなく非常にスムーズな開発を行うことができます。
ちなみに、gRPCだけでなく普通にRESTのサーバとしても使えるらしいので、それはまた別で記事にしようと思います。今回はシンプルにMagicOnionをAPIサーバ的な使い方で動かす方法を説明していきます。

なお、今回書いたコードは以下のリポジトリに置いておきますので、参考にしていただければと思います。
https://github.com/Uta-member/SampleProject.MO

環境

  • VisualStudio 2022 Community
  • .NET8
  • C# 13
  • MagicOnion 7.0.2

インターフェースの作成

まず、サーバとクライアント間で共有するインターフェースを定義します。このインターフェースを通して通信を行うわけですね。

プロジェクトの準備

プロジェクトの作成

テンプレートはクラス ライブラリを選択します。
image.png

プロジェクト名は何でもいいです。なんとなくインターフェースの部分というのがわかる名前にしたほうがいいとは思います。
image.png

パッケージのインストール

インターフェースのプロジェクトには以下のパッケージをインストールします

  • MagicOnion.Abstractions

image.png

フォルダの作成

別に必須ではありませんが、ModelsServicesというフォルダを作成して分けるといいと思います。
image.png

モデルの作成

データをやり取りする際に使用するクラスをModelsに作成していきます。
とりあえずユーザのIDと名前を持ったクラスを作成してみます。

MPUser.cs
using MessagePack;

namespace SampleProject.MO.Interface
{
    // MagicOnionの通信で使用するクラスには必ずMessagePackObject属性を付ける
    [MessagePackObject]
    public sealed record MPUser
    {
        // プロパティにはKey属性を付けて、0から順番に数字を振っていく
        
        [Key(0)]
        public required string Id { get; set; }

        [Key(1)]
        public required string Name { get; set; }
    }
}

頭のMPはMessagePackのクラスというのを明示したくてつけてます。ドメインオブジェクトとかと混在したら嫌なので。。
sealed recordは筆者が好んで使っているだけなので、別に普通のclassでも大丈夫です。requiredもつけておいたほうがコーディングは楽になることが多いので、必須のプロパティにはつけてます。

サービスの作成

モデルができたのでこれを使ってインターフェースをServicesフォルダに作成してみましょう。

IMOUserService.cs
using MagicOnion;

namespace SampleProject.MO.Interface
{
    public interface IMOUserService : IService<IMOUserService>
    {
        UnaryResult<MPUser?> FindUserByUserId(string userId);

        UnaryResult RegisterUser(MPUser user);
    }
}

MOが頭についているのはMagicOnionのサービスというのを明示するためになんとなくつけてるだけです。なくても大丈夫です。サービスのインターフェースは必ずIServiceインターフェースを実装し、型引数に自分自身を指定します。MagicOnionのメソッドは必ず戻り値をUnaryResultにします。また、引数や戻り値に設定できるのはC#のプリミティブ型とMessagePackObject属性のついたクラスだけです。ちなみに、クラス自体にMessagePackObject属性がついていても、そのクラスのプロパティにMessagePackObject属性がついていないクラスを使用してしまうのもダメなので、やり取りに使用されるクラスはすべてMessagePackObject属性を付ける必要があります。

これでインターフェース側の準備は完了です。

サーバの実装

サーバ側の実装を行っていきます。まずはプロジェクトの準備を行いましょう。

プロジェクトの準備

プロジェクトの作成

プロジェクトのテンプレートにはASP.NET Core gRPC サービスを使用します。
image.png

プロジェクト名は何でもOKです。
image.png

フレームワークは.NET 8.0を指定して作成します。
image.png

参照の設定

先ほど作成したインターフェースのプロジェクトを参照します。
image.png

パッケージの追加

以下のパッケージをインストールします。

  • MagicOnion.Server

image.png

不要なフォルダとファイルを削除

Protosフォルダ自体とServicesフォルダ内のファイルをすべて削除します。
image.png

Program.csの変更

Program.csを以下のように修正します。

Program.cs
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddGrpc();
builder.Services.AddMagicOnion();

var app = builder.Build();

app.MapMagicOnionService();

app.MapGet(
    "/",
    () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");

app.Run();

サービスの実装

インターフェースのプロジェクトに作成したサービスのインターフェースを実装します。実装クラスはServicesフォルダに作成しましょう。
以下のように、ServiceBaseを継承してインターフェースを実装すればOKです。ServiceBaseの型引数には実装するサービスのインターフェースを渡してください。UnaryResultはTaskなどと同じく非同期を扱えるクラスなので、メソッド内でawaitなども使用できます。

MOUserService.cs
using MagicOnion;
using SampleProject.MO.Interface;

namespace SampleProject.MO.APIServer.Services
{
    public sealed class MOUserService : ServiceBase<IMOUserService>, IMOUserService
    {
        public UnaryResult<MPUser?> FindUserByUserId(string userId)
        {
            // DBなどから情報を取得して返す
            // これはサンプルなので適当にコンストラクタを呼んで返してます
            return UnaryResult.FromResult<MPUser?>(
                new MPUser() 
                { 
                    Id = userId,
                    Name = "ユーザの名前",
                });
        }

        public UnaryResult RegisterUser(MPUser user)
        {
            // DBに登録をする
            // これはサンプルなので何もしてません
            return UnaryResult.CompletedResult;
        }
    }
}

サーバ側の実装はこれで完了です。

クライアントの実装

今回はとりあえず簡単に実装したいのでコマンドラインアプリで実装しますが、Blazorのサーバ側やWinForms、MAUI、WPFなどでも使用できます。ちなみに、BlazorWebAssemblyなどのようなブラウザ上で動くコードではそのまま使えません。ブラウザ上で動かすためにはgRPC-Webというものを使用する必要があり、サーバ側にも設定が必要なので、それはまた別の記事で解説しようと思います。

プロジェクトの準備

プロジェクトの作成

今回はコンソールアプリを作成したいので、テンプレートはコンソール アプリを使用します。
image.png

プロジェクト名は何でもOKです。
image.png

フレームワークは.NET 8.0を選択して作成します。
image.png

プロジェクト参照

インターフェースのプロジェクトを参照に追加します。
image.png

パッケージの追加

以下のパッケージをインストールします。

  • MagicOnion.Client

image.png

APIの呼び出し

サーバのIPアドレスが必要なので、サーバ側のプロジェクトのProperties\launchSettings.jsonを開き、IPアドレスを確認しておきましょう。
applicationUrlのhttpsのほうです。httpで動かす場合はhttpのほうを確認します。
image.png

クライアント側のProgram.csを以下のように変更します。

Program.cs
using Grpc.Net.Client;
using MagicOnion.Client;
using SampleProject.MO.Interface;

// サーバのIPを入れる
// 実際のアプリではDIコンテナに入れて共有などを行うと思う
GrpcChannel channel = GrpcChannel.ForAddress("https://localhost:7022");

// チャンネルを使ってサービスのクライアントを作成する
IMOUserService userService = MagicOnionClient.Create<IMOUserService>(channel);

// 実行
var userResult = userService.FindUserByUserId("userId");
var user = userResult.GetAwaiter().GetResult();

// 普通にawaitも使えます
// var user = await userService.FindUserByUserId("userId");

if(user != null)
{
    Console.WriteLine($"UserId: {user.Id}");
    Console.WriteLine($"UserName: {user.Name}");
}
else
{
    Console.WriteLine("ユーザが見つかりませんでした");
}

Console.ReadLine();

実行してみる

準備

念のため書いておきますが、VisualStudioで複数のプロジェクトを一気にデバッグするには、[ソリューションを右クリック] - [スタートアップ プロジェクトの構成]を開き、マルチスタートアッププロジェクトで立ち上げたいプロジェクトのアクションを開始にしておくことで複数起動ができます。
image.png

image.png

実行

実行するとこんな感じでちゃんとサーバ側で書いたモデルの情報が返ってきます。
image.png

最後に

以上、MagicOnionで最低限通信を行うための方法でした。非常に高速で動作しますし通信のインターフェースがそのままC#のインターフェースなのでコーディングも非常に簡単で型安全ですね。
ただ、実際に大きめのプロジェクトで採用していますが、以下のような問題点があります。

  • MessagePackObject属性書き忘れやすい(現状、LINTとかで頑張る?)
  • OptionalやImmutableのようなよく使うけどプリミティブではない汎用的なクラスをそのまま使うとエラーになるので、それらもMessagePackObjectにしないといけない(シリアライザが存在していたりはするのでそれらを活用する)
  • テストがRESTほど気楽にできない(Swaggerでデバッグできる環境を作れるパッケージが公式から出ているので、それで対応する)
  • そのままだとブラウザ上で動作するコードから呼び出せない(gRPC-Webで通信すればできる)
  • gRPCを使いたくないクライアントもある(RESTに変換できるパッケージがある)

関連リンク

MagicOnionはCySharpがゲーム開発で使用するために開発したものなのでゲーム開発向けとして書かれている記事が多い気がしますが、C#のAPIは基本的にこれでいいと感じるほどよくできているパッケージなので、ぜひ人口が増えたらうれしいなと思っています

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?