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 andCreateBroadcaster
methods in Group
- Added
- Changes to the API for getting group receivers
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
andAddGrpc
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);
}