Skip to content

Commit 8aef993

Browse files
The most significant changes in the code include the update of the Kentico.Xperience.Libraries package from version 13.0.70 to 13.0.74, the addition of new files IObjectQueryExtensions.cs and BaseKenticoHealthCheck.cs, and the refactoring of several health check classes.
1. The `Kentico.Xperience.Libraries` package was updated to version `13.0.74` from `13.0.70`, which is reflected in the `packages.lock.json` file. 2. The `StagingTaskHealthCheck` was commented out in the `DependencyInjection.cs` file. 3. A new file `IObjectQueryExtensions.cs` was added, providing extension methods for `IObjectQuery`. 4. The `AzureSearchTaskHealthCheck` class was refactored to inherit from `BaseKenticoHealthCheck<SearchTaskAzureInfo>`, which involved refactoring the `CheckHealthAsync` method and adding new methods for data retrieval and error data. 5. A new file `BaseKenticoHealthCheck.cs` was added, which provides a base class for Kentico health checks. 6. Several health check classes, including `EventLogHealthCheck`, `LocalSearchTaskHealthCheck`, `SiteConfigurationHealthCheck`, `SitePresentationHealthCheck`, and `WebFarmTaskHealthCheck`, were refactored in a similar way to `AzureSearchTaskHealthCheck`. 7. The `StagingTaskHealthCheck` class was refactored to use the `ToList` method instead of `GetEnumerableTypedResultAsync`. 8. The `WebFarmHealthCheck` class was refactored to check if `webFarmServers` is null or empty before proceeding.
1 parent 5e9166b commit 8aef993

13 files changed

+219
-126
lines changed

Directory.Packages.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
44
</PropertyGroup>
55
<ItemGroup>
6-
<PackageVersion Include="Kentico.Xperience.Libraries" Version="[13.0.70, 13.1.0)" />
6+
<PackageVersion Include="Kentico.Xperience.Libraries" Version="[13.0.74, 13.1.0)" />
77
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.0.0" />
88
<PackageVersion Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
99
<PackageVersion Include="CSharpFunctionalExtensions" Version="2.41.0" />

