今天參加了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:
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 }
AspireApp1.Web專案的主要內容:
Program.cs:
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();
WeatherApiClient.cs:
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 }
AspireApp1.ServiceDefaults專案的主要內容:
Extensions.cs:
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 }
AspireApp1.AppHost專案的主要內容:
Program.cs:
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();
還有一個Tests測試專案的內容:
WebTests.cs:
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 }
2、我們將AspireApp1.AppHost專案設為啟動專案,按F5執行。專案即啟動生成和執行,可在輸出視窗看到對應資訊。等程式執行起來會自動開啟一個瀏覽器概覽儀表盤視窗如下:
點選對應專案的終結點,即可看到對應的內容:
AspireApp1.ApiService:
AspireApp1.Web:
在儀表盤可以看到資源、控制檯、結構化、跟蹤、指標幾個欄目,沒有寫一句程式碼,你的可觀測、生產就緒的雲原生分散式應用程式就搭建完成了。
以下是一些圖示:
另外多嘴一句,並不是用了Aspire就一定要上雲,我突然有個主意,邊緣運算、單體程式照樣也可以用Aspire。另外Aspire和Dapr應該是有益的補充,而不是替代關係。