feat: add DI integrations for every PatternKit example#204
Conversation
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
Test Results 1 files 1 suites 1m 33s ⏱️ Results for commit 75f5dbc. ♻️ This comment has been updated with latest results. |
🔍 PR Validation ResultsVersion: `` ✅ Validation Steps
📊 ArtifactsDry-run artifacts have been uploaded and will be available for 7 days. This comment was automatically generated by the PR validation workflow. |
There was a problem hiding this comment.
Pull request overview
This PR adds first-class Microsoft.Extensions.DependencyInjection (DI) integrations for the PatternKit examples package, making every production-readiness catalog entry resolvable via IServiceCollection while also documenting the new DI surface and adding automated coverage to keep the catalog and DI registrations in sync.
Changes:
- Introduces
PatternKit.Examples.DependencyInjectionwithAddPatternKitExamples()and per-exampleAdd...Example()registration helpers plus auditablePatternKitExampleServiceDescriptormetadata. - Adds TinyBDD/xUnit coverage that validates each catalog entry has a corresponding DI descriptor and that every registered example service resolves.
- Updates the production-ready integrations documentation to show aggregate and per-example DI usage.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| test/PatternKit.Examples.Tests/DependencyInjection/PatternKitExampleDependencyInjectionTests.cs | Adds DI smoke/contract tests ensuring every catalog example has a registered, resolvable DI entry point. |
| src/PatternKit.Examples/DependencyInjection/PatternKitExampleServiceCollectionExtensions.cs | Adds the DI extension surface (AddPatternKitExamples + per-example extensions), typed integration entry points, and descriptor metadata. |
| docs/examples/production-ready-integrations.md | Documents how to register all examples or selected examples via IServiceCollection. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| services.AddSingleton<EnterpriseFeatureSlicesExample>(sp => new( | ||
| sp.GetRequiredService<EnterpriseCheckout>(), | ||
| () => EnterpriseFeatureSlicesDemo.CreateRetailRequest())); | ||
| return services.RegisterExample<EnterpriseFeatureSlicesExample>("Enterprise Feature Slices with .NET DI", ExampleIntegrationSurface.DependencyInjection); |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #204 +/- ##
==========================================
+ Coverage 91.78% 96.59% +4.80%
==========================================
Files 253 254 +1
Lines 23251 23489 +238
Branches 3265 3268 +3
==========================================
+ Hits 21341 22689 +1348
+ Misses 847 800 -47
+ Partials 1063 0 -1063
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (4)
test/PatternKit.Examples.Tests/DependencyInjection/PatternKitExampleDependencyInjectionTests.cs:101
LineItemandPaymentKindare ambiguous here because bothPatternKit.Examples.ChainandPatternKit.Examples.ObserverDemodefine these types and both namespaces are imported. This will fail to compile; use type aliases (e.g.,using ObserverLineItem = ...) or fully-qualify the ObserverDemo types for the reactive transaction calls.
viewModel.ViewModel.FirstName.Value = "Ada";
viewModel.ViewModel.LastName.Value = "Lovelace";
transaction.Transaction.AddItem(new LineItem("SKU-1", 1, 10m));
transaction.Transaction.SetPayment(PaymentKind.CreditCard);
test/PatternKit.Examples.Tests/DependencyInjection/PatternKitExampleDependencyInjectionTests.cs:130
- This creates a new
CheckoutServicesinstance instead of using the one registered inAddResilientCheckoutMailboxesExample(), so the test doesn’t actually validate that the DI registration forCheckoutServicesworks. Prefer resolvingCheckoutServicesfrom the provider and passing that intocheckout.Run(...).
&& template.Processor.Execute("one two") == 2
&& asyncResult == "PAYLOAD:7"
&& routing.Run().AggregatedTotal == 100m
&& envelope.Run().Attempt == 1
&& checkout.Run(CreateCheckoutRequest(), new PatternKit.Examples.Messaging.CheckoutServices()).Succeeded;
src/PatternKit.Examples/DependencyInjection/PatternKitExampleServiceCollectionExtensions.cs:166
AuthLoggingChainExampleis registered as a singleton but contains a mutableList<string>that is appended to on every chain execution. In a real DI host this is not thread-safe and will also grow unbounded over time; consider registering this example as transient/scoped, or make the log sink per-execution (or use a thread-safe bounded collection).
services.AddSingleton<AuthLoggingChainExample>(_ =>
{
var log = new List<string>();
var chain = ActionChain<HttpRequest>.Create()
.When(static (in r) => r.Headers.ContainsKey("X-Request-Id"))
.ThenContinue(r => log.Add($"reqid={r.Headers["X-Request-Id"]}"))
.When(static (in r) => r.Path.StartsWith("/admin", StringComparison.Ordinal) && !r.Headers.ContainsKey("Authorization"))
.ThenStop(_ => log.Add("deny: missing auth"))
.Finally((in r, next) =>
{
log.Add($"{r.Method} {r.Path}");
next(r);
})
.Build();
return new AuthLoggingChainExample(chain, log);
});
src/PatternKit.Examples/DependencyInjection/PatternKitExampleServiceCollectionExtensions.cs:174
- Unlike the other example integrations, this open-generic registration uses
AddSingletoninstead ofTryAdd..., so it will override any existingICoercer<T>registrations an importing app may already have. Consider usingTryAddSingleton(typeof(ICoercer<>), typeof(CoercerService<>))to keep the examples package additive/friendly to host applications.
public static IServiceCollection AddStrategyBasedDataCoercionExample(this IServiceCollection services)
{
services.AddSingleton(typeof(ICoercer<>), typeof(CoercerService<>));
services.AddSingleton<CoercionExample>(sp => new(
sp.GetRequiredService<ICoercer<int>>(),
| using PatternKit.Examples.Strategies.Composed; | ||
| using Showcase = PatternKit.Examples.PatternShowcase.PatternShowcase; | ||
| using TinyBDD; |
| using DocumentValidationResult = PatternKit.Examples.Generators.Visitors.DocumentProcessingDemo.ValidationResult; | ||
| using EnterpriseCheckout = PatternKit.Examples.EnterpriseFeatureSlices.EnterpriseFeatureSlicesDemo.IEnterpriseCheckout; | ||
| using EnterpriseCheckoutRequest = PatternKit.Examples.EnterpriseFeatureSlices.EnterpriseFeatureSlicesDemo.CheckoutRequest; | ||
| using EnterpriseCheckoutResult = PatternKit.Examples.EnterpriseFeatureSlices.EnterpriseFeatureSlicesDemo.CheckoutResult; | ||
| using PosPaymentKind = PatternKit.Examples.ObserverDemo.PaymentKind; |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (2)
src/PatternKit.Examples/DependencyInjection/PatternKitExampleServiceCollectionExtensions.cs:159
AuthLoggingChainExampleis registered as a singleton and exposes a mutableList<string>that is appended to duringChain.Execute(...). If this service is used concurrently (e.g., multiple requests),List<T>mutations are not thread-safe and logs from different executions can interleave/corrupt. Consider using a thread-safe collection (e.g.,ConcurrentQueue<string>), adding synchronization, or making the example service transient/scoped so per-consumer execution state isn’t shared globally.
services.AddSingleton<AuthLoggingChainExample>(_ =>
{
var log = new List<string>();
var chain = ActionChain<HttpRequest>.Create()
.When(static (in r) => r.Headers.ContainsKey("X-Request-Id"))
.ThenContinue(r => log.Add($"reqid={r.Headers["X-Request-Id"]}"))
.When(static (in r) => r.Path.StartsWith("/admin", StringComparison.Ordinal) && !r.Headers.ContainsKey("Authorization"))
.ThenStop(_ => log.Add("deny: missing auth"))
.Finally((in r, next) =>
src/PatternKit.Examples/DependencyInjection/PatternKitExampleServiceCollectionExtensions.cs:427
RegisterExample<T>always usesAddSingleton(new PatternKitExampleServiceDescriptor(...)). If a consumer callsAddPatternKitExamples()(or any per-exampleAdd...Example()) multiple times, this will silently add duplicatePatternKitExampleServiceDescriptorentries, making the "auditable" metadata harder to reason about. Consider usingTryAddEnumerable(ServiceDescriptor.Singleton(...))(or otherwise de-duplicating byExampleName) so registrations are idempotent.
private static IServiceCollection RegisterExample<T>(
this IServiceCollection services,
string name,
ExampleIntegrationSurface integration)
where T : class
{
services.AddSingleton(new PatternKitExampleServiceDescriptor(name, typeof(T), integration));
return services;
| using DocumentValidationResult = PatternKit.Examples.Generators.Visitors.DocumentProcessingDemo.ValidationResult; | ||
| using EnterpriseCheckout = PatternKit.Examples.EnterpriseFeatureSlices.EnterpriseFeatureSlicesDemo.IEnterpriseCheckout; | ||
| using EnterpriseCheckoutRequest = PatternKit.Examples.EnterpriseFeatureSlices.EnterpriseFeatureSlicesDemo.CheckoutRequest; | ||
| using EnterpriseCheckoutResult = PatternKit.Examples.EnterpriseFeatureSlices.EnterpriseFeatureSlicesDemo.CheckoutResult; | ||
| using PosPaymentKind = PatternKit.Examples.ObserverDemo.PaymentKind; |
| Each example also has its own focused extension, so sample applications can import only the slice they need: | ||
|
|
||
| ```csharp | ||
| services | ||
| .AddPricingCalculatorExample() | ||
| .AddPaymentProcessorDecoratorExample() | ||
| .AddMessagingBackplaneFacadeExample(); | ||
| ``` |
Code Coverage |
Summary
Validation