.Net Aspire初体验
今天参加了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应该是有益的补充,而不是替代关系。