The given following example works fine:
namespace Test
{
public class Program
{
static async Task Main(string[] args)
{
using var host = new HostBuilder()
.UseOrleans(builder =>
{
builder.UseLocalhostClustering();
})
.Build();
await host.StartAsync();
var grainFactory = host.Services.GetRequiredService<IGrainFactory>();
var friend = grainFactory.GetGrain<ITestGrain<bool>>("friend");
CancellationTokenSource ctSource = new();
var test = await friend.Test(ctSource.Token);
Console.WriteLine("Test: " + test);
await host.StopAsync();
}
}
public interface ITestGrain<TType> : IGrainWithStringKey
{
public Task<TType> Test(CancellationToken? cancellationToken = default);
}
public class TestGrain : Grain, ITestGrain<bool>
{
public Task<bool> Test(CancellationToken? cancellationToken = default) =>
Task.FromResult(true);
}
}
Imported packages in .csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.2" />
<PackageReference Include="Microsoft.Orleans.Client" Version="9.1.2" />
<PackageReference Include="Microsoft.Orleans.Core" Version="9.1.2" />
<PackageReference Include="Microsoft.Orleans.Core.Abstractions" Version="9.1.2" />
<PackageReference Include="Microsoft.Orleans.Server" Version="9.1.2" />
<PackageReference Include="OrleansDashboard" Version="8.2.0" />
</ItemGroup>
</Project>
This minimal example works and produces the expected output:
Test: True
When I switch to a webapplication builder I get the following exception.
var builder = WebApplication.CreateBuilder(args);
//using var host = new HostBuilder()
builder
.UseOrleans(builder =>
{
builder.UseLocalhostClustering();
});
//.Build();
var host = builder.Build();
Orleans.Serialization.CodecNotFoundException: 'Could not find a codec for type System.Threading.CancellationToken.'
I know that providing a CancellationToken to a .NET Orleans grain method doesn't make any sense. I don't want to rewrite my API (remove CancellationToken parameter) in case I am switching to another medium where CancellationTokens are supported.
Still I wondered what are the differences between running .NET Orleans on a console or web host? I heard that on a console application the grain call is executed locally and nothing has to be serialized, but still when switching to a web application the grain is still hosted inside the same process. Can I configure Orleans to make also the web application version work without throwing a exception?
By configuring Orleans to serialize also CancellationTokens with System.Text.Json, I made the webapplication version work and also produce the expected output.
For that the nuget package Microsoft.Orleans.Serialization.SystemTextJson must be installed.
builder
.UseOrleans(siloBuilder =>
{
siloBuilder.UseLocalhostClustering();
siloBuilder.Services.AddSerializer(serializerBuilder =>
{
serializerBuilder.AddJsonSerializer(isSupported: typeCand => typeCand == typeof(System.Threading.CancellationToken));
});
});
More details and other approaches can be found in the docs (https://learn.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/serialization-configuration?pivots=orleans-7-0#configure-orleans-to-use-systemtextjson).