Tutorial: Build your first Orleans app
In this tutorial, you build a small Orleans application using project boundaries typical of larger applications. Grain contracts, grain implementations, the silo, and an external client are separate projects with distinct dependencies and deployment roles.
You learn how to:
- Define and implement a grain.
- Host grains in a silo.
- Connect an external client to a silo.
- Obtain a grain reference and call it.
- Structure project references so that clients depend on contracts, not implementations.
The manual walkthrough uses localhost clustering and transient in-memory state so that the silo and client can run directly from two terminals.
Prerequisites
Section titled “Prerequisites”- .NET 10 SDK
- An editor such as Visual Studio or Visual Studio Code
- Two terminals so that the silo and client can run at the same time
Create the solution
Section titled “Create the solution”Create a solution with four Orleans projects:
- GrainInterfaces contains grain contracts shared by callers and implementations.
- Grains contains the grain implementations.
- Silo hosts the Orleans runtime and grain activations.
- Client is an external process that connects to the silo and calls grains.
The Microsoft.Orleans.Templates package creates the same Orleans project boundaries and adds an Aspire AppHost. The generated application uses Azurite-backed Azure Table clustering and Azure Blob grain storage instead of the manual walkthrough’s localhost configuration. A running container runtime lets the AppHost start Azurite:
dotnet new install Microsoft.Orleans.Templatesdotnet new orleans --name OrleansHelloWorld --output OrleansHelloWorldContinue with the manual steps below to learn how the projects and references fit together.
Run the following commands in an empty directory:
dotnet new sln --name OrleansHelloWorld --format slnxdotnet new classlib --name GrainInterfaces --framework net10.0dotnet new classlib --name Grains --framework net10.0dotnet new console --name Silo --framework net10.0dotnet new console --name Client --framework net10.0
dotnet solution OrleansHelloWorld.slnx add GrainInterfaces/GrainInterfaces.csprojdotnet solution OrleansHelloWorld.slnx add Grains/Grains.csprojdotnet solution OrleansHelloWorld.slnx add Silo/Silo.csprojdotnet solution OrleansHelloWorld.slnx add Client/Client.csproj
dotnet reference add GrainInterfaces/GrainInterfaces.csproj --project Grains/Grains.csprojdotnet reference add Grains/Grains.csproj --project Silo/Silo.csprojdotnet reference add GrainInterfaces/GrainInterfaces.csproj --project Client/Client.csproj
dotnet package add Microsoft.Orleans.Sdk --version 10.2.2 --project GrainInterfaces/GrainInterfaces.csprojdotnet package add Microsoft.Orleans.Sdk --version 10.2.2 --project Grains/Grains.csprojdotnet package add Microsoft.Orleans.Server --version 10.2.2 --project Silo/Silo.csprojdotnet package add Microsoft.Orleans.Client --version 10.2.2 --project Client/Client.csprojdotnet package add Microsoft.Extensions.Hosting --version 10.0.9 --project Silo/Silo.csprojdotnet package add Microsoft.Extensions.Hosting --version 10.0.9 --project Client/Client.csprojThe client references only GrainInterfaces. It doesn’t need the grain implementation assembly. The silo references Grains, which in turn references GrainInterfaces.
Define the grain contract
Section titled “Define the grain contract”Delete GrainInterfaces/Class1.cs, create GrainInterfaces/IHello.cs, and add the following grain interface:
public interface IHello : IGrainWithStringKey{ ValueTask<string> SayHello(string greeting);}IGrainWithStringKey identifies the grain by a string key. Grain contracts use asynchronous return types because calls can cross process and network boundaries.
Implement the grain
Section titled “Implement the grain”Delete Grains/Class1.cs, create Grains/HelloGrain.cs, and add the following implementation:
public sealed class HelloGrain : Grain, IHello{ private readonly ILogger<HelloGrain> _logger;
public HelloGrain(ILogger<HelloGrain> logger) { _logger = logger; }
public ValueTask<string> SayHello(string greeting) { _logger.LogInformation( "SayHello message received: greeting = {Greeting}", greeting);
return ValueTask.FromResult($"Hello, {greeting}!"); }}The implementation inherits from Grain and implements IHello. Orleans source generators discover the grain contract and implementation at build time, so you don’t need to register application parts manually.
Configure the silo
Section titled “Configure the silo”Replace Silo/Program.cs with the following code:
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
builder.UseOrleans(siloBuilder =>{ siloBuilder.UseLocalhostClustering();});
using var host = builder.Build();await host.RunAsync();UseOrleans adds the Orleans silo to the .NET Generic Host. UseLocalhostClustering configures development-only clustering and gateway endpoints on the local machine.
The silo project references Grains, so the runtime can discover and activate HelloGrain.
Configure the external client
Section titled “Configure the external client”Replace Client/Program.cs with the following code:
using GrainInterfaces;using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Hosting;using Orleans;
var builder = Host.CreateApplicationBuilder(args);
builder.UseOrleansClient(clientBuilder =>{ clientBuilder.UseLocalhostClustering();});
using var host = builder.Build();await host.StartAsync();
var grainFactory = host.Services.GetRequiredService<IGrainFactory>();var friend = grainFactory.GetGrain<IHello>("friend");var response = await friend.SayHello("Hi friend");
Console.WriteLine(response);
await host.StopAsync();UseOrleansClient adds an external Orleans client to the Generic Host. The client uses the same localhost clustering configuration as the silo. After the host starts, the client resolves IGrainFactory from dependency injection, obtains a grain reference, and invokes the grain.
Build and run the application
Section titled “Build and run the application”Build the solution:
dotnet build OrleansHelloWorld.slnxStart the silo in the first terminal:
dotnet run --project SiloWait until the silo prints Application started, then start the client in the second terminal:
dotnet run --project ClientThe client prints the grain response:
Hello, Hi friend!The client never creates or locates a HelloGrain object directly. GetGrain<IHello>("friend") returns a logical reference. When the client invokes SayHello, Orleans routes the call through a silo gateway and activates the grain if it isn’t already active.
Stop the silo by pressing Ctrl+C in its terminal.
