Skip to main content

What's new in 7.0

General

void return value support for StreamingHub methods

StreamingHub method now supports void return value. This can be used for Fire-and-Forget pattern that does not require a return value from the server.

Client results

Added a mechanism to wait for the receiver's method call in StreamingHub and receive the processing result on the server. For more information, see Client Results.

Built-in heartbeat for StreamingHub

Added a built-in heartbeat feature to StreamingHub. For more information, see Heartbeat.

Upgrade to MessagePack v3

MessagePack has been upgraded to v3. This eliminates the need for using MessagePack Generator (mpc) in Unity and AOT environments. For more information, see the MessagePack-CSharp release.

Server

Refresh of the backend implementation of StreamingHub groups

Backends of StreamingHub groups have been refreshed to be based on Multicaster, allowing for more flexible group operations. This has resulted in changes to the group-related APIs.

  • InMemoryStorage API has been removed
  • Changes to the IGroup interface API
    • Changes to the API for getting group receivers
      • Added All, Single, Only, Except methods
      • Removed Broadcast methods in StreamingHubBase and CreateBroadcaster methods in Group

For more information, see Groups and Application-managed groups. For migration from v6, see the migration guide.

Introduction of MagicOnion.Server.JsonTranscoding and removal of MagicOnion.Server.HttpGateway

MagicOnion.Server.HttpGateway, which was used for Swagger support, has been removed. Instead, MagicOnion.Server.JsonTranscoding has been introduced, which encodes/decodes requests/responses in JSON. and its Swagger support has been added. For more information, see JSON Transcoding.

Other improvements

  • Suport for .NET 9
  • Call AddLogging and AddGrpc implicitly

Client

Introduce WaitForDisconnectAsync API

The WaitForDisconnectAsync method has been added to the StreamingHub client. This method waits for disconnection as before with WaitForDisconnect, but now returns the reason for disconnection. This allows you to get disconnection reasons such as timeouts due to heartbeats. For more information, see Disconnection.

Other improvements

  • Enable trimming warnings in the client library
  • Reduce allocations in StreamingHubClient

Breaking changes

Server

Removed support for .NET 7

We no longer support .NET 7 as its runtime support has ended. Please use .NET 8 or later.

StreamingHubContext is now reused

StreamingHubContext is now reused, so caching StreamingHubContext outside of Hub method calls may result in unexpected behavior. The context is only valid during the call.

Reworked the binding logic for gRPC methods

We have reworked the mechanism for registering MagicOnion methods as gRPC methods on the server. This changes the timing of registering MagicOnion Unary/StreamingHub services from application build to request pipeline/route construction.

AddMagicOnion was used to register Unary/StreamingHub services, but now you need to change it to register with MapMagicOnionService.

Client

Removed support for C-Core gRPC library

We have removed support for the C-core gRPC library in Unity. Please use grpc-dotnet and YetAnotherHttpHandler instead. For more information, see Using with Unity.

StreamingHub client now throws RpcException instead of ObjectDisposedException after disconnection

In previous versions, when a client called a method after being disconnected from StreamingHub, an ObjectDisposedException was thrown. Now, an RpcException is thrown. ObjectDisposedException is only thrown when it is disposed like a general class.

Migration guide from v6

Unity client migration

In contrast to general .NET applications, upgrading Unity clients requires installing MagicOnion in an empty Unity project and copying it to the target project.

This is because you cannot install NuGet packages in Unity projects that already have MagicOnion and MessagePack installed via the Unity package manager, and even if you simply remove those libraries, you will get build errors and the packages will remain in place.

Create an empty Unity project for work

  • Install NuGetForUnity
  • Install MagicOnion.Client with NuGetForUnity

Prepare the target project for upgrade

  • Install NuGetForUnity
  • Remove MagicOnion and MessagePack packages

Copy the NuGet packages installed in the work project to the target project

  • Copy or merge the NuGet.config and packages.config under Assets

Modify the Packages/manifest.json

    "com.cysharp.magiconion": "https://github.com/Cysharp/MagicOnion.git?path=/src/MagicOnion.Client.Unity/Assets/Scripts/MagicOnion#{Version}",

