共用方式為


快速入門: 使用 ASP.NET Core 組建您的第一個 Orleans 應用程式

在本快速入門中,您將會使用 Orleans 和 ASP.NET Core 8.0 最小 API 來組建 URL 縮短程式應用程式。 使用者將完整的 URL 提交至應用程式的 /shorten 端點,並取得縮短的版本,以與重新導向至原始網站的其他人共用。 應用程式會使用 Orleans 微粒和筒倉,以分散式方式管理狀態,允許可擴展性和復原。 針對分散式雲端裝載服務 (例如 Azure Container Apps 和 Kubernetes 之類的平台) 開發應用程式時,這些功能非常重要。

在快速入門的最後,您將會有一個應用程式,其會使用簡短且易記的 URL 來建立及處理重新導向。 您將學習如何:

  • 在 ASP.NET Core 應用程式新增 Orleans
  • 從事穀物和筒倉的工作
  • 設定狀態管理
  • 將 Orleans 整合至 API 端點

必要條件

建立 應用程式

  1. 啟動 Visual Studio 2022 並選取 [建立新專案]

  2. [建立新專案] 對話方塊中,選取 [ASP.NET Core Web API],然後選取 [下一步]

  3. 在 [設定新專案] 對話方塊中,輸入 OrleansURLShortener 作為 [專案名稱],然後選取 [下一步]

  4. [其他資訊] 對話方塊中,選取 [.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 用於追蹤顆粒的識別碼,例如字串或整數。

針對本快速入門,您將會使用 IGrainWithStringKey,因為字串是處理 URL 值和短代碼的合理選擇。

Orleans 元件也可以使用自訂介面來定義其方法和屬性。 URL 縮短工具介面應該定義兩種方法:

  • 用於持續保存原始 URL 及其對應縮短 URL 的 SetUrl 方法。
  • 一種用於從縮短的 URL 擷取原始 URL 的 GetUrl 方法。
  1. 將下列介面定義新增至在 Program.cs 檔案底部。

    public interface IUrlShortenerGrain : IGrainWithStringKey
    {
        Task SetUrl(string fullUrl);
    
        Task<string> GetUrl();
    }
    
  2. 使用下列程式碼建立 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>

使用下列步驟在瀏覽器中測試應用程式:

  1. 您可以使用 Visual Studio 頂端的 [執行] 按鈕,啟動應用程式。 應用程式應該會在瀏覽器中啟動,並顯示熟悉的 Hello world! 文字。

  2. 在瀏覽器網址列中,輸入 URL 路徑 (例如 shorten) 來測試 {localhost}/shorten?url=https://learn.microsoft.com 端點。 頁面應該會重新載入,並提供縮短的網址。 將縮短的 URL 複製到剪貼簿。

    顯示從 Visual Studio 啟動 URL 縮短程式結果的螢幕擷取畫面。

  3. 將縮短的 URL 貼到網址列中,然後按 Enter。 頁面應該會重新載入,並將您重新導向至 https://learn.microsoft.com