-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #49 from dotnet-presentations/damianedwards/8.2-up…
…dates Updates for 8.2
- Loading branch information
Showing
28 changed files
with
662 additions
and
774 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.