The above line should be rewritten as follows. Replace {Version} with the version you want to install.

    "com.cysharp.magiconion.client.unity": "https://github.com/Cysharp/MagicOnion.git?path=/src/MagicOnion.Client.Unity/Assets/Scripts/MagicOnion.Client.Unity#{Version}",

Server API: StreamingHub

StreamingHub group functionality has been significantly changed in StreamingHub v7, so there is no API compatibility at the code level. Therefore, when migrating from v6 to v7, you need to change the usage to v7 or use the compatibility API shim.

Compatibility API shim

If you are using StreamingHub and InMemoryStorage, you can use the following compatibility API shim when migrating to v7 to enable a step-by-step migration. However, this compatibility shim does not include the features of InMemoryStorage in groups, so you will need to make separate arrangements if you are using those features.

using Cysharp.Runtime.Multicast;

namespace MagicOnion.Server.Hubs;

public abstract class StreamingHubBaseCompat<THub, TReceiver> : StreamingHubBase<THub, TReceiver>
where THub : IStreamingHub<THub, TReceiver>
{
protected TReceiver Broadcast(IGroup group) => ((GroupCompat<TReceiver>)group).Inner.All;
protected TReceiver BroadcastExceptSelf(IGroup group) => ((GroupCompat<TReceiver>)group).Inner.Except(ConnectionId);
protected TReceiver BroadcastExcept(IGroup group, Guid except) => ((GroupCompat<TReceiver>)group).Inner.Except(except);
protected TReceiver BroadcastExcept(IGroup group, IReadOnlyList<Guid> excepts) => ((GroupCompat<TReceiver>)group).Inner.Except(excepts);
protected TReceiver BroadcastToSelf(IGroup group) => ((GroupCompat<TReceiver>)group).Inner.Single(ConnectionId);
protected TReceiver BroadcastTo(IGroup group, Guid toConnectionId) => ((GroupCompat<TReceiver>)group).Inner.Single(toConnectionId);
protected TReceiver BroadcastTo(IGroup group, IReadOnlyList<Guid> toConnectionIds) => ((GroupCompat<TReceiver>)group).Inner.Only(toConnectionIds);

protected new HubGroupRepository Group { get; }

protected StreamingHubBaseCompat()
{
Group = new HubGroupRepositoryCompat<TReceiver>(base.Group);
}

class HubGroupRepositoryCompat<T> : HubGroupRepository
{
readonly HubGroupRepository<T> groupRepo;

public HubGroupRepositoryCompat(HubGroupRepository<T> groupRepo)
{
this.groupRepo = groupRepo;
}

public override async ValueTask<IGroup> AddAsync(string name)
=> new GroupCompat<T>(await groupRepo.AddAsync(name));
}

class GroupCompat<T> : IGroup
{
public IGroup<T> Inner { get; }

public GroupCompat(IGroup<T> inner)
{
this.Inner = inner;
}

public ValueTask RemoveAsync(ServiceContext serviceContext)
=> this.Inner.RemoveAsync(serviceContext);

public T1 CreateBroadcaster<T1>() => (T1)(object)Inner.All!;
public T1 CreateBroadcasterTo<T1>(Guid toConnectionId) => (T1)(object)Inner.Single(toConnectionId)!;
public T1 CreateBroadcasterTo<T1>(IReadOnlyList<Guid> toConnectionIds) => (T1)(object)Inner.Only(toConnectionIds)!;
public T1 CreateBroadcasterExcept<T1>(Guid toConnectionId) => (T1)(object)Inner.Except(toConnectionId)!;
public T1 CreateBroadcasterExcept<T1>(IReadOnlyList<Guid> toConnectionIds) => (T1)(object)Inner.Except(toConnectionIds)!;
}
}

public interface IGroup
{
ValueTask RemoveAsync(ServiceContext serviceContext);

T CreateBroadcaster<T>();
T CreateBroadcasterTo<T>(Guid toConnectionId);
T CreateBroadcasterTo<T>(IReadOnlyList<Guid> toConnectionIds);
T CreateBroadcasterExcept<T>(Guid toConnectionId);
T CreateBroadcasterExcept<T>(IReadOnlyList<Guid> toConnectionIds);
}

public abstract class HubGroupRepository
{
public abstract ValueTask<IGroup> AddAsync(string name);
}