Framework
Cysharp/MagicOnion avatar
Cysharp/MagicOnion

MagicOnion puts the gRPC transport under C# interfaces instead of .proto files

Unified Realtime/API framework for .NET platform and Unity.

4,458 stars458 forksC#MIT

At a glance

What is it?
Cysharp/MagicOnion is an RPC and realtime framework for .NET and Unity. It rides on gRPC, but the contract is a C# interface shared between projects, and a StreamingHub service covers the push side. The quick start in the README covers only the request and response half.
Who is it for?
MagicOnion earns a place in a .NET shop that wants one interface to serve a WPF client, an ASP.NET service and a Unity build without maintaining an IDL file. Before committing, read the realtime and authentication pages on the documentation site, since the README covers neither, and check return types separately: unary service methods use UnaryResult, while StreamingHub methods support Task, ValueTask or void.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly C#, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • Cysharp/MagicOnion4,458 ★ · +6
Star history
Repository2026-09-232026-11-14
Cysharp/MagicOnion4.5k4.5k

6 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

The schema is an interface, so there is no .proto file to maintain

MagicOnion is built on gRPC, which is described as a fast and compact binary network transport over HTTP/2. The departure from plain gRPC is the contract. Instead of writing a Protocol Buffers IDL file, you declare a C# interface and treat that interface as the protocol schema, which is what lets code be shared between C# projects.

The framing in the README is that interfaces are schemas and provide API services, just like plain C# code. There is no separate description language to keep in sync with the implementation, and no generation step between an IDL and the code that implements it.

The framework is positioned against several existing options, and those comparisons come from the project itself: RPC services such as gRPC used by microservices and WCF, which is common in WinForms and WPF; API services such as ASP.NET Core Web API targeting WPF applications, Unity games, .NET for iOS, Android and .NET MAUI; and bi-directional realtime communication in the shape of Socket.io, SignalR, Photon and UNet. One framework is meant to cover those three shapes, and either half can be used alone.

Server hookup is one package, one service call, one map call

The server side starts from an ordinary web project. The README points at a Minimal API project created from the ASP.NET Core Empty template, and the only dependency to add is the `MagicOnion.Server` NuGet package:

bash
dotnet add package MagicOnion.Server

`Program.cs` then gets two additions, one in the service collection and one on the built application.

csharp
using MagicOnion;
using MagicOnion.Server;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddMagicOnion(); // Add this line(MagicOnion.Server)

var app = builder.Build();

app.MapMagicOnionService(); // Add this line

app.Run();

That is the whole registration surface shown here. Nothing in the file adds a controller, a serializer or a routing convention, because the contract arrives as a C# interface rather than as HTTP attributes. After those lines the project is described as ready to use MagicOnion, and it is started with F5 or `dotnet run`. The URL printed when the server starts is the address the client will need, so note it rather than assuming the default.

Unary service method signatures use UnaryResult, not Task

The shared interface is a plain C# interface, but with two framework specific requirements attached. It inherits `IService<T>` from the library, and the return type must be `UnaryResult<T>` or `UnaryResult`, which is treated as an asynchronous method in the way `Task` or `ValueTask` would be.

csharp
using System;
using MagicOnion;

namespace MyApp.Shared
{
    // Defines .NET interface as a Server/Client IDL.
    // The interface is shared between server and client.
    public interface IMyFirstService : IService<IMyFirstService>
    {
        // The return type must be `UnaryResult<T>` or `UnaryResult`.
        UnaryResult<int> SumAsync(int x, int y);
    }
}

The implementation class has a matching constraint: it must inherit `ServiceBase<IMyFirstService>` and implement the interface itself.

csharp
public class MyFirstService : ServiceBase<IMyFirstService>, IMyFirstService
{
    // `UnaryResult<T>` allows the method to be treated as `async` method.
    public async UnaryResult<int> SumAsync(int x, int y)
    {
        Console.WriteLine($"Received:{x}, {y}");
        return x + y;
    }
}

So an interface that already returns `Task<int>` does not transfer unchanged, and the parameter list travels as it is. The interface lives in a namespace called `MyApp.Shared`, which is a convention here rather than a requirement of the framework.

The client proxy is built from a gRPC channel you create first

On the calling side there is no generated stub file to reference. You open a gRPC channel with `GrpcChannel.ForAddress`, which is described as abstracting the connection, and hand it to `MagicOnionClient.Create<T>` to get a proxy typed to the shared interface.

csharp
using Grpc.Net.Client;
using MagicOnion.Client;
using MyApp.Shared;

// Connect to the server using gRPC channel.
var channel = GrpcChannel.ForAddress("https://localhost:5001");

// Create a proxy to call the server transparently.
var client = MagicOnionClient.Create<IMyFirstService>(channel);

