MagicOnion: C# Interfaces as the RPC Schema, With StreamingHub Added
Unified Realtime/API framework for .NET platform and Unity.
At a glance
- What is it?
- MagicOnion is a .NET and Unity framework that puts gRPC underneath C# interface definitions, so server and client share the same contract without a .proto file. The StreamingHub side adds group broadcast on top of the same transport.
- Who is it for?
- Adopt MagicOnion when both ends of the wire are C# and you want one interface file to serve as the contract, particularly if the same service also needs group broadcast through StreamingHub. Do not adopt it when a non-.NET client must call the service, because the schema lives in C# rather than in .proto and other languages cannot generate a client from it.
- 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 received new commits within the last day.
- What is it written in?
- Mainly C#, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The contract problem MagicOnion removes for C# teams
Plain gRPC makes you maintain a .proto file. That file is the schema, and every language generates its client from it. The cost shows up when the server and the client are both C#: you write the message types twice, once in the .proto and once as the C# class you actually use, and you keep the two in step by hand or by code generation.
MagicOnion takes the position that a C# interface is already a schema. A service is declared as an interface that inherits from `IService<T>`, and the method signature is the request and response definition. The README states the framework "treats C# interfaces as a protocol schema, enabling seamless code sharing between C# projects without `.proto` (Protocol Buffers IDL)." The client proxy is generated from that shared interface at compile time, so a change to the method signature breaks the client build rather than producing a runtime mismatch.
The audience is narrow and specific. It is for teams whose server is ASP.NET Core and whose clients are WPF, Unity, .NET MAUI, iOS, or Android, all in C#. The README lists exactly those use cases, alongside microservices and WinForms/WPF services that would previously have used WCF. If one end of your system is written in Go, Python or TypeScript, the interface-as-schema idea does not carry across, and you are back to needing a language-neutral definition.
How the interface becomes a callable service
The mechanism has three moving parts. The first is the shared interface, which lives in a project both sides reference. The second is the server implementation class, which inherits `ServiceBase<T>` and the interface itself. The third is the client proxy, created through `MagicOnionClient.Create<T>` over a gRPC channel.
On the server, registration is done through dependency injection. `builder.Services.AddMagicOnion()` registers the framework, and `app.MapMagicOnionService()` maps the endpoints. The README's quick start shows both calls added to a minimal ASP.NET Core host with no other configuration.
Return types are constrained. A unary method must return `UnaryResult<T>` or `UnaryResult`, which the README describes as being treated as an asynchronous method in the same way as `Task` or `ValueTask`. That constraint is what lets the framework wrap the result for serialization without you writing marshalling code.
StreamingHub is the second half of the framework and the reason it is described as unified. The README states that with the StreamingHub real-time communication service, "the server can broadcast data to multiple clients." That is the SignalR and Socket.io shape: a persistent connection, server-initiated messages, and groups. The transport underneath both halves is the same gRPC channel over HTTP/2, so a service can expose unary RPC endpoints and a hub without a second networking stack.
Installing MagicOnion and calling a first service
The server needs .NET 8 or newer. Start from the ASP.NET Core Empty template and add the server package with the .NET CLI:
dotnet add package MagicOnion.ServerThen wire the framework into `Program.cs`. The two added lines are the whole server registration; the README shows them in exactly this position relative to `Build()` and `Run()`.
using MagicOnion;
using MagicOnion.Server;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMagicOnion();
var app = builder.Build();
app.MapMagicOnionService();
app.Run();Define the service as an interface in a project both sides can see. The return type must be `UnaryResult<T>`.
using MagicOnion;
namespace MyApp.Shared
{
public interface IMyFirstService : IService<IMyFirstService>
{
UnaryResult<int> SumAsync(int x, int y);
}
}Implement it on the server by inheriting both `ServiceBase<IMyFirstService>` and the interface. The method body is ordinary C#; the framework handles the call boundary.
using MagicOnion;
using MagicOnion.Server;
using MyApp.Shared;
namespace MyApp.Services;
public class MyFirstService : ServiceBase<IMyFirstService>, IMyFirstService
{
public async UnaryResult<int> SumAsync(int x, int y)
{
Console.WriteLine($"Received:{x}, {y}");
return x + y;
}
}Run the server with `dotnet run` or F5. The README advises noting the URL printed at startup, because that URL is what the client connects to.
On the client, create a Console Application, add `MagicOnion.Client`, and share the same interface file. The README lists file links, a shared library, or copy and paste as the ways to do that, which tells you the framework does not impose a packaging mechanism. Build a gRPC channel, then build the proxy over it.
using Grpc.Net.Client;
using MagicOnion.Client;
using MyApp.Shared;
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = MagicOnionClient.Create<IMyFirstService>(channel);
var result = await client.SumAsync(10, 20);The call is awaited like any other async method. What you should see on the server console is the `Received:10, 20` line, and `result` holds 30.
Where MagicOnion is the wrong tool
The clearest boundary is the client language. Because the schema is a C# interface, a client that is not .NET has nothing to generate from. The README's supported client list is .NET 8 and above, .NET Standard 2.1 and 2.0, and Unity 2022.3 LTS or newer on Windows, macOS, iOS and Android with IL2CPP or Mono. A browser client in TypeScript, a Go service, or a Python data job is outside that list. If those are in your system, plain gRPC with a .proto file is the more honest choice, because the schema is then usable by every language you have.
The second boundary is the framework floor on the server. .NET 8 or newer is a hard requirement. A team still running a service on .NET Framework or .NET 6 cannot put MagicOnion on the server side, even though the client side reaches back to .NET Framework 4.6.1. The asymmetry is easy to miss when reading the platform list.
The third is transport. The framework is built on gRPC over HTTP/2. Proxies, load balancers and hosting environments that do not pass HTTP/2 through will break the connection, and that is a property of the transport rather than something MagicOnion abstracts away. The README does not document a fallback transport.
Finally, the README does not document rollback or versioning strategy for the shared interface. That silence matters: because the interface is compiled into both sides, a change to a method signature is a breaking change at build time, and the documentation does not describe how to stage that change across a deployed server and older clients.
MagicOnion against plain gRPC and SignalR
The most direct comparison is MagicOnion against plain gRPC. Both run on HTTP/2 and both use the same underlying channel type on the client, `GrpcChannel`. The difference is where the schema lives. In plain gRPC it lives in a .proto file that any language can consume, and C# message classes are generated from it. In MagicOnion it lives in a C# interface that only C# can consume, and the client proxy is generated from that. You trade cross-language reach for not maintaining a second definition of the same types.
The second comparison is against SignalR. SignalR is the .NET answer to real-time browser and server push, and it negotiates its transport, falling back when WebSockets are unavailable. MagicOnion's StreamingHub serves the same broadcast use case but sits on gRPC over HTTP/2 with no documented fallback. The practical consequence is that SignalR is more forgiving of hostile network paths, and MagicOnion is more consistent about wire format because everything, unary calls included, goes through the same binary transport.
The third is Socket.io, which the README names as a comparable for bi-directional real-time communication. Socket.io is JavaScript-first with clients in many languages; MagicOnion is C#-first with clients in the .NET family. The choice between them usually follows from the client platform rather than from the feature list.
Maintenance, licensing and the cost of upgrading
The repository is not archived. The last push was on 2026-09-14, and release 7.11.0 carries the same date, with 7.10.2 on 2026-07-01 and 7.10.1 on 2026-06-11 before it. That is a release cadence measured in weeks to a couple of months, not years.
The licence is MIT, which permits commercial use, modification and redistribution provided the copyright notice and permission notice are included. That is a permissive licence with no copyleft obligation on your own code. This is a description of the licence text, not legal advice; if your organisation has a policy on third-party licences, the file to read is `LICENSE` at the repository root.
Upgrade cost is driven by the interface-as-schema design. Because the contract is compiled into both server and client, a version bump can require rebuilding both sides together. The repository ships a `Directory.Packages.props` file, which is the central package versioning mechanism, so a team pinning versions has one place to change. The client-side floor is broad enough that a server upgrade does not force every client to move at once, but the README does not describe a supported mixed-version protocol, so the safe assumption is that server and client should be built from the same interface revision.
Editorial conclusion
Adopt MagicOnion when both ends of the wire are C# and you want one interface file to serve as the contract, particularly if the same service also needs group broadcast through StreamingHub. Do not adopt it when a non-.NET client must call the service, because the schema lives in C# rather than in .proto and other languages cannot generate a client from it. Before committing, verify that your client's target framework is on the supported list (the README stops at .NET Framework 4.6.1 on the low end and Unity 2022.3 LTS on the Unity side), and confirm that your deployment terminates HTTP/2 in a way gRPC accepts, since the transport is HTTP/2 and not plain HTTP.
Frequently asked questions
What is MagicOnion?
MagicOnion is a unified realtime and API framework for the .NET platform and Unity, built on gRPC. It uses C# interfaces as the protocol schema instead of .proto files, and provides both RPC services and StreamingHub for bi-directional real-time communication.
How do I install MagicOnion on the server?
The server project needs .NET 8 or newer. Add the package with `dotnet add package MagicOnion.Server`, then call `builder.Services.AddMagicOnion()` and `app.MapMagicOnionService()` in `Program.cs`.
Can MagicOnion be used with Unity?
Yes. The README lists Unity 2022.3 (LTS) or newer on Windows, macOS, iOS and Android, with both IL2CPP and Mono, among the supported client platforms.
Does MagicOnion require a .proto file?
No. The README states that it treats C# interfaces as a protocol schema, so code can be shared between C# projects without Protocol Buffers IDL.
Official sources
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.
[](https://hysenlabs.com/projects/cysharp-magiconion)