Skip to content

Commit

Permalink
Merge pull request #49 from dotnet-presentations/damianedwards/8.2-up…
Browse files Browse the repository at this point in the history
…dates

Updates for 8.2
  • Loading branch information
DamianEdwards authored Sep 6, 2024
2 parents e0c24aa + 3995b1e commit e02122c
Show file tree
Hide file tree
Showing 28 changed files with 662 additions and 774 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ Come learn all about [.NET Aspire](https://learn.microsoft.com/dotnet/aspire/),

.NET Aspire streamlines app development with:

- **Orchestration**: Built-in orchestration with a simple, yet powerful, workflow engine.Use C# and familiar APIs without a line of YAML. Easily add popular cloud services, connect them to your projects, and run locally with a single click.
- **Service Discovery**: Automatic injection the right connection strings or network configurations and service discovery information to simplify the developer experience.
- **Components**: Built-in components for common cloud services like databases, queues, and storage. Integrated with logging, health checks, telemetry, and more.
- **Orchestration**: Use C# and familiar APIs to model your distributed application without a line of YAML. Easily add popular databases, messaging systems, and cloud services, connect them to your projects, and run locally with a single click.
- **Service Discovery**: Automatic injection of the right connection strings or network configurations and service discovery information to simplify the developer experience.
- **Integrations**: Built-in integrations for common cloud services like databases, queues, and storage. Configured for logging, health checks, telemetry, and more.
- **Dashboard**: See live OpenTelemetry data with no configuration required. Launched by default on run, .NET Aspire's developer dashboard shows logs, environment variables, distributed traces, metrics and more to quickly verify app behavior.
- **Deployment**: manages injecting the right connection strings or network configurations and service discovery information to simplify the developer experience.
- **Deployment**: Easily produce a manifest of all the configuration your application resources require to run in production. Optionally, quickly and easily deploy to Azure Container Apps or Kubernetes using Aspire-aware tools.
- **So Much More**: .NET Aspire is packed full of features that developers will love and help you be more productive.

Learn more about .NET Aspire with the following resources:
- [Documentation](https://learn.microsoft.com/dotnet/aspire)
- [Microsoft Learn Training Path](https://learn.microsoft.com/en-us/training/paths/dotnet-aspire/)
- [Microsoft Learn Training Path](https://learn.microsoft.com/training/paths/dotnet-aspire/)
- [.NET Aspire Videos](https://aka.ms/aspire/videos)
- [eShop Reference Sample App](https://github.com/dotnet/eshop)
- [.NET Aspire Samples](https://learn.microsoft.com/samples/browse/?expanded=dotnet&products=dotnet-aspire)
Expand Down Expand Up @@ -48,7 +48,7 @@ This .NET Aspire workshop is part of the [Let's Learn .NET](https://aka.ms/letsl
1. [Service Defaults](./workshop/2-servicedefaults.md)
1. [Developer Dashboard & Orchestration](./workshop/3-dashboard-apphost.md)
1. [Service Discovery](./workshop/4-servicediscovery.md)
1. [Components](./workshop/5-components.md)
1. [Integrations](./workshop/5-integrations.md)
1. [Deployment](./workshop/6-deployment.md)

A full slide deck is available for this workshop [here](./workshop/AspireWorkshop.pptx).
Expand Down
6 changes: 3 additions & 3 deletions complete/Api/Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.StackExchange.Redis.OutputCaching" Version="8.1.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.7" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
<PackageReference Include="Aspire.StackExchange.Redis.OutputCaching" Version="8.2.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.8" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.7.3" />
</ItemGroup>


Expand Down
241 changes: 115 additions & 126 deletions complete/Api/Data/NwsManager.cs
Original file line number Diff line number Diff line change
@@ -1,136 +1,125 @@
using Api.Data;
using System.Text.Json;
using System.Web;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.Extensions.Caching.Memory;
using System.Text.Json;
using Api.Data;

namespace Api
{

public class NwsManager(HttpClient httpClient, IMemoryCache cache)
{
JsonSerializerOptions options = new()
{
PropertyNameCaseInsensitive = true
};

public async Task<Zone[]?> GetZonesAsync()
{
return await cache.GetOrCreateAsync("zones", async entry =>
{
if (entry is null)
return [];
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);
// To get the live zone data from NWS, uncomment the following code and comment out the return statement below. This is required if you are deploying to ACA
//var response = await httpClient.GetAsync("https://api.weather.gov/zones?type=forecast");
//response.EnsureSuccessStatusCode();
//var content = await response.Content.ReadAsStringAsync();
//var zones = JsonSerializer.Deserialize<ZonesResponse>(content, options);
//return zones?.Features
// ?.Where(f => f.Properties?.ObservationStations?.Count > 0)
// .Select(f => (Zone)f)
// .Distinct()
// .ToArray() ?? [];
// Deserialize the zones.json file from the wwwroot folder
var zonesJson = File.Open("wwwroot/zones.json", FileMode.Open);
if (zonesJson is null)
return [];
var zones = await JsonSerializer.DeserializeAsync<ZonesResponse>(zonesJson, options);
return zones?.Features
?.Where(f => f.Properties?.ObservationStations?.Count > 0)
.Select(f => (Zone)f)
.Distinct()
.ToArray() ?? [];
});

}

static int forecastCount = 0;
public async Task<Forecast[]> GetForecastByZoneAsync(string zoneId)
{

forecastCount++;
if (forecastCount % 5 == 0)
{
throw new Exception("Random exception thrown by NwsManager.GetForecastAsync");
}

var response = await httpClient.GetAsync($"https://api.weather.gov/zones/forecast/{zoneId}/forecast");
response.EnsureSuccessStatusCode();
var forecasts = await response.Content.ReadFromJsonAsync<ForecastResponse>(options);
return forecasts?.Properties?.Periods?.Select(p => (Forecast)p).ToArray() ?? [];
}

}

public class NwsManager(HttpClient httpClient, IMemoryCache cache, IWebHostEnvironment webHostEnvironment)
{
private static readonly JsonSerializerOptions options = new(JsonSerializerDefaults.Web);

public async Task<Zone[]?> GetZonesAsync()
{
return await cache.GetOrCreateAsync("zones", async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);
// To get the live zone data from NWS, uncomment the following code and comment out the return statement below.
// This is required if you are deploying to ACA.
//var zones = await httpClient.GetFromJsonAsync<ZonesResponse>("https://api.weather.gov/zones?type=forecast", options);
//return zones?.Features
// ?.Where(f => f.Properties?.ObservationStations?.Count > 0)
// .Select(f => (Zone)f)
// .Distinct()
// .ToArray() ?? [];
// Deserialize the zones.json file from the wwwroot folder
var zonesFilePath = Path.Combine(webHostEnvironment.WebRootPath, "zones.json");
if (!File.Exists(zonesFilePath))
{
return [];
}
using var zonesJson = File.OpenRead(zonesFilePath);
var zones = await JsonSerializer.DeserializeAsync<ZonesResponse>(zonesJson, options);
return zones?.Features
?.Where(f => f.Properties?.ObservationStations?.Count > 0)
.Select(f => (Zone)f)
.Distinct()
.ToArray() ?? [];
});
}

private static int forecastCount = 0;

public async Task<Forecast[]> GetForecastByZoneAsync(string zoneId)
{
// Create an exception every 5 calls to simulate an error for testing
forecastCount++;

if (forecastCount % 5 == 0)
{
throw new Exception("Random exception thrown by NwsManager.GetForecastAsync");
}

var zoneIdSegment = HttpUtility.UrlEncode(zoneId);
var zoneUrl = $"https://api.weather.gov/zones/forecast/{zoneIdSegment}/forecast";
var forecasts = await httpClient.GetFromJsonAsync<ForecastResponse>(zoneUrl, options);
return forecasts
?.Properties
?.Periods
?.Select(p => (Forecast)p)
.ToArray() ?? [];
}
}
}

namespace Microsoft.Extensions.DependencyInjection
{


public static class NwsManagerExtensions
{

public static IServiceCollection AddNwsManager(this IServiceCollection services)
{
services.AddHttpClient<Api.NwsManager>(client =>
{
client.BaseAddress = new Uri("https://api.weather.gov/");
client.DefaultRequestHeaders.Add("User-Agent", "Microsoft - .NET Aspire Demo");
});

services.AddMemoryCache();

return services;
}

public static WebApplication? MapApiEndpoints(this WebApplication? app)
{
if(app is null)
return null;

app.UseOutputCache();

app.MapGet("/zones", async (Api.NwsManager manager) =>
{
var zones = await manager.GetZonesAsync();
return TypedResults.Ok(zones);
})
.WithName("GetZones")
.CacheOutput(policy =>
{
policy.Expire(TimeSpan.FromHours(1));
})
.WithOpenApi();

app.MapGet("/forecast/{zoneId}", async Task<Results<Ok<Api.Forecast[]>, NotFound>> (Api.NwsManager manager, string zoneId) =>
{
try
{
var forecasts = await manager.GetForecastByZoneAsync(zoneId);
return TypedResults.Ok(forecasts);
}
catch (HttpRequestException ex)
{
return TypedResults.NotFound();
}
})
.WithName("GetForecastByZone")
.CacheOutput(policy =>
{
policy.Expire(TimeSpan.FromMinutes(15)).SetVaryByRouteValue("zoneId");
})
.WithOpenApi();

return app;

}

}
public static class NwsManagerExtensions
{
public static IServiceCollection AddNwsManager(this IServiceCollection services)
{
services.AddHttpClient<Api.NwsManager>(client =>
{
client.BaseAddress = new Uri("https://api.weather.gov/");
client.DefaultRequestHeaders.Add("User-Agent", "Microsoft - .NET Aspire Demo");
});

services.AddMemoryCache();

// Add default output caching
services.AddOutputCache(options =>
{
options.AddBasePolicy(builder => builder.Cache());
});

return services;
}

public static WebApplication? MapApiEndpoints(this WebApplication app)
{
app.UseOutputCache();

app.MapGet("/zones", async (Api.NwsManager manager) =>
{
var zones = await manager.GetZonesAsync();
return TypedResults.Ok(zones);
})
.CacheOutput(policy => policy.Expire(TimeSpan.FromHours(1)))
.WithName("GetZones")
.WithOpenApi();

app.MapGet("/forecast/{zoneId}", async Task<Results<Ok<Api.Forecast[]>, NotFound>> (Api.NwsManager manager, string zoneId) =>
{
try
{
var forecasts = await manager.GetForecastByZoneAsync(zoneId);
return TypedResults.Ok(forecasts);
}
catch (HttpRequestException)
{
return TypedResults.NotFound();
}
})
.CacheOutput(policy => policy.Expire(TimeSpan.FromMinutes(15)).SetVaryByRouteValue("zoneId"))
.WithName("GetForecastByZone")
.WithOpenApi();

return app;
}
}
}
20 changes: 2 additions & 18 deletions complete/Api/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
Expand All @@ -20,14 +21,6 @@
"dotnetRunMessages": true,
"applicationUrl": "https://localhost:7032;http://localhost:5271"
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Container (.NET SDK)": {
"commandName": "SdkContainer",
"launchBrowser": true,
Expand All @@ -39,14 +32,5 @@
"publishAllPorts": true,
"useSSL": true
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:27734",
"sslPort": 44380
}
}
}
}
4 changes: 2 additions & 2 deletions complete/AppHost/AppHost.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="8.1.0" />
<PackageReference Include="Aspire.Hosting.Redis" Version="8.1.0" />
<PackageReference Include="Aspire.Hosting.AppHost" Version="8.2.0" />
<PackageReference Include="Aspire.Hosting.Redis" Version="8.2.0" />
</ItemGroup>

<ItemGroup>
Expand Down
9 changes: 4 additions & 5 deletions complete/AppHost/Program.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
var builder = DistributedApplication.CreateBuilder(args);

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

var api = builder.AddProject<Projects.Api>("api")
.WithReference(cache);
.WithReference(cache);

var web = builder.AddProject<Projects.MyWeatherHub>("myweatherhub")
.WithReference(api)
.WithExternalHttpEndpoints();

.WithReference(api)
.WithExternalHttpEndpoints();

builder.Build().Run();
4 changes: 2 additions & 2 deletions complete/MyWeatherHub/MyWeatherHub.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@


<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.QuickGrid" Version="8.0.7" />
<PackageReference Include="Microsoft.Extensions.ApiDescription.Client" Version="8.0.7">
<PackageReference Include="Microsoft.AspNetCore.Components.QuickGrid" Version="8.0.8" />
<PackageReference Include="Microsoft.Extensions.ApiDescription.Client" Version="8.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
Loading

0 comments on commit e02122c

Please sign in to comment.