Skip to content

Commit fa2a1e1

Browse files
authored
Merge pull request #3 from Zio-Net/release/v1.1.0
Release/v1.1.0
2 parents f91e286 + 7e6a568 commit fa2a1e1

28 files changed

+827
-51
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
bin/
33
obj/
44
.vscode/settings.json
5+
.claude/settings.local.json

src/TypedRequestContext.Propagation/Abstractions/IRequestContextDeserializer.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using TypedRequestContext;
2-
31
namespace TypedRequestContext.Propagation;
42

53
/// <summary>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
namespace TypedRequestContext.Propagation;
2+
3+
/// <summary>
4+
/// Combines deserialization and accessor management into a single scoped operation.
5+
/// Intended for non-HTTP flows such as queues, events, or background processing,
6+
/// where context arrives as a metadata dictionary rather than HTTP headers or claims.
7+
/// </summary>
8+
/// <typeparam name="T">The typed request context to propagate.</typeparam>
9+
/// <remarks>
10+
/// Calling <see cref="Propagate(IReadOnlyDictionary{string, string})"/> deserializes the metadata,
11+
/// validates and sets the context on <see cref="IRequestContextAccessor"/>, and returns an
12+
/// <see cref="IDisposable"/> that restores the previous context when disposed.
13+
/// Use a <c>using</c> statement to guarantee cleanup even if the handler throws.
14+
/// <code>
15+
/// using var _ = propagator.Propagate(metadata);
16+
/// // CustomerRequestContext is now accessible via DI or GetRequired&lt;T&gt;()
17+
/// await DoWorkAsync(ct);
18+
/// </code>
19+
/// For validators with scoped dependencies, use the overload accepting <see cref="IServiceProvider"/>:
20+
/// <code>
21+
/// using var scope = serviceProvider.CreateScope();
22+
/// using var _ = propagator.Propagate(metadata, scope.ServiceProvider);
23+
/// </code>
24+
/// </remarks>
25+
public interface IRequestContextPropagator<T> where T : class, ITypedRequestContext
26+
{
27+
/// <summary>
28+
/// Deserializes <paramref name="metadata"/> into a <typeparamref name="T"/> context,
29+
/// validates (if configured), sets it as the current context on
30+
/// <see cref="IRequestContextAccessor"/>, and returns a scope that restores
31+
/// the previous context when disposed.
32+
/// </summary>
33+
/// <param name="metadata">The key/value metadata (e.g. message headers) to deserialize from.</param>
34+
/// <returns>
35+
/// An <see cref="IDisposable"/> scope. Dispose it (or use a <c>using</c> block) to
36+
/// restore the previous context after the handler finishes.
37+
/// </returns>
38+
/// <exception cref="Infrastructure.RequestContextDeserializationException">
39+
/// Thrown when a required propagation key is missing or cannot be converted.
40+
/// </exception>
41+
IDisposable Propagate(IReadOnlyDictionary<string, string> metadata);
42+
43+
/// <summary>
44+
/// Deserializes <paramref name="metadata"/> into a <typeparamref name="T"/> context,
45+
/// validates (if configured) using the provided <paramref name="serviceProvider"/>
46+
/// (enabling scoped validator dependencies), sets it as the current context,
47+
/// and returns a scope that restores the previous context when disposed.
48+
/// </summary>
49+
/// <param name="metadata">The key/value metadata (e.g. message headers) to deserialize from.</param>
50+
/// <param name="serviceProvider">
51+
/// The <see cref="IServiceProvider"/> to use for resolving validators.
52+
/// Pass a scoped provider when validators depend on scoped services.
53+
/// </param>
54+
/// <returns>
55+
/// An <see cref="IDisposable"/> scope. Dispose it (or use a <c>using</c> block) to
56+
/// restore the previous context after the handler finishes.
57+
/// </returns>
58+
/// <exception cref="Infrastructure.RequestContextDeserializationException">
59+
/// Thrown when a required propagation key is missing or cannot be converted.
60+
/// </exception>
61+
IDisposable Propagate(IReadOnlyDictionary<string, string> metadata, IServiceProvider serviceProvider);
62+
}

src/TypedRequestContext.Propagation/Abstractions/IRequestContextSerializer.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using TypedRequestContext;
2-
31
namespace TypedRequestContext.Propagation;
42

53
/// <summary>

src/TypedRequestContext.Propagation/DependencyInjection/TypedRequestContextPropagationServiceCollectionExtensions.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using TypedRequestContext;
2-
using TypedRequestContext.Propagation;
31
using TypedRequestContext.Propagation.Infrastructure;
42

53
namespace TypedRequestContext.Propagation;
@@ -24,6 +22,10 @@ public static IServiceCollection AddTypedRequestContextPropagation(this IService
2422
typeof(IRequestContextSerializer<>),
2523
typeof(AttributeBasedRequestContextSerializer<>));
2624

25+
services.AddSingleton(
26+
typeof(IRequestContextPropagator<>),
27+
typeof(RequestContextPropagator<>));
28+
2729
return services;
2830
}
2931
}

