Quickstart: Build your first Orleans app with ASP.NET Core
In this quickstart, you use Orleans and ASP.NET Core 8.0 Minimal APIs to build a URL shortener app. Users submit a full URL to the app’s /shorten endpoint and get a shortened version to share with others, who are redirected to the original site. The app uses Orleans grains and silos to manage state in a distributed manner to allow for scalability and resiliency. These features are critical when developing apps for distributed cloud hosting services like Azure Container Apps and platforms like Kubernetes.
At the end of the quickstart, you have an app that creates and handles redirects using short, friendly URLs. You learn how to:
- Add Orleans to an ASP.NET Core app
- Work with grains and silos
- Configure state management
- Integrate Orleans with API endpoints
Prerequisites
Section titled “Prerequisites”Visual Studio
Section titled “Visual Studio”- .NET 8.0 SDK or later
- Visual Studio 2022 or later with the ASP.NET and web development workload
Visual Studio Code
Section titled “Visual Studio Code”- The latest .NET SDK
- Visual Studio Code editor
- The C# DevKit
Create the app
Section titled “Create the app”Visual Studio
Section titled “Visual Studio”-
Start Visual Studio and select Create a new project.
-
On the Create a new project dialog, select ASP.NET Core Web API, and then select Next.
-
On the Configure your new project dialog, enter
OrleansURLShortenerfor Project name, and then select Next. -
On the Additional information dialog, select .NET 8.0 (Long Term Support) and uncheck Use controllers, and then select Create.
Visual Studio Code
Section titled “Visual Studio Code”-
Inside Visual Studio Code, open the integrated terminal.
-
Change to the directory (
cd) that will contain the project. -
Run the following commands:
Terminal window dotnet new webapi -o OrleansURLShortenercode -r OrleansURLShortenerThe
dotnet newcommand creates a new Minimal API project in the OrleansURLShortener folder. Thecodecommand opens the OrleansURLShortener folder in the current instance of Visual Studio Code.Visual Studio Code displays a dialog box that asks Do you trust the authors of the files in this folder. Select:
- The checkbox trust the authors of all files in the parent folder
- Yes, I trust the authors (because dotnet generated the files).
Add Orleans to the project
Section titled “Add Orleans to the project”Orleans is available through a collection of NuGet packages, each of which provides access to various features. For this quickstart, add the Microsoft.Orleans.Server NuGet package to the app:
Visual Studio
Section titled “Visual Studio”- Right-click on the OrleansURLShortener project node in the solution explorer and select Manage NuGet Packages.
- In the package manager window, search for Orleans.
- Choose the Microsoft.Orleans.Server package from the search results and then select Install.
Visual Studio Code
Section titled “Visual Studio Code”In the Visual Studio Code terminal, run the following command:
dotnet add package Microsoft.Orleans.ServerOr, in .NET 10+:
dotnet package add Microsoft.Orleans.ServerOpen the Program.cs file and replace the existing content with the following code:
using Orleans.Runtime;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();Configure the silos
Section titled “Configure the silos”Silos are a core building block of Orleans responsible for storing and managing grains. A silo can contain one or more grains; a group of silos is known as a cluster. The cluster coordinates work between silos, allowing communication with grains as though they were all available in a single process.
At the top of the Program.cs file, refactor the code to use Orleans. The following code uses a ISiloBuilder class to create a localhost cluster with a silo that can store grains. The ISiloBuilder also uses the AddMemoryGrainStorage to configure the Orleans silos to persist grains in memory. This scenario uses local resources for development, but a production app can be configured to use highly scalable clusters and storage using services like Azure Blob Storage.
using Orleans.Runtime;
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseOrleans(static siloBuilder =>{ siloBuilder.UseLocalhostClustering(); siloBuilder.AddMemoryGrainStorage("urls");});
using var app = builder.Build();Create the URL shortener grain
Section titled “Create the URL shortener grain”Grains are the most essential primitives and building blocks of Orleans applications. A grain is a class that inherits from the Grain base class, which manages various internal behaviors and integration points with Orleans. Grains should also implement one of the following interfaces to define their grain key identifier. Each of these interfaces defines a similar contract, but marks your class with a different data type for the identifier that Orleans uses to track the grain, such as a string or integer.
- IGrainWithGuidKey
- IGrainWithIntegerKey
- IGrainWithStringKey
- IGrainWithGuidCompoundKey
- IGrainWithIntegerCompoundKey
For this quickstart, you use the IGrainWithStringKey, since strings are a logical choice for working with URL values and short codes.
Orleans grains can also use a custom interface to define their methods and properties. The URL shortener grain interface should define two methods:
- A
SetUrlmethod to persist the original and their corresponding shortened URLs. - A
GetUrlmethod to retrieve the original URL given the shortened URL.
-
Append the following interface definition to the bottom of the Program.cs file.
public interface IUrlShortenerGrain : IGrainWithStringKey{Task SetUrl(string fullUrl);Task<string> GetUrl();} -
Create a
UrlShortenerGrainclass using the following code. This class inherits from the Grain class provided by Orleans and implements theIUrlShortenerGraininterface you created. The class also uses the IPersistentState<T> interface of Orleans to manage reading and writing state values for the URLs to the configured silo storage.public sealed class UrlShortenerGrain([PersistentState(stateName: "url",storageName: "urls")]IPersistentState<UrlDetails> state): Grain, IUrlShortenerGrain{public async Task SetUrl(string fullUrl){state.State = new(){ShortenedRouteSegment = this.GetPrimaryKeyString(),FullUrl = fullUrl};await state.WriteStateAsync();}public Task<string> GetUrl() =>Task.FromResult(state.State.FullUrl);}[GenerateSerializer, Alias(nameof(UrlDetails))]public sealed record class UrlDetails{[Id(0)]public string FullUrl { get; set; } = "";[Id(1)]public string ShortenedRouteSegment { get; set; } = "";}
Create the endpoints
Section titled “Create the endpoints”Next, create two endpoints to utilize the Orleans grain and silo configurations:
- A
/shortenendpoint to handle creating and storing a shortened version of the URL. The original, full URL is provided as a query string parameter namedurl, and the shortened URL is returned to the user for later use. - A
/go/{shortenedRouteSegment:required}endpoint to handle redirecting users to the original URL using the shortened URL that is supplied as a parameter.
Inject the IGrainFactory interface into both endpoints. Grain Factories enable you to retrieve and manage references to individual grains that are stored in silos. Append the following code to the Program.cs file before the app.Run() method call:
app.MapGet("/", static () => "Welcome to the URL shortener, powered by Orleans!");
app.MapGet("/shorten", static async (IGrainFactory grains, HttpRequest request, string url) => { var host = $"{request.Scheme}://{request.Host.Value}";
// Validate the URL query string. if (string.IsNullOrWhiteSpace(url) || Uri.IsWellFormedUriString(url, UriKind.Absolute) is false) { return Results.BadRequest($""" The URL query string is required and needs to be well formed. Consider, ${host}/shorten?url=https://www.microsoft.com. """); }
// Create a unique, short ID var shortenedRouteSegment = Guid.NewGuid().GetHashCode().ToString("X");
// Create and persist a grain with the shortened ID and full URL var shortenerGrain = grains.GetGrain<IUrlShortenerGrain>(shortenedRouteSegment);
await shortenerGrain.SetUrl(url);
// Return the shortened URL for later use var resultBuilder = new UriBuilder(host) { Path = $"/go/{shortenedRouteSegment}" };
return Results.Ok(resultBuilder.Uri); });
app.MapGet("/go/{shortenedRouteSegment:required}", static async (IGrainFactory grains, string shortenedRouteSegment) => { // Retrieve the grain using the shortened ID and url to the original URL var shortenerGrain = grains.GetGrain<IUrlShortenerGrain>(shortenedRouteSegment);
var url = await shortenerGrain.GetUrl();
// Handles missing schemes, defaults to "http://". var redirectBuilder = new UriBuilder(url);
return Results.Redirect(redirectBuilder.Uri.ToString()); });
app.Run();Test the completed app
Section titled “Test the completed app”The core functionality of the app is now complete and ready to be tested. The final app code should match the following example:
// <configuration>using Orleans.Runtime;
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseOrleans(static siloBuilder =>{ siloBuilder.UseLocalhostClustering(); siloBuilder.AddMemoryGrainStorage("urls");});
using var app = builder.Build();// </configuration>
// <endpoints>app.MapGet("/", static () => "Welcome to the URL shortener, powered by Orleans!");
app.MapGet("/shorten", static async (IGrainFactory grains, HttpRequest request, string url) => { var host = $"{request.Scheme}://{request.Host.Value}";
// Validate the URL query string. if (string.IsNullOrWhiteSpace(url) || Uri.IsWellFormedUriString(url, UriKind.Absolute) is false) { return Results.BadRequest($""" The URL query string is required and needs to be well formed. Consider, ${host}/shorten?url=https://www.microsoft.com. """); }
// Create a unique, short ID var shortenedRouteSegment = Guid.NewGuid().GetHashCode().ToString("X");
// Create and persist a grain with the shortened ID and full URL var shortenerGrain = grains.GetGrain<IUrlShortenerGrain>(shortenedRouteSegment);
await shortenerGrain.SetUrl(url);
// Return the shortened URL for later use var resultBuilder = new UriBuilder(host) { Path = $"/go/{shortenedRouteSegment}" };
return Results.Ok(resultBuilder.Uri); });
app.MapGet("/go/{shortenedRouteSegment:required}", static async (IGrainFactory grains, string shortenedRouteSegment) => { // Retrieve the grain using the shortened ID and url to the original URL var shortenerGrain = grains.GetGrain<IUrlShortenerGrain>(shortenedRouteSegment);
var url = await shortenerGrain.GetUrl();
// Handles missing schemes, defaults to "http://". var redirectBuilder = new UriBuilder(url);
return Results.Redirect(redirectBuilder.Uri.ToString()); });
app.Run();// </endpoints>
// <graininterface>public interface IUrlShortenerGrain : IGrainWithStringKey{ Task SetUrl(string fullUrl);
Task<string> GetUrl();}// </graininterface>
// <grain>public sealed class UrlShortenerGrain( [PersistentState( stateName: "url", storageName: "urls")] IPersistentState<UrlDetails> state) : Grain, IUrlShortenerGrain{ public async Task SetUrl(string fullUrl) { state.State = new() { ShortenedRouteSegment = this.GetPrimaryKeyString(), FullUrl = fullUrl };
await state.WriteStateAsync(); }
public Task<string> GetUrl() => Task.FromResult(state.State.FullUrl);}
[GenerateSerializer, Alias(nameof(UrlDetails))]public sealed record class UrlDetails{ [Id(0)] public string FullUrl { get; set; } = "";
[Id(1)] public string ShortenedRouteSegment { get; set; } = "";}// </grain>Test the application in the browser using the following steps:
Visual Studio
Section titled “Visual Studio”-
Start the app using the run button at the top of Visual Studio. The app should launch in the browser and display the familiar
Hello world!text. -
In the browser address bar, test the
shortenendpoint by entering a URL path such as{localhost}/shorten?url=https://learn.microsoft.com. The page should reload and provide a shortened URL. Copy the shortened URL to your clipboard.
-
Paste the shortened URL into the address bar and press enter. The page should reload and redirect you to https://learn.microsoft.com.
Visual Studio Code
Section titled “Visual Studio Code”-
Inside the Visual Studio Code terminal, run the
dotnet runcommand again to launch the app. The app should launch in the browser and display the familiarHello world!text.Terminal window dotnet run -
In the browser address bar, test the
shortenendpoint by entering a URL path such as{localhost}/shorten?url=https://learn.microsoft.com. The page should reload and provide a shortened URL. Copy the shortened URL to your clipboard.
-
Paste the shortened URL into the address bar and press enter. The page should reload and redirect you to https://learn.microsoft.com.
Applies to: Orleans 8.0, Orleans 9.0, Orleans 10.0
Next steps: Production-ready Orleans with Aspire
Section titled “Next steps: Production-ready Orleans with Aspire”The quickstart above uses in-memory storage and localhost clustering, which works well for development but isn’t suitable for production deployments. When you’re ready to build production-ready Orleans applications, Aspire provides a streamlined approach with:
- Resource management: Easily add Redis, Azure Storage, or SQL databases as backing stores for clustering, grain storage, and reminders
- Service discovery: Automatic configuration of Orleans silos and clients without manual endpoint configuration
- Observability: Built-in OpenTelemetry integration for distributed tracing, metrics, and logging
- Health checks: Automatic health check endpoints for your Orleans cluster
- Multi-replica support: Scale your Orleans silos horizontally with
WithReplicas()
Quick example with Aspire
Section titled “Quick example with Aspire”Here’s how the URL shortener app would look with Aspire and Redis:
AppHost project (Program.cs):
var builder = DistributedApplication.CreateBuilder(args);
// Add Redis for clustering and grain storagevar redis = builder.AddRedis("redis");
// Configure Orleans to use Redisvar orleans = builder.AddOrleans("cluster") .WithClustering(redis) .WithGrainStorage("urls", redis);
// Add the Orleans silo projectbuilder.AddProject<Projects.OrleansURLShortener>("silo") .WithReference(orleans) .WithReference(redis);
builder.Build().Run();Silo project (Program.cs):
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();builder.AddKeyedRedisClient("redis");builder.UseOrleans();
var app = builder.Build();// ... rest of app configurationWith Aspire, Orleans automatically picks up the cluster configuration from environment variables injected by the AppHost, eliminating the need for manual configuration code.
Applies to: Orleans 3.x, Orleans 7.0
