Skip to content

Commit d999488

Browse files
authored
v3.15.0 (#95)
1 parent d60bb46 commit d999488

File tree

56 files changed

+640
-2008
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+640
-2008
lines changed

CHANGELOG.md

+12
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22

33
Represents the **NuGet** versions.
44

5+
## v3.15.0
6+
- *Enhancement*: This is a clean-up version to remove all obsolete code and dependencies. This will result in a number of minor breaking changes, but will ensure that the codebase is up-to-date and maintainable.
7+
- As per [`v3.14.0`](#v3.14.0) the previously obsoleted `TypedHttpClientBase` methods `WithRetry`, `WithTimeout`, `WithCustomRetryPolicy` and `WithMaxRetryDelay` are now removed; including `TypedHttpClientOptions`, `HttpRequestLogger` and related `SettingsBase` capabilities.
8+
- Health checks:
9+
- `CoreEx.Azure.HealthChecks` namespace and classes removed.
10+
- `SqlServerHealthCheck` replaced with simple generic `DatabaseHealthCheck`.
11+
- `IServiceCollection.AddDatabase` automatically adds `DatabaseHealthCheck`.
12+
- `IServiceCollection.AddSqlServerEventOutboxHostedService` automatically adds `TimerHostedServiceHealthCheck`.
13+
- `IServiceCollection.AddReferenceDataOrchestrator` automatically adds `ReferenceDataOrchestratorHealthCheck` (reports cache statistics).
14+
- `HealthReportStatusWriter` added to support richer JSON reporting.
15+
- Generally recommend using 3rd-party library to enable further health checks; for example: [`https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks`](https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks).
16+
517
## v3.14.1
618
- *Fixed*: The `Result.ValidatesAsync` extension method signature has had the value nullability corrected to enable fluent-style method-chaining.
719
- *Fixed*: The fully qualified type and property name is now correctly used as the `LText.KeyAndOrText` when creating within the `PropertyExpression<TEntity, TProperty>` to enable a qualified _key_ that can be used by the `ITextProvider` to substitute the text at runtime; the existing text fallback behavior remains such that an appropriate text is used. The `PropertyExpression.CreatePropertyLTextKey` function can be overridden to change this behavior.

Common.targets

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project>
22
<PropertyGroup>
3-
<Version>3.14.1</Version>
3+
<Version>3.15.0</Version>
44
<LangVersion>preview</LangVersion>
55
<Authors>Avanade</Authors>
66
<Company>Avanade</Company>

samples/My.Hr/My.Hr.Api/Controllers/HealthController.cs

-23
This file was deleted.

samples/My.Hr/My.Hr.Api/ImplicitUsings.cs

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
global using CoreEx.Configuration;
33
global using CoreEx.Entities;
44
global using CoreEx.Events;
5-
global using CoreEx.HealthChecks;
65
global using CoreEx.Http;
76
global using CoreEx.Json;
87
global using CoreEx.RefData;

samples/My.Hr/My.Hr.Api/Startup.cs

+5-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
using CoreEx.Azure.HealthChecks;
2-
using CoreEx.Database;
3-
using CoreEx.DataBase.HealthChecks;
1+
using CoreEx.Database;
42
using Microsoft.EntityFrameworkCore;
53
using Microsoft.Extensions.Diagnostics.HealthChecks;
64
using Azure.Monitor.OpenTelemetry.AspNetCore;
75
using OpenTelemetry.Trace;
86
using Az = Azure.Messaging.ServiceBus;
7+
using CoreEx.Database.HealthChecks;
98

109
namespace My.Hr.Api;
1110

@@ -52,10 +51,8 @@ public void ConfigureServices(IServiceCollection services)
5251

5352
// Register the health checks.
5453
services
55-
.AddScoped<HealthService>()
56-
.AddHealthChecks()
57-
.AddTypeActivatedCheck<AzureServiceBusQueueHealthCheck>("Health check for service bus verification queue", HealthStatus.Unhealthy, nameof(HrSettings.ServiceBusConnection), nameof(HrSettings.VerificationQueueName))
58-
.AddTypeActivatedCheck<SqlServerHealthCheck>("SQL Server", HealthStatus.Unhealthy, tags: default!, timeout: TimeSpan.FromSeconds(15), nameof(HrSettings.ConnectionStrings__Database));
54+
.AddHealthChecks();
55+
//.AddTypeActivatedCheck<AzureServiceBusQueueHealthCheck>("Verification Queue", HealthStatus.Unhealthy, nameof(HrSettings.ServiceBusConnection), nameof(HrSettings.VerificationQueueName))
5956

6057
services.AddControllers();
6158

@@ -90,6 +87,7 @@ public void Configure(IApplicationBuilder app)
9087
.UseRouting()
9188
.UseAuthorization()
9289
.UseExecutionContext()
90+
.UseHealthChecks("/healthz", new Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions { ResponseWriter = HealthReportStatusWriter.WriteJsonResults })
9391
.UseReferenceDataOrchestrator()
9492
.UseEndpoints(endpoints => endpoints.MapControllers());
9593
}

samples/My.Hr/My.Hr.Business/External/AgifyServiceClient.cs

+4-5
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ namespace My.Hr.Business.External;
55
/// </summary>
66
public class AgifyApiClient : TypedHttpClientCore<AgifyApiClient>
77
{
8-
public AgifyApiClient(HttpClient client, IJsonSerializer jsonSerializer, CoreEx.ExecutionContext executionContext, HrSettings settings, ILogger<TypedHttpClientCore<AgifyApiClient>> logger)
9-
: base(client, jsonSerializer, executionContext, settings, logger)
8+
public AgifyApiClient(HttpClient client, IJsonSerializer jsonSerializer, CoreEx.ExecutionContext executionContext, HrSettings settings)
9+
: base(client, jsonSerializer, executionContext)
1010
{
1111
if (!Uri.IsWellFormedUriString(settings.AgifyApiEndpointUri, UriKind.Absolute))
1212
throw new InvalidOperationException(@$"The Api endpoint URI is not valid: {settings.AgifyApiEndpointUri}. Provide valid Api endpoint URI in the configuration '{nameof(settings.AgifyApiEndpointUri)}'.
@@ -17,14 +17,13 @@ public AgifyApiClient(HttpClient client, IJsonSerializer jsonSerializer, CoreEx.
1717

1818
public override Task<HttpResult> HealthCheckAsync(CancellationToken cancellationToken)
1919
{
20-
return base.HeadAsync(string.Empty, null, new HttpArg<string>[] { new HttpArg<string>("name", "health") }, cancellationToken);
20+
return base.HeadAsync(string.Empty, null, new HttpArg<string>[] { new("name", "health") }, cancellationToken);
2121
}
2222

2323
public async Task<AgifyResponse> GetAgeAsync(string name)
2424
{
2525
var response = await
26-
WithRetry(1, 5)
27-
.ThrowTransientException()
26+
ThrowTransientException()
2827
.GetAsync<AgifyResponse>(string.Empty, null, HttpArgs.Create(new HttpArg<string>("name", name)));
2928

3029
return response.Value;

samples/My.Hr/My.Hr.Business/External/GenderizeApiClient.cs

+3-4
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ namespace My.Hr.Business.External;
55
/// </summary>
66
public class GenderizeApiClient : TypedHttpClientCore<GenderizeApiClient>
77
{
8-
public GenderizeApiClient(HttpClient client, IJsonSerializer jsonSerializer, CoreEx.ExecutionContext executionContext, HrSettings settings, ILogger<TypedHttpClientCore<GenderizeApiClient>> logger)
9-
: base(client, jsonSerializer, executionContext, settings, logger)
8+
public GenderizeApiClient(HttpClient client, IJsonSerializer jsonSerializer, CoreEx.ExecutionContext executionContext, HrSettings settings)
9+
: base(client, jsonSerializer, executionContext)
1010
{
1111
if (!Uri.IsWellFormedUriString(settings.GenderizeApiClientApiEndpointUri, UriKind.Absolute))
1212
throw new InvalidOperationException(@$"The Api endpoint URI is not valid: {settings.GenderizeApiClientApiEndpointUri}. Provide valid Api endpoint URI in the configuration '{nameof(settings.GenderizeApiClientApiEndpointUri)}'.
@@ -23,8 +23,7 @@ public override Task<HttpResult> HealthCheckAsync(CancellationToken cancellation
2323
public async Task<GenderizeResponse> GetGenderAsync(string name)
2424
{
2525
var response = await
26-
WithRetry(1, 5)
27-
.ThrowTransientException()
26+
ThrowTransientException()
2827
.GetAsync<GenderizeResponse>(string.Empty, null, HttpArgs.Create(new HttpArg<string>("name", name)));
2928

3029
return response.Value;

samples/My.Hr/My.Hr.Business/External/NationalizeApiClient.cs

+3-4
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ namespace My.Hr.Business.External;
55
/// </summary>
66
public class NationalizeApiClient : TypedHttpClientCore<NationalizeApiClient>
77
{
8-
public NationalizeApiClient(HttpClient client, IJsonSerializer jsonSerializer, CoreEx.ExecutionContext executionContext, HrSettings settings, ILogger<TypedHttpClientCore<NationalizeApiClient>> logger)
9-
: base(client, jsonSerializer, executionContext, settings, logger)
8+
public NationalizeApiClient(HttpClient client, IJsonSerializer jsonSerializer, CoreEx.ExecutionContext executionContext, HrSettings settings)
9+
: base(client, jsonSerializer, executionContext)
1010
{
1111
if (!Uri.IsWellFormedUriString(settings.NationalizeApiClientApiEndpointUri, UriKind.Absolute))
1212
throw new InvalidOperationException(@$"The Api endpoint URI is not valid: {settings.NationalizeApiClientApiEndpointUri}. Provide valid Api endpoint URI in the configuration '{nameof(settings.NationalizeApiClientApiEndpointUri)}'.
@@ -23,8 +23,7 @@ public override Task<HttpResult> HealthCheckAsync(CancellationToken cancellation
2323
public async Task<NationalizeResponse> GetNationalityAsync(string name)
2424
{
2525
var response = await
26-
WithRetry(1, 5)
27-
.ThrowTransientException()
26+
ThrowTransientException()
2827
.GetAsync<NationalizeResponse>(string.Empty, null, HttpArgs.Create(new HttpArg<string>("name", name)));
2928

3029
return response.Value;

samples/My.Hr/My.Hr.Functions/Functions/HttpHealthFunction.cs

-31
This file was deleted.

samples/My.Hr/My.Hr.Functions/Startup.cs

+4-6
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22
using CoreEx;
33
using CoreEx.AspNetCore.HealthChecks;
44
using CoreEx.AspNetCore.WebApis;
5-
using CoreEx.Azure.HealthChecks;
65
using CoreEx.Database;
7-
using CoreEx.DataBase.HealthChecks;
8-
using CoreEx.HealthChecks;
6+
using CoreEx.Database.HealthChecks;
7+
using CoreEx.Http.HealthChecks;
98
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
109
using Microsoft.EntityFrameworkCore;
1110
using Microsoft.Extensions.DependencyInjection;
@@ -48,13 +47,12 @@ public override void Configure(IFunctionsHostBuilder builder)
4847

4948
// Register the health checks.
5049
builder.Services
51-
.AddScoped<HealthService>()
5250
.AddHealthChecks()
5351
.AddTypeActivatedCheck<TypedHttpClientCoreHealthCheck<GenderizeApiClient>>("Genderize API")
5452
.AddTypeActivatedCheck<TypedHttpClientCoreHealthCheck<AgifyApiClient>>("Agify API")
5553
.AddTypeActivatedCheck<TypedHttpClientCoreHealthCheck<NationalizeApiClient>>("Nationalize API")
56-
.AddTypeActivatedCheck<AzureServiceBusQueueHealthCheck>("Health check for service bus verification queue", HealthStatus.Unhealthy, nameof(HrSettings.ServiceBusConnection), nameof(HrSettings.VerificationQueueName))
57-
.AddTypeActivatedCheck<SqlServerHealthCheck>("SQL Server", HealthStatus.Unhealthy, tags: default, timeout: System.TimeSpan.FromSeconds(15), nameof(HrSettings.ConnectionStrings__Database));
54+
//.AddTypeActivatedCheck<AzureServiceBusQueueHealthCheck>("Health check for service bus verification queue", HealthStatus.Unhealthy, nameof(HrSettings.ServiceBusConnection), nameof(HrSettings.VerificationQueueName))
55+
.AddTypeActivatedCheck<DatabaseHealthCheck<IDatabase>>("SQL Server", HealthStatus.Unhealthy, tags: default, timeout: System.TimeSpan.FromSeconds(15), nameof(HrSettings.ConnectionStrings__Database));
5856

5957
// Register the business services.
6058
builder.Services
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx
2+
3+
using CoreEx.Configuration;
4+
using Microsoft.AspNetCore.Http;
5+
using Microsoft.Extensions.Diagnostics.HealthChecks;
6+
using System;
7+
using System.IO;
8+
using System.Net.Mime;
9+
using System.Text;
10+
using System.Text.Json;
11+
using System.Threading.Tasks;
12+
13+
namespace CoreEx.AspNetCore.HealthChecks
14+
{
15+
/// <summary>
16+
/// Provides additional <see cref="HealthReport"/> <c>HealthCheckOptions.ResponseWriter</c> capabilities.
17+
/// </summary>
18+
public static class HealthReportStatusWriter
19+
{
20+
/// <summary>
21+
/// Writes the <paramref name="healthReport"/> as a JSON summary.
22+
/// </summary>
23+
/// <param name="context">The <see cref="HttpContext"/>.</param>
24+
/// <param name="healthReport">The <see cref="HealthReport"/>.</param>
25+
public static Task WriteJsonSummary(HttpContext context, HealthReport healthReport) => WriteJson(context, healthReport, false, false, null);
26+
27+
/// <summary>
28+
/// Writes the <paramref name="healthReport"/> as JSON including the <see cref="HealthReport.Entries"/> results.
29+
/// </summary>
30+
/// <param name="context">The <see cref="HttpContext"/>.</param>
31+
/// <param name="healthReport">The <see cref="HealthReport"/>.</param>
32+
public static Task WriteJsonResults(HttpContext context, HealthReport healthReport) => WriteJson(context, healthReport, true, false, null);
33+
34+
/// <summary>
35+
/// Writes the <paramref name="healthReport"/> as JSON including the <see cref="SettingsBase.Deployment"/> and <see cref="HealthReport.Entries"/> results.
36+
/// </summary>
37+
/// <param name="context">The <see cref="HttpContext"/>.</param>
38+
/// <param name="healthReport">The <see cref="HealthReport"/>.</param>
39+
public static async Task WriteJsonDeploymentResults(HttpContext context, HealthReport healthReport) => await WriteJson(context, healthReport, true, true, null).ConfigureAwait(false);
40+
41+
/// <summary>
42+
/// Writes the <paramref name="healthReport"/> as JSON to the <paramref name="context"/>.
43+
/// </summary>
44+
/// <param name="context">The <see cref="HttpContext"/>.</param>
45+
/// <param name="healthReport">The <see cref="HealthReport"/>.</param>
46+
/// <param name="includeResults">Indicates whether to include <see cref="HealthReport.Entries"/> results (where applicable).</param>
47+
/// <param name="includeDeployment">Indicates whether to include <see cref="SettingsBase.Deployment"/> information (where applicable).</param>
48+
/// <param name="extension">An action to enable extensions to the underlying JSON being written.</param>
49+
public static async Task WriteJson(HttpContext context, HealthReport healthReport, bool includeResults = true, bool includeDeployment = true, Action<HealthReport, Utf8JsonWriter>? extension = null)
50+
{
51+
using var memoryStream = new MemoryStream();
52+
using (var jsonWriter = new Utf8JsonWriter(memoryStream))
53+
{
54+
jsonWriter.WriteStartObject();
55+
jsonWriter.WriteString("status", healthReport.Status.ToString());
56+
jsonWriter.WriteString("duration", healthReport.TotalDuration.ToString());
57+
58+
if (ExecutionContext.HasCurrent)
59+
jsonWriter.WriteString("correlationId", ExecutionContext.Current.CorrelationId);
60+
61+
jsonWriter.WriteStartObject("results");
62+
63+
foreach (var e in healthReport.Entries)
64+
{
65+
jsonWriter.WriteStartObject(e.Key.Replace(' ', '-'));
66+
jsonWriter.WriteString("status", e.Value.Status.ToString());
67+
jsonWriter.WriteString("description", e.Value.Description);
68+
jsonWriter.WriteString("duration", e.Value.Duration.ToString());
69+
70+
if (e.Value.Exception is not null)
71+
{
72+
var settings = ExecutionContext.GetService<SettingsBase>();
73+
if (settings is not null && settings.IncludeExceptionInResult)
74+
jsonWriter.WriteString("exception", e.Value.Exception?.Message);
75+
}
76+
77+
if (includeDeployment)
78+
{
79+
var settings = ExecutionContext.GetService<SettingsBase>();
80+
if (settings is not null)
81+
{
82+
jsonWriter.WritePropertyName("deployment");
83+
JsonSerializer.Serialize(jsonWriter, settings, Text.Json.JsonSerializer.DefaultOptions);
84+
}
85+
}
86+
87+
if (includeResults && e.Value.Data.Count > 0)
88+
{
89+
jsonWriter.WriteStartObject("data");
90+
91+
foreach (var d in e.Value.Data)
92+
{
93+
jsonWriter.WritePropertyName(d.Key);
94+
JsonSerializer.Serialize(jsonWriter, d.Value, d.Value?.GetType() ?? typeof(object), Text.Json.JsonSerializer.DefaultOptions);
95+
}
96+
97+
jsonWriter.WriteEndObject();
98+
}
99+
100+
jsonWriter.WriteEndObject();
101+
}
102+
103+
extension?.Invoke(healthReport, jsonWriter);
104+
105+
jsonWriter.WriteEndObject();
106+
jsonWriter.WriteEndObject();
107+
}
108+
109+
context.Response.ContentType = MediaTypeNames.Application.Json;
110+
await context.Response.WriteAsync(Encoding.UTF8.GetString(memoryStream.ToArray())).ConfigureAwait(false);
111+
}
112+
}
113+
}

0 commit comments

Comments
 (0)