src/TypedRequestContext.Propagation/Infrastructure/AttributeBasedRequestContextDeserializer.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
using System.Reflection;
2-
using TypedRequestContext;
3-
using TypedRequestContext.Propagation;
42

53
namespace TypedRequestContext.Propagation.Infrastructure;
64

src/TypedRequestContext.Propagation/Infrastructure/AttributeBasedRequestContextSerializer.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
using System.Reflection;
22
using System.Globalization;
3-
using TypedRequestContext;
4-
using TypedRequestContext.Propagation;
53

64
namespace TypedRequestContext.Propagation.Infrastructure;
75

src/TypedRequestContext.Propagation/Infrastructure/PropagationHeadersProvider.cs

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,18 @@ namespace TypedRequestContext.Propagation.Infrastructure;
1010
/// resolves the correct serializer via a lazily-built delegate map,
1111
/// and serializes on the spot — no eager pre-serialization, no second AsyncLocal.
1212
/// </summary>
13-
internal sealed class PropagationHeadersProvider : IPropagationHeadersProvider
13+
internal sealed class PropagationHeadersProvider(
14+
IRequestContextAccessor contextAccessor,
15+
IOptions<RequestContextOptions> options,
16+
IServiceScopeFactory scopeFactory,
17+
ICorrelationContext? correlationContext = null) : IPropagationHeadersProvider
1418
{
15-
private readonly IRequestContextAccessor _contextAccessor;
16-
private readonly ICorrelationContext? _correlationContext;
17-
private readonly IServiceScopeFactory _scopeFactory;
18-
private readonly Lazy<Dictionary<Type, SerializerRegistration>> _serializerRegistrations;
19-
private static readonly ConcurrentDictionary<Type, Func<object, ITypedRequestContext, IReadOnlyDictionary<string, string>>> _invokerCache = new();
20-
21-
public PropagationHeadersProvider(
22-
IRequestContextAccessor contextAccessor,
23-
IOptions<RequestContextOptions> options,
24-
IServiceScopeFactory scopeFactory,
25-
ICorrelationContext? correlationContext = null)
26-
{
27-
_contextAccessor = contextAccessor;
28-
_correlationContext = correlationContext;
29-
_scopeFactory = scopeFactory;
30-
31-
_serializerRegistrations = new Lazy<Dictionary<Type, SerializerRegistration>>(
19+
private readonly IRequestContextAccessor _contextAccessor = contextAccessor;
20+
private readonly ICorrelationContext? _correlationContext = correlationContext;
21+
private readonly IServiceScopeFactory _scopeFactory = scopeFactory;
22+
private readonly Lazy<Dictionary<Type, SerializerRegistration>> _serializerRegistrations = new(
3223
() => BuildSerializerMap(options.Value));
33-
}
24+
private static readonly ConcurrentDictionary<Type, Func<object, ITypedRequestContext, IReadOnlyDictionary<string, string>>> _invokerCache = new();
3425

3526
/// <inheritdoc />
3627
public IReadOnlyDictionary<string, string> GetCurrentHeaders()
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using TypedRequestContext.Infrastructure;
2+
3+
namespace TypedRequestContext.Propagation.Infrastructure;
4+
5+
/// <summary>
6+
/// Default <see cref="IRequestContextPropagator{T}"/> implementation that delegates
7+
/// deserialization to <see cref="IRequestContextDeserializer{T}"/> and manages
8+
/// accessor state via <see cref="RequestContextScopeFactory"/>.
9+
/// </summary>
10+
/// <typeparam name="T">The typed request context to propagate.</typeparam>
11+
/// <remarks>
12+
/// Initializes a new instance of <see cref="RequestContextPropagator{T}"/>.
13+
/// </remarks>
14+
public sealed class RequestContextPropagator<T>(
15+
IRequestContextDeserializer<T> deserializer,
16+
RequestContextScopeFactory scopeFactory) : IRequestContextPropagator<T>
17+
where T : class, ITypedRequestContext
18+
{
19+
/// <inheritdoc />
20+
public IDisposable Propagate(IReadOnlyDictionary<string, string> metadata)
21+
{
22+
var context = deserializer.Deserialize(metadata);
23+
return scopeFactory.Begin(context);
24+
}
25+
26+
/// <inheritdoc />
27+
public IDisposable Propagate(IReadOnlyDictionary<string, string> metadata, IServiceProvider serviceProvider)
28+
{
29+
var context = deserializer.Deserialize(metadata);
30+
return scopeFactory.Begin(context, serviceProvider);
31+
}
32+
}

src/TypedRequestContext.Propagation/TypedRequestContext.Propagation.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
77
<PackageId>TypedRequestContext.Propagation</PackageId>
8-
<Version>1.0.0</Version>
8+
<Version>1.1.0</Version>
99
<Authors>Otapiero</Authors>
1010
<Description>Optional propagation extension for TypedRequestContext with serializer/deserializer and header provider support.</Description>
1111
<PackageLicenseExpression>MIT</PackageLicenseExpression>

0 commit comments

Comments
 (0)