.Net Aspire初體驗

Bruce_Cheung發表於2024-06-23

今天參加了Post Microsoft Build & AI Day深圳的集會,眾多大佬分享了非常優質前沿的技術和實踐,實在受益良多,為了消化吸收關於張隊分享的.Net Aspire的內容,特實操一遍小示例並記錄如下:

1、以VS2022為例,先升級到最新的版本v17.10.3,新建.NET Aspire Starter應用程式專案,選擇資料夾及Redis勾選和勾選生成Tests(HTTPS不能去除勾選)。

生成的資料夾結構如下:

可以看到按模板生成了一個ApiService(這裡是以前的天氣廣播內容);一個Web專案;一個AppHost( 一個Host,把ApiService及Web前端給引用進來);一個ServiceDefaults擴充套件類;一個Tests測試專案。

AspireApp1.ApiService專案的主要內容:

Program.cs:

.Net Aspire初體驗
 1 var builder = WebApplication.CreateBuilder(args);
 2 
 3 // Add service defaults & Aspire components.
 4 builder.AddServiceDefaults();
 5 
 6 // Add services to the container.
 7 builder.Services.AddProblemDetails();
 8 
 9 var app = builder.Build();
10 
11 // Configure the HTTP request pipeline.
12 app.UseExceptionHandler();
13 
14 var summaries = new[]
15 {
16     "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
17 };
18 
19 app.MapGet("/weatherforecast", () =>
20 {
21     var forecast = Enumerable.Range(1, 5).Select(index =>
22         new WeatherForecast
23         (
24             DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
25             Random.Shared.Next(-20, 55),
26             summaries[Random.Shared.Next(summaries.Length)]
27         ))
28         .ToArray();
29     return forecast;
30 });
31 
32 app.MapDefaultEndpoints();
33 
34 app.Run();
35 
36 record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
37 {
38     public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
39 }
View Code

AspireApp1.Web專案的主要內容:

Program.cs:

.Net Aspire初體驗
 1 using AspireApp1.Web;
 2 using AspireApp1.Web.Components;
 3 
 4 var builder = WebApplication.CreateBuilder(args);
 5 
 6 // Add service defaults & Aspire components.
 7 builder.AddServiceDefaults();
 8 
 9 // Add services to the container.
10 builder.Services.AddRazorComponents()
11     .AddInteractiveServerComponents();
12 
13 builder.Services.AddOutputCache();
14 
15 builder.Services.AddHttpClient<WeatherApiClient>(client => client.BaseAddress = new("http://apiservice"));
16 
17 var app = builder.Build();
18 
19 if (!app.Environment.IsDevelopment())
20 {
21     app.UseExceptionHandler("/Error", createScopeForErrors: true);
22 }
23 
24 app.UseStaticFiles();
25 app.UseAntiforgery();
26 
27 app.UseOutputCache();
28 
29 app.MapRazorComponents<App>()
30     .AddInteractiveServerRenderMode();
31 
32 app.MapDefaultEndpoints();
33 
34 app.Run();
View Code

WeatherApiClient.cs:

.Net Aspire初體驗
 1 namespace AspireApp1.Web;
 2 
 3 public class WeatherApiClient(HttpClient httpClient)
 4 {
 5     public async Task<WeatherForecast[]> GetWeatherAsync(int maxItems = 10, CancellationToken cancellationToken = default)
 6     {
 7         List<WeatherForecast>? forecasts = null;
 8 
 9         await foreach (var forecast in httpClient.GetFromJsonAsAsyncEnumerable<WeatherForecast>("/weatherforecast", cancellationToken))
10         {
11             if (forecasts?.Count >= maxItems)
12             {
13                 break;
14             }
15             if (forecast is not null)
16             {
17                 forecasts ??= [];
18                 forecasts.Add(forecast);
19             }
20         }
21 
22         return forecasts?.ToArray() ?? [];
23     }
24 }
25 
26 public record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
27 {
28     public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
29 }
View Code

AspireApp1.ServiceDefaults專案的主要內容:

Extensions.cs:

