Skip to content

Commit ce9f2dc

Browse files
authoredOct 17, 2024
v3.27.2 (#127)
- *Fixed:* The `IServiceCollection.AddCosmosDb` extension method was registering as a singleton; this has been corrected to register as scoped. The dependent `CosmosClient` should remain a singleton as is [best practice](https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/best-practice-dotnet).
1 parent 26ecd3a commit ce9f2dc

File tree

9 files changed

+34
-12
lines changed

9 files changed

+34
-12
lines changed
 

‎CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
Represents the **NuGet** versions.
44

5+
## v3.27.2
6+
- *Fixed:* The `IServiceCollection.AddCosmosDb` extension method was registering as a singleton; this has been corrected to register as scoped. The dependent `CosmosClient` should remain a singleton as is [best practice](https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/best-practice-dotnet).
7+
58
## v3.27.1
69
- *Fixed:* Updated `Microsoft.Extensions.Caching.Memory` package depenedency to latest (including related); resolve [Microsoft Security Advisory CVE-2024-43483](https://github.com/advisories/GHSA-qj66-m88j-hmgj).
710
- *Fixed:* Fixed the `ExecutionContext.UserIsAuthorized` to have base implementation similar to `UserIsInRole`.

‎Common.targets

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

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public void ConfigureServices(IServiceCollection services)
3434
.AddAzureServiceBusSender()
3535
.AddAzureServiceBusPurger()
3636
.AddJsonMergePatch()
37-
.AddWebApi((_, c) => c.UnhandledExceptionAsync = (ex, _, _) => Task.FromResult(ex is DbUpdateConcurrencyException efex ? WebApiBase.CreateActionResultFromExtendedException(new ConcurrencyException()) : null))
37+
.AddWebApi((_, webapi) => webapi.UnhandledExceptionAsync = (ex, _, _) => Task.FromResult(ex is DbUpdateConcurrencyException efex ? webapi.CreateActionResultFromExtendedException(new ConcurrencyException()) : null))
3838
.AddReferenceDataContentWebApi()
3939
.AddRequestCache();
4040

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public override void Configure(IFunctionsHostBuilder builder)
4040
.AddEventPublisher()
4141
.AddSingleton(sp => new Az.ServiceBusClient(sp.GetRequiredService<HrSettings>().ServiceBusConnection__fullyQualifiedNamespace))
4242
.AddAzureServiceBusSender()
43-
.AddWebApi((_, c) => c.UnhandledExceptionAsync = (ex, _, _) => Task.FromResult(ex is DbUpdateConcurrencyException efex ? WebApiBase.CreateActionResultFromExtendedException(new ConcurrencyException()) : null))
43+
.AddWebApi((_, webapi) => webapi.UnhandledExceptionAsync = (ex, _, _) => Task.FromResult(ex is DbUpdateConcurrencyException efex ? webapi.CreateActionResultFromExtendedException(new ConcurrencyException()) : null))
4444
.AddJsonMergePatch()
4545
.AddWebApiPublisher()
4646
.AddAzureServiceBusSubscriber();

‎src/CoreEx.AspNetCore/WebApis/WebApiBase.cs

+17-2
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ public abstract class WebApiBase(ExecutionContext executionContext, SettingsBase
6363
/// <remarks>Searches the <see cref="HttpRequest.Headers"/> for <see cref="HttpConsts.CorrelationIdHeaderName"/> or one of the other <see cref="SecondaryCorrelationIdNames"/> to determine the <see cref="ExecutionContext.CorrelationId"/> (uses first value found in sequence).</remarks>
6464
public IEnumerable<string> SecondaryCorrelationIdNames { get; set; } = ["x-ms-client-tracking-id"];
6565

66+
/// <summary>
67+
/// Gets or sets the <see cref="IExtendedException"/> <see cref="IActionResult"/> creator function used by <see cref="CreateActionResultFromExtendedException(IExtendedException)"/>.
68+
/// </summary>
69+
/// <remarks>This allows an alternate serialization or handling as required. Defaults to the <see cref="DefaultExtendedExceptionActionResultCreator"/>.</remarks>
70+
public Func<IExtendedException, IActionResult> ExtendedExceptionActionResultCreator { get; set; } = DefaultExtendedExceptionActionResultCreator;
71+
6672
/// <summary>
6773
/// Gets the list of correlation identifier names, being <see cref="HttpConsts.CorrelationIdHeaderName"/> and <see cref="SecondaryCorrelationIdNames"/> (inclusive).
6874
/// </summary>
@@ -183,7 +189,9 @@ public static async Task<IActionResult> CreateActionResultFromExceptionAsync(Web
183189
if (eex.ShouldBeLogged)
184190
logger.LogError(exception, "{Error}", exception.Message);
185191

186-
ar = CreateActionResultFromExtendedException(eex);
192+
ar = owner is null
193+
? DefaultExtendedExceptionActionResultCreator(eex)
194+
: owner.CreateActionResultFromExtendedException(eex);
187195
}
188196
else
189197
{
@@ -204,7 +212,14 @@ public static async Task<IActionResult> CreateActionResultFromExceptionAsync(Web
204212
/// Creates an <see cref="IActionResult"/> from an <paramref name="extendedException"/>.
205213
/// </summary>
206214
/// <param name="extendedException">The <see cref="IExtendedException"/>.</param>
207-
public static IActionResult CreateActionResultFromExtendedException(IExtendedException extendedException)
215+
public IActionResult CreateActionResultFromExtendedException(IExtendedException extendedException) => ExtendedExceptionActionResultCreator(extendedException);
216+
217+
/// <summary>
218+
/// The default <see cref="ExtendedExceptionActionResultCreator"/>.
219+
/// </summary>
220+
/// <param name="extendedException">The <see cref="IExtendedException"/>.</param>
221+
/// <returns>The resulting <see cref="IActionResult"/>.</returns>
222+
public static IActionResult DefaultExtendedExceptionActionResultCreator(IExtendedException extendedException)
208223
{
209224
if (extendedException is ValidationException vex && vex.Messages is not null && vex.Messages.Count > 0)
210225
{

‎src/CoreEx.Cosmos/CosmosDb.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ namespace CoreEx.Cosmos
2424
/// <param name="database">The <see cref="Microsoft.Azure.Cosmos.Database"/>.</param>
2525
/// <param name="mapper">The <see cref="IMapper"/>.</param>
2626
/// <param name="invoker">Enables the <see cref="Invoker"/> to be overridden; defaults to <see cref="CosmosDbInvoker"/>.</param>
27+
/// <remarks>It is recommended that the <see cref="CosmosDb"/> is registered as a scoped service to enable capabilities such as <see cref="CosmosDbArgs.FilterByTenantId"/> that <i>must</i> be scoped.
28+
/// Use <see cref="Microsoft.Extensions.DependencyInjection.CosmosDbServiceCollectionExtensions.AddCosmosDb{TCosmosDb}(Microsoft.Extensions.DependencyInjection.IServiceCollection, Func{IServiceProvider, TCosmosDb}, string?)"/> to
29+
/// register the scoped <see cref="CosmosDb"/> instance.
30+
/// <para>The dependent <see cref="CosmosClient"/> should however be registered as a singleton as is <see href="https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/best-practice-dotnet">best practice</see>.</para></remarks>
2731
public class CosmosDb(Database database, IMapper mapper, CosmosDbInvoker? invoker = null) : ICosmosDb
2832
{
2933
private static CosmosDbInvoker? _invoker;
@@ -181,7 +185,8 @@ public async Task<Result> SelectMultiSetWithResultAsync(PartitionKey partitionKe
181185

182186
if (multiSetList.Any(x => !x.Container.IsCosmosDbValueModel))
183187
throw new ArgumentException($"All {nameof(IMultiSetArgs)} containers must be of type CosmosDbValueContainer.", nameof(multiSetArgs));
184-
188+
189+
// Build the Cosmos SQL statement.
185190
var container = multiSetList[0].Container;
186191
var types = new Dictionary<string, IMultiSetArgs>([ new KeyValuePair<string, IMultiSetArgs>(container.ModelType.Name, multiSetList[0]) ]);
187192
var sb = string.IsNullOrEmpty(sql) ? new StringBuilder($"SELECT * FROM c WHERE c.type in (\"{container.ModelType.Name}\"") : null;

‎src/CoreEx.Cosmos/CosmosDbServiceCollectionExtensions.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace Microsoft.Extensions.DependencyInjection
1414
public static class CosmosDbServiceCollectionExtensions
1515
{
1616
/// <summary>
17-
/// Adds an <see cref="ICosmosDb"/> as a singleton service.
17+
/// Adds an <see cref="ICosmosDb"/> as a scoped service.
1818
/// </summary>
1919
/// <typeparam name="TCosmosDb">The <see cref="ICosmosDb"/> <see cref="Type"/>.</typeparam>
2020
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
@@ -23,15 +23,15 @@ public static class CosmosDbServiceCollectionExtensions
2323
/// <returns>The <see cref="IServiceCollection"/> to support fluent-style method-chaining.</returns>
2424
public static IServiceCollection AddCosmosDb<TCosmosDb>(this IServiceCollection services, Func<IServiceProvider, TCosmosDb> create, bool healthCheck = true) where TCosmosDb : class, ICosmosDb
2525
{
26-
services.ThrowIfNull(nameof(services)).AddSingleton(sp => create.ThrowIfNull(nameof(create)).Invoke(sp));
26+
services.ThrowIfNull(nameof(services)).AddScoped(sp => create.ThrowIfNull(nameof(create)).Invoke(sp));
2727
if (healthCheck)
2828
services.AddHealthChecks().AddCosmosDbHealthCheck<TCosmosDb>();
2929

3030
return services;
3131
}
3232

3333
/// <summary>
34-
/// Adds an <see cref="ICosmosDb"/> as a singleton service including a corresponding health check.
34+
/// Adds an <see cref="ICosmosDb"/> as a scoped service including a corresponding health check.
3535
/// </summary>
3636
/// <typeparam name="TCosmosDb">The <see cref="ICosmosDb"/> <see cref="Type"/>.</typeparam>
3737
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
@@ -40,7 +40,7 @@ public static IServiceCollection AddCosmosDb<TCosmosDb>(this IServiceCollection
4040
/// <returns>The <see cref="IServiceCollection"/> to support fluent-style method-chaining.</returns>
4141
public static IServiceCollection AddCosmosDb<TCosmosDb>(this IServiceCollection services, Func<IServiceProvider, TCosmosDb> create, string? healthCheckName) where TCosmosDb : class, ICosmosDb
4242
{
43-
services.ThrowIfNull(nameof(services)).AddSingleton(sp => create.ThrowIfNull(nameof(create)).Invoke(sp));
43+
services.ThrowIfNull(nameof(services)).AddScoped(sp => create.ThrowIfNull(nameof(create)).Invoke(sp));
4444
services.AddHealthChecks().AddCosmosDbHealthCheck<TCosmosDb>(healthCheckName);
4545
return services;
4646
}

‎src/CoreEx/Events/EventDataFormatter.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ public virtual void Format(EventData @event)
152152
var value = @event.Value;
153153

154154
@event.Id ??= Guid.NewGuid().ToString();
155-
@event.Timestamp ??= new DateTimeOffset(ExecutionContext.SystemTime.UtcNow);
155+
@event.Timestamp ??= new DateTimeOffset(ExecutionContext.HasCurrent ? ExecutionContext.Current.Timestamp : ExecutionContext.SystemTime.UtcNow);
156156

157157
if (PropertySelection.HasFlag(EventDataProperty.Key))
158158
{

‎src/CoreEx/ExecutionContext.cs

-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
using System;
99
using System.Collections.Concurrent;
1010
using System.Collections.Generic;
11-
using System.Linq;
1211
using System.Threading;
1312

1413
namespace CoreEx

0 commit comments

Comments
 (0)