在本快速入门中,你将使用 Orleans 和 ASP.NET Core 8.0 Minimal API 生成 URL 缩短器应用。 用户将完整的 URL 提交到应用应用的 /shorten
终结点,并获取缩短的版本,以便与重定向到原始站点的其他人共享。 该应用使用 Orleans grain 和接收器以分布式方式管理状态,以实现可伸缩性和复原能力。 为 Azure 容器应用等分布式云托管服务和 Kubernetes 等平台开发应用时,这些功能至关重要。
在快速入门结束后,你将拥有一个应用程序,它可以使用简短易记的 URL 来创建和管理重定向。 你将学习如何执行以下操作:
- 将 Orleans 添加到 ASP.NET Core 应用
- 使用粒度和接收器
- 配置状态管理
- 将 Orleans 与 API 终结点集成
先决条件
- .NET 8.0 SDK
- Visual Studio 2022 与“ASP.NET 和 Web 开发”工作负载
创建应用
启动 Visual Studio 2022 并选择“创建新项目”。
在“新建项目”对话框中,选择“ASP.NET Core Web API”,然后选择“下一步”。
在“配置新项目”对话框中,输入 作为“项目名称”,然后选择“下一步”。
在“其他信息”对话框中,选择“.NET 8.0 (长期支持)”,取消选中“使用控制器”,然后选择“创建”。
将 Orleans 添加到项目中
可通过一系列 NuGet 包获取 Orleans,每个包都提供对各种功能的访问。 在此快速入门中,请将 Microsoft.Orleans.Server NuGet 包添加到应用中:
- 右键单击解决方案资源管理器中的“OrleansURLShortener”项目节点,然后选择“管理 NuGet 包”。
- 在包管理器窗口中,搜索“Orleans”。
- 从搜索结果中选择“Microsoft.Orleans.Server”包,然后选择“安装”。
打开 Program.cs 文件,将现有内容替换为以下代码:
using Orleans.Runtime;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
配置接收器
筒仓是 Orleans 的核心组成部分,负责存储和管理谷物。 筒仓可以容纳一种或多种谷物;一组筒仓被称为集群。 群集协调接收器之间的工作,允许与粒度通信,就像它们在单个进程中都可用一样。
在 Program.cs 文件的顶部,重构代码以使用 Orleans。 以下代码使用 ISiloBuilder 类创建包含可存储粒度的接收器的 localhost 群集。 ISiloBuilder
还使用 AddMemoryGrainStorage
配置 Orleans 接收器以将 grain 永久保存在内存中。 此方案使用本地资源进行开发,但生产应用可以配置为通过 Azure Blob 存储等服务使用高度可缩放的群集和存储。
using Orleans.Runtime;
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseOrleans(static siloBuilder =>
{
siloBuilder.UseLocalhostClustering();
siloBuilder.AddMemoryGrainStorage("urls");
});
using var app = builder.Build();
创建 URL 缩短器粒度
grain 是 Orleans 应用程序的最基本的基元和基础部分。 "Grain 是一个继承自 Grain 基类的类,它管理着各种内部行为和与 Orleans 的集成点。" grain 还应实现下列接口之一,以定义 grain 键标识符。 其中每个接口都定义了一个类似的协定,但对 Orleans 用于跟踪 grain 的标识符使用不同的数据类型来标记类,例如字符串或整数。
- IGrainWithGuidKey
- IGrainWithIntegerKey
- IGrainWithStringKey
- IGrainWithGuidCompoundKey
- IGrainWithIntegerCompoundKey
对于本快速入门,你将使用 IGrainWithStringKey
,因为字符串是处理 URL 值和短代码的合理选择。
Orleans grain 还可以使用自定义接口来定义其方法和属性。 URL 缩短器粒度接口应定义两个方法:
- 用于永久保存原始 URL 和相应缩短 URL 的
SetUrl
方法。 - 用于在给定缩短 URL 的情况下检索原始 URL 的
GetUrl
方法。
将以下接口定义追加到 Program.cs 文件的底部。
public interface IUrlShortenerGrain : IGrainWithStringKey { Task SetUrl(string fullUrl); Task<string> GetUrl(); }
使用以下代码创建
UrlShortenerGrain
类。 此类继承自Grain
提供的 Orleans 类,并实现你创建的IUrlShortenerGrain
接口。 该类还使用IPersistentState
的 Orleans 接口来管理 URL 状态值的读写,以将其存储到已配置的隔离存储中。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; } = ""; }
创建终结点
接下来,创建两个终结点以利用 Orleans grain 和接收器配置:
/shorten
终结点,用于创建和存储缩短版的 URL。 原始的完整 URL 作为名为url
的查询字符串参数提供,缩短的 URL 返回给用户供后续使用。/go/{shortenedRouteSegment:required}
终结点,用于通过作为参数提供的缩短 URL 将用户重定向到原始 URL。
将 IGrainFactory 接口注入到两个端点中。 使用粒度工厂可以检索和管理对接收器中存储的各个粒度的引用。 在调用 方法之前,将以下代码追加到 Program.cs 文件:
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();
测试已完成的应用
应用的核心功能现已完成,可以进行测试。 最终的应用代码应与以下示例匹配:
// <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>
使用以下步骤在浏览器中测试应用程序:
使用 Visual Studio 顶部的“运行”按钮启动应用。 应用应在浏览器中启动,显示熟悉的
Hello world!
文本。在浏览器地址栏中,通过输入 URL 路径(例如
shorten
)来测试{localhost}/shorten?url=https://learn.microsoft.com
终结点。 此时页面应会重新加载并提供缩短的 URL。 将缩短的 URL 复制到剪贴板。将缩短的 URL 粘贴到地址栏中,然后按 Enter 键。 此时页面应会重新加载并重定向到 https://learn.microsoft.com。