YuckQi.Application.Core
10.1.1
See the version list below for details.
dotnet add package YuckQi.Application.Core --version 10.1.1
NuGet\Install-Package YuckQi.Application.Core -Version 10.1.1
<PackageReference Include="YuckQi.Application.Core" Version="10.1.1" />
<PackageVersion Include="YuckQi.Application.Core" Version="10.1.1" />
<PackageReference Include="YuckQi.Application.Core" />
paket add YuckQi.Application.Core --version 10.1.1
#r "nuget: YuckQi.Application.Core, 10.1.1"
#:package YuckQi.Application.Core@10.1.1
#addin nuget:?package=YuckQi.Application.Core&version=10.1.1
#tool nuget:?package=YuckQi.Application.Core&version=10.1.1
YuckQi.Application.Core
A .NET library for bootstrapping a domain application project. Provides Mediator pipeline behaviors for logging, validation (FluentValidation), and caching with dependency-graph invalidation.
Key Types
Abstractions
IHasCacheInvalidationKeys– aspect marker for Mediator response types that trigger cache invalidation; exposesCacheKeys(IReadOnlySet<CacheKey>) as the seeds to invalidate after the handler runsIHasCacheKey– aspect marker for cacheable Mediator messages (IMessage) with aCacheKeyIHasValidationResults– aspect marker for Mediator response types that carry validation results; exposesValidationResults(IReadOnlyCollection<Result>) for validation behaviorICacheDependencyGraph– expands a set of cache keys by walking a resource dependency graph (transitive); lives inYuckQi.Application.Core.Behaviors.Caching.DependencyGraph.Abstract.Interfaces
Behaviors
Pipeline behaviors are organized by purpose in subfolders and namespaces:
Caching (YuckQi.Application.Core.Behaviors.Caching)
CacheKey– value object wrapping a cache key string, with implicit conversions to/fromString(cast toStringwhen callingIMemoryCache, which takesObjectkeys)DistributedCacheInvalidationBehavior<TRequest, TResponse>– Removes keys fromIDistributedCacheafter the handler runs whenTResponseimplementsIHasCacheInvalidationKeys; expands seeds through requiredICacheDependencyGraphDistributedCachingBehavior<TRequest, TResponse>– UsesIDistributedCacheto cache responses for cacheable requests; configuration viaDistributedCachingBehaviorOptionsrecord (same file)MemoryCacheInvalidationBehavior<TRequest, TResponse>– Removes keys fromIMemoryCacheafter the handler runs whenTResponseimplementsIHasCacheInvalidationKeys; expands seeds through requiredICacheDependencyGraphMemoryCachingBehavior<TRequest, TResponse>– UsesIMemoryCacheto cache responses for cacheable requests; configuration viaMemoryCachingBehaviorOptionsrecord (same file)
Caching dependency graph (YuckQi.Application.Core.Behaviors.Caching.DependencyGraph)
CacheKeyParts/CacheKeyContext– structured cache key parse result and factory callback contextCacheDependencyGraph– defaultICacheDependencyGraphimplementation; useCreate(...)orEmpty, then register asICacheDependencyGraph
Caching dependency graph builders (YuckQi.Application.Core.Behaviors.Caching.DependencyGraph.Builders)
CacheDependencyGraphBuilder/CacheResourceDependencyBuilder– fluent configuration of which resources invalidate which dependents
Caching dependency graph factories (YuckQi.Application.Core.Behaviors.Caching.DependencyGraph.Factories)
CacheKeyFactory– creates and parses structured cache keys (resource,resource:identifier,resource:identifier;name=value)
Logging (YuckQi.Application.Core.Behaviors.Logging)
LoggingBehavior<TRequest, TResponse>– Logs message handling start and completion
Validation (YuckQi.Application.Core.Behaviors.Validation)
ValidationResponse/ValidationResponse<T>– recommended MediatorTResponsefor use withValidationBehavior. ImplementsIHasValidationResults. On validation failure the behavior returnsnew ValidationResponse/new ValidationResponse<T> { ValidationResults = … }withValueleft null; on success handlers return the envelope withValueset. Keep payload/Tfree of validation concerns (and free of thenew()constraint), sorequiredmembers on payloads remain valid.ValidationBehavior<TRequest, TResponse>– Runs FluentValidation validators and short-circuits on error whenTResponseimplementsIHasValidationResults(preferValidationResponse/ValidationResponse<T>asTResponse)
Caching guide
Caching is opt-in at the Mediator message/response layer. Reads declare a cache key; writes declare seed keys for what changed. A dependency graph expands those seeds so related cached queries are invalidated without listing every dependent key in every handler.
Mental model
| Role | Responsibility |
|---|---|
IHasCacheKey (on the request) |
“This query’s response may be cached under this key.” |
IHasCacheInvalidationKeys (on the response) |
“This command changed these resources” — the seeds. |
ICacheDependencyGraph |
“Given these seeds, also invalidate these related keys.” |
| Caching behaviors | Get/set on cache hit/miss (reads). |
| Invalidation behaviors | After the handler, expand seeds and remove keys (writes). |
The graph does not discover what changed. Handlers (via the response) still supply seeds. The graph only expands them.
Cache key format
Use CacheKeyFactory so read keys and invalidation seeds share one vocabulary.
| Form | Example | Meaning |
|---|---|---|
| Resource only | order-list |
Global / aggregate key (no entity id) |
| Resource + identifier | order:42 |
Entity-scoped key |
| Resource + identifier + parameters | order:42;customer=7 |
Entity key plus context used when expanding dependents |
using YuckQi.Application.Core.Behaviors.Caching.DependencyGraph.Factories;
CacheKeyFactory.Create("order-list");
CacheKeyFactory.Create("order", orderId);
CacheKeyFactory.Create("order", orderId, ("customer", customerId));
CacheKey is a value object over that string. It converts implicitly to/from String. When calling IMemoryCache (keys are Object), cast explicitly so the store receives a String rather than a boxed CacheKey:
cache.Remove((String) key);
// or
var cacheKey = (String) key;
IDistributedCache takes String, so the implicit conversion is enough at the call site.
Configuring the dependency graph
Register one ICacheDependencyGraph for the host. Edges are authored by resource name (the segment before :), not by full key strings.
using YuckQi.Application.Core.Behaviors.Caching.DependencyGraph;
using YuckQi.Application.Core.Behaviors.Caching.DependencyGraph.Abstract.Interfaces;
services.AddSingleton<ICacheDependencyGraph>(CacheDependencyGraph.Create(graph => graph
.When("order", order => order
.Invalidates("order-detail") // same identifier → order-detail:42
.InvalidatesGlobal("order-list") // always → order-list
.InvalidatesFromParameter("customer-summary", "customer")) // from ;customer=… → customer-summary:7
.When("customer-summary", summary => summary
.InvalidatesGlobal("dashboard"))));
If you are not using graph edges yet, still register a graph — invalidation behaviors require it:
services.AddSingleton(CacheDependencyGraph.Empty);
Builder methods on a resource
For a seed whose resource is order (e.g. order:42;customer=7):
| Method | Resulting key(s) |
|---|---|
Invalidates("order-detail") |
Same identifier as the seed → order-detail:42. If the seed has no identifier, produces a resource-only key. |
InvalidatesGlobal("order-list") |
Always order-list (no identifier). |
InvalidatesFromParameter("customer-summary", "customer") |
Uses parameter customer from the seed → customer-summary:7. Skipped if the parameter is missing. |
Invalidates(ctx => …) |
Custom single key (CacheKey?; return null to skip) or many keys (IEnumerable<CacheKey>). CacheKeyContext exposes Resource, Identifier, Parameter(name), Key(...), and Global(...). |
Expansion is transitive and cycle-safe: if order invalidates customer-summary, and customer-summary invalidates dashboard, a seed of order:7 yields order:7, customer-summary:7, and dashboard.
Seeds themselves are always included in the expanded set.
Wiring pipeline behaviors
The library does not register Mediator behaviors for you. In the host:
- Register
IMemoryCacheand/orIDistributedCache. - Register
ICacheDependencyGraph(configured orEmpty). - Register caching options if you use the caching behaviors.
- Register the open-generic pipeline behaviors you want (Mediator DI).
Example (memory cache + invalidation):
services.AddMemoryCache();
services.AddSingleton(Options.Create(new MemoryCachingBehaviorOptions(TimeSpan.FromMinutes(5))));
services.AddSingleton<ICacheDependencyGraph>(CacheDependencyGraph.Create(/* … */));
// Register with your Mediator pipeline registration of choice, e.g.:
// services.AddSingleton(typeof(IPipelineBehavior<,>), typeof(MemoryCachingBehavior<,>));
// services.AddSingleton(typeof(IPipelineBehavior<,>), typeof(MemoryCacheInvalidationBehavior<,>));
Pipeline order is host-defined. Typical expectations:
- Caching should run so that on a hit it can short-circuit before the handler (and before invalidation for that same request, which would not apply to a pure read anyway).
- Invalidation must run after the handler so seeds come from the completed response.
- Pair memory or distributed behaviors with the matching store; do not assume one store’s keys are visible to the other.
What happens at runtime
Cached read (IHasCacheKey)
MemoryCachingBehavior/DistributedCachingBehaviorreadsrequest.CacheKey.- Hit — returns the cached response; the handler is not invoked.
- Miss — invokes
next, then stores the response (optional absolute expiration from options). - Cache get/set failures are fail-soft (logged; the request still proceeds).
Write / invalidating response (IHasCacheInvalidationKeys)
MemoryCacheInvalidationBehavior/DistributedCacheInvalidationBehavioralways invokesnextfirst.- Reads
response.CacheKeys(the seeds). - Calls
dependencyGraph.GetExpandedCacheKeys(seeds). - Removes every expanded key from the cache.
- Remove failures are fail-soft (logged; other keys still attempted).
Handlers never call the cache APIs directly; behaviors own get/set/remove.
End-to-end example
Graph (startup):
services.AddSingleton<ICacheDependencyGraph>(CacheDependencyGraph.Create(graph => graph
.When("order", order => order
.InvalidatesGlobal("order-list")
.InvalidatesFromParameter("customer-summary", "customer"))));
Cached query — same key vocabulary as invalidation:
public sealed class GetOrderListQuery : IRequest<IReadOnlyList<OrderDto>>, IHasCacheKey
{
public CacheKey CacheKey => CacheKeyFactory.Create("order-list");
}
public sealed class GetCustomerSummaryQuery : IRequest<CustomerSummaryDto>, IHasCacheKey
{
public required Int32 CustomerId { get; init; }
public CacheKey CacheKey => CacheKeyFactory.Create("customer-summary", CustomerId);
}
Command response — seed what changed (include parameters the graph needs):
public sealed record ReviseOrderResponse(OrderDto? Value, IReadOnlySet<CacheKey> CacheKeys) : IHasCacheInvalidationKeys;
// In the handler, after a successful revise of order 42 for customer 7:
return new ReviseOrderResponse(
orderDto,
new HashSet<CacheKey>
{
CacheKeyFactory.Create("order", 42, ("customer", 7))
});
Resulting invalidation for that seed:
- Seed:
order:42;customer=7 - Graph expands to:
order:42;customer=7,order-list,customer-summary:7 - Invalidation behavior removes those three keys from the configured cache
Subsequent GetOrderListQuery and GetCustomerSummaryQuery for customer 7 miss cache and re-run their handlers.
Practical tips
- Prefer
CacheKeyFactoryeverywhere keys are authored so separators and parameter encoding stay consistent. - Put on the seed every parameter a dependent edge might need (
InvalidatesFromParameter/ custom factories); missing parameters simply skip that edge. - Over-invalidation (clearing a list when one row changes) is intentional for correctness; tune edges if that is too aggressive.
CacheDependencyGraph.Emptystill invalidates exact seeds — useful before you add edges.- Data-layer handlers stay cache-unaware; keep caching at the application/Mediator boundary.
Dependencies
- FluentValidation
- Mediator.Abstractions
- Microsoft.Extensions.Caching.Abstractions
- Microsoft.Extensions.Caching.Memory
- Microsoft.Extensions.Logging.Abstractions
- YuckQi.Domain.Validation (project reference)
Installation
dotnet add package YuckQi.Application.Core
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- FluentValidation (>= 12.1.1)
- Mediator.Abstractions (>= 3.0.2)
- Microsoft.Extensions.Caching.Abstractions (>= 10.0.11)
- Microsoft.Extensions.Caching.Memory (>= 10.0.11)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.11)
- Microsoft.Extensions.Options (>= 10.0.11)
- YuckQi.Domain.Validation (>= 10.1.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.3.0 | 53 | 9/17/2026 |
| 10.2.0 | 41 | 9/16/2026 |
| 10.1.1 | 106 | 9/11/2026 |
| 10.1.0 | 155 | 8/26/2026 |
| 10.0.2 | 134 | 7/24/2026 |
| 10.0.1 | 117 | 7/23/2026 |
| 10.0.0 | 120 | 7/23/2026 |
| 8.7.0 | 124 | 7/22/2026 |
| 8.6.0 | 132 | 7/1/2026 |
| 8.5.1 | 162 | 3/18/2026 |
| 8.5.0 | 149 | 3/17/2026 |
| 8.4.0 | 135 | 3/10/2026 |
| 8.3.2 | 135 | 3/9/2026 |
| 8.3.1 | 140 | 3/9/2026 |
| 8.3.0 | 135 | 3/9/2026 |
| 6.4.0 | 289 | 9/18/2023 |
| 6.2.0 | 375 | 3/23/2023 |
| 6.1.0 | 569 | 8/16/2022 |
| 6.0.4 | 610 | 6/7/2022 |
| 1.0.1 | 612 | 6/7/2022 |