// Call the server-side method using the proxy.
var result = await client.SumAsync(123, 456);
Console.WriteLine($"Result: {result}");

The client project starts as a Console Application and adds the `MagicOnion.Client` package. The call reads like an ordinary method call, and the README describes it as transparent, which is the payoff of the interface as schema. The address in the example is an HTTPS localhost URL; the README says to use the URL the server displays when it starts.

Server and client sit on different floors of the .NET range

The version requirements are deliberately asymmetric. The server requires .NET 8 or newer, with no older target listed. The client is far wider: .NET Framework 4.6.1 through .NET 8, plus .NET Standard 2.1 and 2.0, plus Unity 2022.3 LTS or newer on Windows, macOS, iOS and Android, with both IL2CPP and Mono listed as scripting backends.

That gap is what makes the WinForms and WPF story work. A desktop client on an older framework can call a service hosted on a current runtime, so existing line of business applications can adopt the server side without a framework upgrade of their own.

The reverse does not hold, and it is the first thing to check when planning a deployment. There is no listed server target below .NET 8, so a shop that still hosts on an older runtime has no supported configuration here. For Unity, the required baseline is 2022.3 LTS, and anything older is outside the stated range.

Sharing the interface is a manual arrangement in the quick start

Because the contract is source code, somebody has to move that source between projects. The README names three ways to do it: file links, a shared library, or copy and paste. All three are manual arrangements, and no packaging helper for the shared project is described.

The repository layout shows how a larger setup is organized. Sample projects sit under `samples/`, including `ChatApp`, `JsonTranscoding` and `JwtAuthentication`, which between them cover a realtime chat, a payload format question and an authentication scheme. Package versions are centralized in `Directory.Packages.props` alongside `Directory.Build.props`, and the solution is described by `MagicOnion.slnx` with a separate packaging filter file. A `perf/` directory and an `opensource.snk` strong name key are present as well.

Those samples are the fastest way to see the parts the quick start leaves out. Read them before deciding what a streaming method signature or an authenticated call looks like, because the README shows neither.

The realtime half and the security story are left to the docs site

Two advertised capabilities get a sentence each and no code. The first is StreamingHub, the realtime service through which the server broadcasts data to multiple clients; the quick start implements an RPC service instead, so the broadcast path is never demonstrated in the file. The second is security. The example channel is an HTTPS localhost address and nothing else about authentication, authorization or transport configuration appears, even though a `JwtAuthentication` sample exists in the tree.

There is also no documented handling for the failure cases a distributed call produces: timeouts, retries, partial results or error translation are not configured anywhere in the shown setup.

The remedy is the documentation site, which the README links in English and Japanese, including a page on working with Unity. Budget for reading it before the first production call, and treat the quick start as proof that the pieces connect rather than as a description of how to operate them.

Editorial conclusion

MagicOnion earns a place in a .NET shop that wants one interface to serve a WPF client, an ASP.NET service and a Unity build without maintaining an IDL file. Before committing, read the realtime and authentication pages on the documentation site, since the README covers neither, and check return types separately: unary service methods use UnaryResult, while StreamingHub methods support Task, ValueTask or void.

Frequently asked questions

What network transport does MagicOnion use?

It is based on gRPC, described as a fast and compact binary network transport for HTTP/2. Unlike plain gRPC, the C# interface acts as the protocol schema, so no .proto IDL file is involved.

Which .NET and Unity versions does MagicOnion support?

The server requires .NET 8 or newer. The client supports .NET Framework 4.6.1 through .NET 8, .NET Standard 2.1 and 2.0, and Unity 2022.3 LTS or newer on Windows, macOS, iOS and Android with IL2CPP and Mono.

How do I call a MagicOnion service from client code?

Create a channel with `GrpcChannel.ForAddress`, pass it to `MagicOnionClient.Create<IMyFirstService>(channel)`, then await the call on the proxy, as in `await client.SumAsync(123, 456)`. The client project adds the `MagicOnion.Client` NuGet package.

What return type must a MagicOnion interface method use?

Unary service methods return `UnaryResult<T>` or `UnaryResult`; those interfaces inherit `IService<T>`. StreamingHub methods instead support `Task`, `Task<T>`, `ValueTask`, `ValueTask<T>`, or `void`.

Does MagicOnion cover realtime push as well as request and response?

Yes. The StreamingHub service lets the server broadcast data to multiple clients, and either that half or the RPC half can be used on its own, though a setup combining both is supported too.

Official sources

  1. Cysharp/MagicOnion on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/cysharp-magiconion.svg)](https://hysenlabs.com/projects/cysharp-magiconion)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/cysharp-magiconion.svg)](https://hysenlabs.com/projects/cysharp-magiconion)

Was this useful?