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