.Net Aspire初體驗
  1 using Microsoft.AspNetCore.Builder;
  2 using Microsoft.AspNetCore.Diagnostics.HealthChecks;
  3 using Microsoft.Extensions.DependencyInjection;
  4 using Microsoft.Extensions.Diagnostics.HealthChecks;
  5 using Microsoft.Extensions.Logging;
  6 using OpenTelemetry;
  7 using OpenTelemetry.Metrics;
  8 using OpenTelemetry.Trace;
  9 
 10 namespace Microsoft.Extensions.Hosting;
 11 
 12 // Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry.
 13 // This project should be referenced by each service project in your solution.
 14 // To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults
 15 public static class Extensions
 16 {
 17     public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder)
 18     {
 19         builder.ConfigureOpenTelemetry();
 20 
 21         builder.AddDefaultHealthChecks();
 22 
 23         builder.Services.AddServiceDiscovery();
 24 
 25         builder.Services.ConfigureHttpClientDefaults(http =>
 26         {
 27             // Turn on resilience by default
 28             http.AddStandardResilienceHandler();
 29 
 30             // Turn on service discovery by default
 31             http.AddServiceDiscovery();
 32         });
 33 
 34         return builder;
 35     }
 36 
 37     public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder)
 38     {
 39         builder.Logging.AddOpenTelemetry(logging =>
 40         {
 41             logging.IncludeFormattedMessage = true;
 42             logging.IncludeScopes = true;
 43         });
 44 
 45         builder.Services.AddOpenTelemetry()
 46             .WithMetrics(metrics =>
 47             {
 48                 metrics.AddAspNetCoreInstrumentation()
 49                     .AddHttpClientInstrumentation()
 50                     .AddRuntimeInstrumentation();
 51             })
 52             .WithTracing(tracing =>
 53             {
 54                 tracing.AddAspNetCoreInstrumentation()
 55                     // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package)
 56                     //.AddGrpcClientInstrumentation()
 57                     .AddHttpClientInstrumentation();
 58             });
 59 
 60         builder.AddOpenTelemetryExporters();
 61 
 62         return builder;
 63     }
 64 
 65     private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder)
 66     {
 67         var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
 68 
 69         if (useOtlpExporter)
 70         {
 71             builder.Services.AddOpenTelemetry().UseOtlpExporter();
 72         }
 73 
 74         // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package)
 75         //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]))
 76         //{
 77         //    builder.Services.AddOpenTelemetry()
 78         //       .UseAzureMonitor();
 79         //}
 80 
 81         return builder;
 82     }
 83 
 84     public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicationBuilder builder)
 85     {
 86         builder.Services.AddHealthChecks()
 87             // Add a default liveness check to ensure app is responsive
 88             .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
 89 
 90         return builder;
 91     }
 92 
 93     public static WebApplication MapDefaultEndpoints(this WebApplication app)
 94     {
 95         // Adding health checks endpoints to applications in non-development environments has security implications.
 96         // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments.
 97         if (app.Environment.IsDevelopment())
 98         {
 99             // All health checks must pass for app to be considered ready to accept traffic after starting
100             app.MapHealthChecks("/health");
101 
102             // Only health checks tagged with the "live" tag must pass for app to be considered alive
103             app.MapHealthChecks("/alive", new HealthCheckOptions
104             {
105                 Predicate = r => r.Tags.Contains("live")
106             });
107         }
108 
109         return app;
110     }
111 }
View Code

AspireApp1.AppHost專案的主要內容:

Program.cs:

.Net Aspire初體驗
1 var builder = DistributedApplication.CreateBuilder(args);
2 
3 var apiService = builder.AddProject<Projects.AspireApp1_ApiService>("apiservice");
4 
5 builder.AddProject<Projects.AspireApp1_Web>("webfrontend")
6     .WithExternalHttpEndpoints()
7     .WithReference(apiService);
8 
9 builder.Build().Run();
View Code

還有一個Tests測試專案的內容:

WebTests.cs:

.Net Aspire初體驗
 1 using System.Net;
 2 
 3 namespace AspireApp1.Tests;
 4 
 5 public class WebTests
 6 {
 7     [Fact]
 8     public async Task GetWebResourceRootReturnsOkStatusCode()
 9     {
10         // Arrange
11         var appHost = await DistributedApplicationTestingBuilder.CreateAsync<Projects.AspireApp1_AppHost>();
12         await using var app = await appHost.BuildAsync();
13         await app.StartAsync();
14 
15         // Act
16         var httpClient = app.CreateHttpClient("webfrontend");
17         var response = await httpClient.GetAsync("/");
18 
19         // Assert
20         Assert.Equal(HttpStatusCode.OK, response.StatusCode);
21     }
22 }
View Code

2、我們將AspireApp1.AppHost專案設為啟動專案,按F5執行。專案即啟動生成和執行,可在輸出視窗看到對應資訊。等程式執行起來會自動開啟一個瀏覽器概覽儀表盤視窗如下:

點選對應專案的終結點,即可看到對應的內容:

AspireApp1.ApiService:

AspireApp1.Web:

在儀表盤可以看到資源、控制檯、結構化、跟蹤、指標幾個欄目,沒有寫一句程式碼,你的可觀測、生產就緒的雲原生分散式應用程式就搭建完成了。

以下是一些圖示:

另外多嘴一句,並不是用了Aspire就一定要上雲,我突然有個主意,邊緣運算、單體程式照樣也可以用Aspire。另外Aspire和Dapr應該是有益的補充,而不是替代關係。

相關文章