src/XperienceCommunity.AspNetCore.HealthChecks/DependencyInjection.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public static IHealthChecksBuilder AddKenticoHealthChecks(this IServiceCollectio
3636
.AddHealthChecks()
3737
.AddCheck<SiteConfigurationHealthCheck>("Site Configuration Health Check", tags: s_tags)
3838
.AddCheck<SitePresentationHealthCheck>("Site Presentation Url Health Check", tags: s_tags)
39-
.AddCheck<StagingTaskHealthCheck>("Staging Task Health Check", tags: s_tags)
39+
//.AddCheck<StagingTaskHealthCheck>("Staging Task Health Check", tags: s_tags)
4040
.AddCheck<EventLogHealthCheck>("Search Task Health Check", tags: s_tags)
4141
.AddCheck<WebFarmHealthCheck>("Web Farm Health Check", tags: s_tags)
4242
.AddCheck<AzureSearchTaskHealthCheck>("Azure Search Task Health Checks", tags: s_tags)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System.Data;
2+
using CMS.DataEngine;
3+
4+
namespace XperienceCommunity.AspNetCore.HealthChecks.Extensions
5+
{
6+
public static class IObjectQueryExtensions
7+
{
8+
/// <summary>
9+
/// Return an IEnumerable of objects from the query.
10+
/// </summary>
11+
/// <typeparam name="TQuery"></typeparam>
12+
/// <typeparam name="TObject"></typeparam>
13+
/// <param name="query"></param>
14+
/// <param name="cancellationToken"></param>
15+
/// <returns></returns>
16+
/// <exception cref="ArgumentNullException"></exception>
17+
public static async Task<IEnumerable<TObject>> ToListAsync<TQuery, TObject>(this IObjectQuery<TQuery, TObject> query, CancellationToken cancellationToken = default)
18+
where TQuery : IObjectQuery<TQuery, TObject>
19+
where TObject : BaseInfo
20+
{
21+
ArgumentNullException.ThrowIfNull(query);
22+
23+
cancellationToken.ThrowIfCancellationRequested();
24+
25+
var results = await query.GetTypedQuery()
26+
//.GetEnumerableResultAsync(CommandBehavior.Default, false, cancellationToken)
27+
.GetEnumerableTypedResultAsync(cancellationToken: cancellationToken)
28+
//.GetEnumerableTypedResultAsync(commandBehavior: CommandBehavior.CloseConnection, true, cancellationToken: cancellationToken)
29+
.ConfigureAwait(false);
30+
31+
32+
return results.ToList() ?? Enumerable.Empty<TObject>();
33+
}
34+
}
35+
}

src/XperienceCommunity.AspNetCore.HealthChecks/HealthChecks/AzureSearchTaskHealthCheck.cs

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@
44
using CMS.Search;
55
using CMS.Search.Azure;
66
using Microsoft.Extensions.Diagnostics.HealthChecks;
7+
using XperienceCommunity.AspNetCore.HealthChecks.Extensions;
78

89
namespace XperienceCommunity.AspNetCore.HealthChecks.HealthChecks
910
{
1011
/// <summary>
1112
/// Azure Search Task Health Check
1213
/// </summary>
1314
/// <remarks>Checks the Azure Search Task for any errors.</remarks>
14-
public sealed class AzureSearchTaskHealthCheck : IHealthCheck
15+
public sealed class AzureSearchTaskHealthCheck : BaseKenticoHealthCheck<SearchTaskAzureInfo>, IHealthCheck
1516
{
1617
private readonly ISearchTaskAzureInfoProvider _searchTaskAzureInfoProvider;
1718
private readonly IProgressiveCache _cache;
@@ -27,25 +28,7 @@ public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context
2728
{
2829
try
2930
{
30-
// Asynchronously loads data and ensures caching
31-
var data = await _cache.LoadAsync(async cacheSettings =>
32-
{
33-
// Calls an async method that loads the required data
34-
var result = await _searchTaskAzureInfoProvider
35-
.Get()
36-
.GetEnumerableTypedResultAsync(CommandBehavior.CloseConnection, true, cancellationToken)
37-
.ConfigureAwait(false);
38-
39-
cacheSettings.CacheDependency =
40-
CacheHelper.GetCacheDependency($"{SearchTaskAzureInfo.OBJECT_TYPE}|all");
41-
42-
return result;
43-
},
44-
new CacheSettings(TimeSpan.FromMinutes(10).TotalMinutes,
45-
$"apphealth|{SearchTaskAzureInfo.OBJECT_TYPE}"))
46-
.ConfigureAwait(false);
47-
48-
var searchTasks = data.ToList();
31+
var searchTasks = (await GetDataForTypeAsync(cancellationToken)).ToList();
4932

5033
if (searchTasks.Count == 0)
5134
{
@@ -60,13 +43,11 @@ public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context
6043
return HealthCheckResult.Healthy();
6144
}
6245

63-
var healthResultData = GetData(errorTasks);
64-
65-
return HealthCheckResult.Degraded("Azure Search Tasks Contain Errors.", data: healthResultData);
46+
return HealthCheckResult.Degraded("Azure Search Tasks Contain Errors.", data: GetErrorData(errorTasks));
6647
}
6748
catch (InvalidOperationException ex)
6849
{
69-
if (ex.Message.Contains("open DataReader", StringComparison.OrdinalIgnoreCase))
50+
if (ex.Message.Contains("open DataReader", StringComparison.OrdinalIgnoreCase) || ex.Message.Contains("current state", StringComparison.OrdinalIgnoreCase))
7051
{
7152
return HealthCheckResult.Healthy();
7253
}
@@ -79,7 +60,24 @@ public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context
7960
}
8061
}
8162

82-
private static IReadOnlyDictionary<string, object> GetData(IEnumerable<SearchTaskAzureInfo> objects)
63+
protected override IEnumerable<SearchTaskAzureInfo> GetDataForType()
64+
{
65+
var result = _searchTaskAzureInfoProvider.Get()
66+
.WhereNotEmpty(nameof(SearchTaskAzureInfo.SearchTaskAzureErrorMessage));
67+
68+
return result.ToList();
69+
}
70+
71+
protected override async Task<IEnumerable<SearchTaskAzureInfo>> GetDataForTypeAsync(
72+
CancellationToken cancellationToken = default)
73+
{
74+
var query = _searchTaskAzureInfoProvider.Get()
75+
.WhereNotEmpty(nameof(SearchTaskAzureInfo.SearchTaskAzureErrorMessage));
76+
77+
return await query.ToListAsync(cancellationToken: cancellationToken);
78+
}
79+
80+
protected override IReadOnlyDictionary<string, object> GetErrorData(IEnumerable<SearchTaskAzureInfo> objects)
8381
{
8482
var dictionary = objects.ToDictionary<SearchTaskAzureInfo, string, object>(searchTask => searchTask.SearchTaskAzureID.ToString(), searchTask => searchTask.SearchTaskAzureErrorMessage);
8583

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using CMS.DataEngine;
2+
3+
namespace XperienceCommunity.AspNetCore.HealthChecks.HealthChecks
4+
{
5+
/// <summary>
6+
/// Represents a base class for Kentico health checks.
7+
/// </summary>
8+
/// <typeparam name="T">The type of the Kentico object.</typeparam>
9+
public abstract class BaseKenticoHealthCheck<T> where T : BaseInfo
10+
{
11+
/// <summary>
12+
/// Gets the data for the specified Kentico object type.
13+
/// </summary>
14+
/// <returns>An enumerable collection of Kentico objects.</returns>
15+
protected abstract IEnumerable<T> GetDataForType();
16+
17+
/// <summary>
18+
/// Gets the data for the specified Kentico object type asynchronously.
19+
/// </summary>
20+
/// <param name="cancellationToken">The cancellation token.</param>
21+
/// <returns>A task that represents the asynchronous operation and contains an enumerable collection of Kentico objects.</returns>
22+
protected abstract Task<IEnumerable<T>> GetDataForTypeAsync(CancellationToken cancellationToken = default);
23+
24+
/// <summary>
25+
/// Gets the error data for the specified Kentico objects.
26+
/// </summary>
27+
/// <param name="objects">The Kentico objects.</param>
28+
/// <returns>A read-only dictionary containing the error data.</returns>
29+
protected abstract IReadOnlyDictionary<string, object> GetErrorData(IEnumerable<T> objects);
30+
}
31+
}

src/XperienceCommunity.AspNetCore.HealthChecks/HealthChecks/EventLogHealthCheck.cs

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@
22
using System.Data;
33
using CMS.EventLog;
44
using CMS.Helpers;
5+
using CMS.Search.Azure;
6+
using CMS.SiteProvider;
57
using Microsoft.Extensions.Diagnostics.HealthChecks;
8+
using XperienceCommunity.AspNetCore.HealthChecks.Extensions;
69

710
namespace XperienceCommunity.AspNetCore.HealthChecks.HealthChecks
811
{
912
/// <summary>
1013
/// Event Log Health Check
1114
/// </summary>
1215
/// <remarks>Investigates the Last 100 Event Log Entries for Errors.</remarks>
13-
public sealed class EventLogHealthCheck : IHealthCheck
16+
public sealed class EventLogHealthCheck : BaseKenticoHealthCheck<EventLogInfo>, IHealthCheck
1417
{
1518
private readonly IEventLogInfoProvider _eventLogInfoProvider;
1619
private readonly IProgressiveCache _cache;
@@ -26,38 +29,25 @@ public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context
2629
{
2730
try
2831
{
29-
var data = await _cache.LoadAsync(async cs =>
30-
{
31-
var results = await _eventLogInfoProvider
32-
.Get()
33-
.GetEnumerableTypedResultAsync(CommandBehavior.CloseConnection, true, cancellationToken)
34-
.ConfigureAwait(false);
35-
36-
cs.CacheDependency = CacheHelper.GetCacheDependency($"{EventLogInfo.OBJECT_TYPE}|all");
37-
38-
return results;
39-
}, new CacheSettings(TimeSpan.FromMinutes(10).TotalMinutes, $"apphealth|{EventLogInfo.OBJECT_TYPE}"))
40-
.ConfigureAwait(false);
41-
42-
var eventList = data.ToList();
32+
var eventList = (await GetDataForTypeAsync(cancellationToken)).ToList();
4333

4434
var exceptionEvents = eventList
45-
.Where(e => e.EventType == "E"
46-
&& e.EventTime >= DateTime.UtcNow.AddHours(-24)
35+
.Where(e => e.EventType == "E"
36+
&& e.EventTime >= DateTime.UtcNow.AddHours(-24)
4737
&& !e.Source.Equals(nameof(HealthReport), StringComparison.OrdinalIgnoreCase))
4838
.OrderByDescending(x => x.EventID)
4939
.ToList();
5040

5141
if (exceptionEvents.Count >= 25)
5242
{
53-
return HealthCheckResult.Degraded($"There are {exceptionEvents.Count} errors in the event log.", null, GetData(exceptionEvents));
43+
return HealthCheckResult.Degraded($"There are {exceptionEvents.Count} errors in the event log.", null, GetErrorData(exceptionEvents));
5444
}
5545

5646
return HealthCheckResult.Healthy();
5747
}
5848
catch (InvalidOperationException ex)
5949
{
60-
if (ex.Message.Contains("open DataReader", StringComparison.OrdinalIgnoreCase))
50+
if (ex.Message.Contains("open DataReader", StringComparison.OrdinalIgnoreCase) || ex.Message.Contains("current state", StringComparison.OrdinalIgnoreCase))
6151
{
6252
return HealthCheckResult.Healthy();
6353
}
@@ -70,7 +60,25 @@ public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context
7060
}
7161
}
7262

73-
private static IReadOnlyDictionary<string, object> GetData(IEnumerable<EventLogInfo> objects)
63+
protected override IEnumerable<EventLogInfo> GetDataForType()
64+
{
65+
var query = _eventLogInfoProvider.Get()
66+
.WhereEquals(nameof(EventLogInfo.EventType), "E")
67+
.WhereNotEquals(nameof(EventLogInfo.Source), nameof(HealthReport));
68+
69+
return query.ToList();
70+
}
71+
72+
protected override async Task<IEnumerable<EventLogInfo>> GetDataForTypeAsync(CancellationToken cancellationToken = default)
73+
{
74+
var query = _eventLogInfoProvider.Get()
75+
.WhereEquals(nameof(EventLogInfo.EventType), "E")
76+
.WhereNotEquals(nameof(EventLogInfo.Source), nameof(HealthReport));
77+
78+
return await query.ToListAsync(cancellationToken: cancellationToken);
79+
}
80+
81+
protected override IReadOnlyDictionary<string, object> GetErrorData(IEnumerable<EventLogInfo> objects)
7482
{
7583
var dictionary = objects.ToDictionary<EventLogInfo, string, object>(e => e.EventID.ToString(), ev => ev.Exception);
7684

src/XperienceCommunity.AspNetCore.HealthChecks/HealthChecks/LocalSearchTaskHealthCheck.cs

Lines changed: 23 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
using CMS.Helpers;
44
using CMS.Search;
55
using Microsoft.Extensions.Diagnostics.HealthChecks;
6+
using XperienceCommunity.AspNetCore.HealthChecks.Extensions;
67

78
namespace XperienceCommunity.AspNetCore.HealthChecks.HealthChecks
89
{
910
/// <summary>
1011
/// Local Search Task Health Check
1112
/// </summary>
1213
/// <remarks>Checks the Local Search Tasks to determine if any errors are present.</remarks>
13-
public sealed class LocalSearchTaskHealthCheck : IHealthCheck
14+
public sealed class LocalSearchTaskHealthCheck : BaseKenticoHealthCheck<SearchTaskInfo>, IHealthCheck
1415
{
1516
private readonly ISearchTaskInfoProvider _searchTaskInfoProvider;
1617
private readonly IProgressiveCache _cache;
@@ -25,42 +26,18 @@ public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context
2526
{
2627
try
2728
{
28-
// Asynchronously loads data and ensures caching
29-
var data = await _cache.LoadAsync(async cacheSettings =>
30-
{
31-
// Calls an async method that loads the required data
32-
var result = await _searchTaskInfoProvider
33-
.Get()
34-
.GetEnumerableTypedResultAsync(CommandBehavior.CloseConnection, true, cancellationToken)
35-
.ConfigureAwait(false);
36-
37-
cacheSettings.CacheDependency = CacheHelper.GetCacheDependency($"{SearchTaskInfo.OBJECT_TYPE}|all");
38-
39-
return result;
40-
}, new CacheSettings(TimeSpan.FromMinutes(10).TotalMinutes, $"apphealth|{SearchTaskInfo.OBJECT_TYPE}"))
41-
.ConfigureAwait(false);
42-
43-
var searchTasks = data.ToList();
29+
var searchTasks = (await GetDataForTypeAsync(cancellationToken)).ToList();
4430

4531
if (searchTasks.Count == 0)
4632
{
4733
return HealthCheckResult.Healthy();
4834
}
4935

50-
var errorTasks = searchTasks.Where(searchTask => !string.IsNullOrEmpty(searchTask.SearchTaskErrorMessage)).ToList();
51-
52-
if (errorTasks.Count == 0)
53-
{
54-
return HealthCheckResult.Healthy();
55-
}
56-
57-
var resultData = GetData(errorTasks);
58-
59-
return HealthCheckResult.Degraded("Local Search Tasks Contain Errors.", data: resultData);
36+
return HealthCheckResult.Degraded("Local Search Tasks Contain Errors.", data: GetErrorData(searchTasks));
6037
}
6138
catch (InvalidOperationException ex)
6239
{
63-
if (ex.Message.Contains("open DataReader", StringComparison.OrdinalIgnoreCase))
40+
if (ex.Message.Contains("open DataReader", StringComparison.OrdinalIgnoreCase) || ex.Message.Contains("current state", StringComparison.OrdinalIgnoreCase))
6441
{
6542
return HealthCheckResult.Healthy();
6643
}
@@ -73,7 +50,24 @@ public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context
7350
}
7451
}
7552

76-
private static IReadOnlyDictionary<string, object> GetData(IEnumerable<SearchTaskInfo> objects)
53+
protected override IEnumerable<SearchTaskInfo> GetDataForType()
54+
{
55+
var query = _searchTaskInfoProvider.Get()
56+
.WhereNotEmpty(nameof(SearchTaskInfo.SearchTaskErrorMessage));
57+
58+
return query.ToList();
59+
}
60+
61+
protected override async Task<IEnumerable<SearchTaskInfo>> GetDataForTypeAsync(CancellationToken cancellationToken = default)
62+
{
63+
64+
var query = _searchTaskInfoProvider.Get()
65+
.WhereNotEmpty(nameof(SearchTaskInfo.SearchTaskErrorMessage));
66+
67+
return await query.ToListAsync(cancellationToken: cancellationToken);
68+
}
69+
70+
protected override IReadOnlyDictionary<string, object> GetErrorData(IEnumerable<SearchTaskInfo> objects)
7771
{
7872
var dictionary = objects.ToDictionary<SearchTaskInfo, string, object>(searchTask => searchTask.SearchTaskID.ToString(), searchTask => searchTask.SearchTaskErrorMessage);
7973

0 commit comments

Comments
 (0)