17 使用 .NET Aspire 改进应用缓存

发布于:2025-08-14 ⋅ 阅读:(12) ⋅ 点赞:(0)

使用 .NET Aspire 改进应用缓存

核心内容

  • 主题:讲解如何利用 .NET Aspire 集成 Redis 实现两种缓存(输出缓存和分布式缓存),涉及项目创建、配置及测试等步骤。
  • 两种缓存方式
    • 输出缓存:存储整个 HTTP 响应,供将来请求使用,可配置且可扩展。
    • 分布式缓存:由外部服务维护,供多个应用服务器共享,用于缓存特定数据片段,能提升应用性能和可伸缩性。

关键步骤及示例

  1. 准备工作:需安装 .NET 8.0 及以上版本、符合 OCI 规范的容器运行时(如 Docker 桌面)以及相关 IDE 等。
  2. 创建项目:在 Visual Studio 中选择“.NET Aspire 初学者应用程序”模板,创建名为“AspireRedis”的项目,生成包含 UI、API 服务、应用主机和服务默认设置等项目的解决方案。
  3. 配置应用主机项目
    • 添加 NuGet 包 Aspire.Hosting.Redis,示例代码(.NET CLI):dotnet add package Aspire.Hosting.Redis
    • 更新 Program.cs,创建 Redis 容器实例并配置 UI 和 API 与 Redis 的关联,代码如下:
    var builder = DistributedApplication.CreateBuilder(args);
    var redis = builder.AddRedis("cache");
    var apiservice = builder.AddProject<Projects.AspireRedis_ApiService>("apiservice").WithReference(redis);
    builder.AddProject<Projects.AspireRedis_Web>("webfrontend").WithExternalHttpEndpoints().WithReference(apiservice).WithReference(redis);
    builder.Build().Run();
    
  4. 配置 UI 实现输出缓存
    • 添加包 Aspire.StackExchange.Redis.OutputCaching,示例(.NET CLI):dotnet add package Aspire.StackExchange.Redis.OutputCaching
    • Program.cs 中添加缓存配置:builder.AddRedisOutputCache("cache");
    • 修改 Blazor 页面,添加 [OutputCache] 属性实现缓存,页面显示当前时间以验证缓存效果,代码:
    @page "/"
    @attribute [OutputCache(Duration = 10)]
    <PageTitle>Home</PageTitle>
    <h1>Hello, world!</h1>
    Welcome to your new app on @DateTime.Now
    
  5. 配置 API 实现分布式缓存
    • 添加包 Aspire.StackExchange.Redis.DistributedCaching,示例(.NET CLI):dotnet add package Aspire.StackExchange.Redis.DistributedCaching
    • Program.cs 中添加缓存配置:builder.AddRedisDistributedCache("cache");
    • 修改 /weatherforecast 端点代码,实现天气数据的缓存与读取,代码:
    app.MapGet("/weatherforecast", async (IDistributedCache cache) =>
    {
        var cachedForecast = await cache.GetAsync("forecast");
        if (cachedForecast is null)
        {
            var summaries = new[] { "Freezing", "Bracing", ... };
            var forecast = Enumerable.Range(1, 5).Select(index => new WeatherForecast(...)).ToArray();
            await cache.SetAsync("forecast", Encoding.UTF8.GetBytes(JsonSerializer.Serialize(forecast)), new() { AbsoluteExpiration = DateTime.Now.AddSeconds(10) });
            return forecast;
        }
        return JsonSerializer.Deserialize<IEnumerable<WeatherForecast>>(cachedForecast);
    }).WithName("GetWeatherForecast");
    
  6. 示例代码
  • AppHost
using StackExchange.Redis;

var builder = DistributedApplication.CreateBuilder(args);

var cache = builder.AddRedis("cache").WithRedisInsight();

var apiService = builder
    .AddProject<Projects.aspire_cache_redis_ApiService>("aspire-cache-redis-apiservice")
    .WithReference(cache)
    .WaitFor(cache);

builder
    .AddProject<Projects.aspire_cache_redis_Web>("aspire-cache-redis-web")
    .WithReference(apiService)
    .WithReference(cache)
    .WaitFor(apiService);

builder.Build().Run();

  • ApiService

using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Caching.Distributed;
using Scalar.AspNetCore;

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();
builder.AddRedisDistributedCache("cache");

// Add services to the container.
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();

var app = builder.Build();

app.MapDefaultEndpoints();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
    app.MapScalarApiReference();
}

var summaries = new[]
{
    "Freezing",
    "Bracing",
    "Chilly",
    "Cool",
    "Mild",
    "Warm",
    "Balmy",
    "Hot",
    "Sweltering",
    "Scorching",
};

app.MapGet(
        "/weatherforecast",
        async (IDistributedCache cache) =>
        {
            var cachedForecast = await cache.GetAsync("forecast");

            if (cachedForecast is null)
            {
                var summaries = new[]
                {
                    "Freezing",
                    "Bracing",
                    "Chilly",
                    "Cool",
                    "Mild",
                    "Warm",
                    "Balmy",
                    "Hot",
                    "Sweltering",
                    "Scorching",
                };
                var forecast = Enumerable
                    .Range(1, 5)
                    .Select(index => new WeatherForecast(
                        DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
                        Random.Shared.Next(-20, 55),
                        summaries[Random.Shared.Next(summaries.Length)]
                    ))
                    .ToArray();

                await cache.SetAsync(
                    "forecast",
                    Encoding.UTF8.GetBytes(JsonSerializer.Serialize(forecast)),
                    new() { AbsoluteExpiration = DateTime.Now.AddSeconds(10) }
                );

                return forecast;
            }

            return JsonSerializer.Deserialize<IEnumerable<WeatherForecast>>(cachedForecast);
        }
    )
    .WithName("GetWeatherForecast");

app.Run();

record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}


  • Web
// Program.cs
using aspire_cache_redis.Web;
using aspire_cache_redis.Web.Components;

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();
builder.AddRedisOutputCache("cache");
builder.Services.AddHttpClient<WeatherApiClient>(client =>
{
    // This URL uses "https+http://" to indicate HTTPS is preferred over HTTP.
    // Learn more about service discovery scheme resolution at https://aka.ms/dotnet/sdschemes.
    client.BaseAddress = new("https+http://aspire-cache-redis-apiservice");
});

// Add services to the container.
builder.Services.AddRazorComponents().AddInteractiveServerComponents();

var app = builder.Build();

app.MapDefaultEndpoints();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseOutputCache();
app.UseAntiforgery();

app.MapStaticAssets();
app.MapRazorComponents<App>().AddInteractiveServerRenderMode();

app.Run();

//Home.razor

@page "/"
@using Microsoft.AspNetCore.OutputCaching
@attribute [OutputCache(Duration = 10)]

<PageTitle>Home</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app on @DateTime.Now

//Weather.razor

@page "/weather"
@using Microsoft.AspNetCore.OutputCaching
@attribute [StreamRendering(true)]
@attribute [OutputCache(Duration = 10)]

@inject WeatherApiClient WeatherApi

<PageTitle>Weather</PageTitle>

<h1>Weather @DateTime.Now</h1>

<p>This component demonstrates showing data loaded from a backend API service.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private WeatherForecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await WeatherApi.GetWeatherAsync();
    }
}


在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到