在本快速入門中,您將會使用 Orleans 和 ASP.NET Core 8.0 最小 API 來組建 URL 縮短程式應用程式。 使用者將完整的 URL 提交至應用程式的 /shorten
端點,並取得縮短的版本,以與重新導向至原始網站的其他人共用。 應用程式會使用 Orleans 微粒和筒倉,以分散式方式管理狀態,允許可擴展性和復原。 針對分散式雲端裝載服務 (例如 Azure Container Apps 和 Kubernetes 之類的平台) 開發應用程式時,這些功能非常重要。
在快速入門的最後,您將會有一個應用程式,其會使用簡短且易記的 URL 來建立及處理重新導向。 您將學習如何:
- 在 ASP.NET Core 應用程式新增 Orleans
- 從事穀物和筒倉的工作
- 設定狀態管理
- 將 Orleans 整合至 API 端點
必要條件
- .NET 8.0 SDK
- Visual Studio 2022 搭配 ASP.NET 和 Web 開發工作負載
建立 應用程式
啟動 Visual Studio 2022 並選取 [建立新專案]。
在 [建立新專案] 對話方塊中,選取 [ASP.NET Core Web API],然後選取 [下一步]。
在 [設定新專案] 對話方塊中,輸入
OrleansURLShortener
作為 [專案名稱],然後選取 [下一步]。在 [其他資訊] 對話方塊中,選取 [.NET 8.0 (長期支援)],取消核取 [使用控制器],然後選取 [建立]。
將 Orleans 新增至專案
Orleans 可透過 NuGet 套件的集合取得,每個套件都提供各種功能的存取權。 針對本快速入門,將 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 類別來建立包含可以儲存 unit 的 silo 的 localhost 叢集。
ISiloBuilder
也會使用 AddMemoryGrainStorage
來設定 Orleans 資料倉儲,以在記憶體中持久化 grains。 此案例會使用本機資源進行開發,但生產應用程式可以設定為使用高度可調整的叢集和儲存體 (使用 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 縮短器精細度
Grains 是 Orleans 應用程式最基本的要素和建置組塊。 Grain 是一個繼承自 Grain 基類的類別,負責管理各種內部行為及與 Orleans 的整合點。 穀物也應該實作以下介面之一,以定義其穀物鍵識別碼。 每個介面都會定義類似的合約,但會以不同的資料類型標記您的類別,作為 Orleans 用於追蹤顆粒的識別碼,例如字串或整數。
- IGrainWithGuidKey
- IGrainWithIntegerKey
- IGrainWithStringKey
- IGrainWithGuidCompoundKey
- IGrainWithIntegerCompoundKey
針對本快速入門,您將會使用 IGrainWithStringKey
,因為字串是處理 URL 值和短代碼的合理選擇。
Orleans 元件也可以使用自訂介面來定義其方法和屬性。 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 顆粒和筒倉配置:
- 用來處理建立及儲存 URL 縮短版本的
/shorten
端點。 原始的完整 URL 會以名爲url
的查詢字串參數提供,而縮短的 URL 會傳回給使用者以供稍後使用。 - 一個
/go/{shortenedRouteSegment:required}
端點負責使用作為參數提供的縮短 URL,引導使用者重新導向至原始 URL。
將 IGrainFactory 介面插入這兩個端點。 穀物工廠可讓您擷取和管理儲存在筒倉中的個別穀物的參考。 在 方法呼叫之前,將下列程式碼附加至 app.Run()
檔案:
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 貼到網址列中,然後按 Enter。 頁面應該會重新載入,並將您重新導向至 https://learn.microsoft.com。