.net.net-8.0orleans

Unexpected Codec Exception when running .NET Orleans web application


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?


Solution

  • 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).