Skip to main content

shiny-mediator

Generate Shiny Mediator handlers, contracts, middleware, and scaffold projects for .NET applications

Aller à l'installation

Informations de source

Dépôt
shinyorg/skills
Dernière activité de la source
15 août 2026 à 17:13
Langue détectée de SKILL.md
anglais
Étoiles
4
Forks
0

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Explorateur de fichiers
5 fichiers

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
shiny-mediator
description
Generate Shiny Mediator handlers, contracts, middleware, and scaffold projects for .NET applications
auto_invoke
true
triggers
["mediator","handler","request handler","command handler","event handler","stream handler","middleware","IRequest","ICommand","IEvent","CQRS","Shiny.Mediator","server sent events","SSE","EventStream","WaitForSingleEvent","IAsyncEnumerable","IStreamRequest","IServerSentEventsStream","OpenAPI","HTTP client","MediatorHttp","swagger","contract-first","strongly typed HTTP","AI tools","AITool","Microsoft.Extensions.AI","AddGeneratedAITools","ShinyMediatorGenerateAITools","ISerializer","ISerializerService","[Truncated]"]
# Shiny Mediator Skill You are an expert in Shiny Mediator, a mediator pattern library for .NET applications. ## When to Use This Skill Invoke this skill when the user wants to: - Create request handlers, command handlers, event handlers, or stream handlers - Generate contracts (IRequest, ICommand, IEvent, IStreamRequest) - Add middleware (caching, resilience, validation, offline) - Scaffold ASP.NET, MAUI, or Blazor projects with Shiny Mediator - Configure Shiny Mediator in their application - Set up ASP.NET Server-Sent Events (SSE) endpoints with stream handlers - Use event subscriptions (WaitForSingleEvent, EventStream, Subscribe) - Generate strongly-typed HTTP clients from OpenAPI/Swagger specs - Create contract-first HTTP request handlers with [Get], [Post], etc. - Expose mediator contracts as AI tools via Microsoft.Extensions.AI - Migrate from MediatR to Shiny Mediator ## Library Overview **Documentation**: https://shinylib.net/mediator Shiny Mediator is AOT & trimming friendly, using source generators for automatic DI registration. ### Core Patterns | Pattern | Contract | Handler | Usage | |---------|----------|---------|-------| | Request | `IRequest<TResult>` | `IRequestHandler<TRequest, TResult>` | Queries returning data | | Command | `ICommand` | `ICommandHandler<TCommand>` | Void state changes | | Event | `IEvent` | `IEventHandler<TEvent>` | Pub/sub notifications | | Stream | `IStreamRequest<TResult>` | `IStreamRequestHandler<TRequest, TResult>` | IAsyncEnumerable | ### Handler Registration Always use registration attributes: ```csharp [MediatorSingleton] // Stateless handlers [MediatorScoped] // Handlers needing per-request services (DbContext) ``` **Critical: Partial Class Requirement** When using **any middleware attribute** (`[Cache]`, `[OfflineAvailable]`, `[Resilient]`, `[MainThread]`, `[TimerRefresh]`, `[Sample]`, `[Throttle]`), the handler class **must be declared as `partial`**: ```csharp [MediatorSingleton] public partial class MyHandler : IRequestHandler<MyRequest, MyResult> // partial required! { [Cache(AbsoluteExpirationSeconds = 60)] public Task<MyResult> Handle(...) { } } ``` This enables the source generator to create the `IHandlerAttributeMarker` implementation. Without `partial`, you'll get error `SHINY001`. ### Basic Setup **ASP.NET:** ```csharp builder.Services.AddShinyMediator(x => x .AddMediatorRegistry() ); app.MapGeneratedMediatorEndpoints(); ``` **MAUI:** ```csharp builder.AddShinyMediator(x => x .AddMediatorRegistry() .UseMaui() .AddMauiPersistentCache() .PreventEventExceptions() ); ``` **Blazor:** ```csharp builder.Services.AddShinyMediator(x => x .AddMediatorRegistry() .UseBlazor() .PreventEventExceptions() ); ``` **Logging & Configuration are optional (v6.8+)** - do NOT tell users they must call `AddLogging()` or register an `IConfiguration` to use the mediator. Every built-in service and middleware injects `ILogger` / `ILoggerFactory` / `IConfiguration` as an optional constructor argument defaulting to `null`. With no logging registered, log statements are skipped. With no `IConfiguration` registered, every configuration section (`Cache`, `Offline`, `ReplayStream`, `Resilience`, `TimerRefresh`, `PerformanceLogging`, `UserErrorNotifications`, `Http`) resolves to "not configured" and the middleware passes through - use the attribute equivalents (`[Cache]`, `[OfflineAvailable]`, `[TimerRefresh]`, `[Resilient]`) when there is no configuration source. Application-level handlers and middleware can still inject `ILogger` / `IConfiguration` as required dependencies - the app controls its own container. Follow the optional convention (`ILogger<T>? logger = null` last in the constructor, `logger?.LogDebug(...)`) when writing middleware or infrastructure meant to ship in a reusable library. ## JSON Serialization (v6.6+) Mediator uses `Shiny.ISerializer` from `Shiny.Extensions.Serialization`. The default chain is **AOT-strict** — no reflection fallback — so every contract that touches JSON (HTTP transport, storage cache, offline service, TickerQ scheduled commands, ASP.NET endpoints) must be in a registered `JsonSerializerContext`. Missing registrations throw `InvalidOperationException: No JsonTypeInfo registered for type 'T'` at runtime; the compiler won't catch it. **Default pattern when generating contracts.** Always emit a `[ShinyJsonContext]`-tagged partial `JsonSerializerContext` alongside the contracts and list every request, response, event, and scheduled-command type: ```csharp using System.Text.Json.Serialization; using Shiny; [ShinyJsonContext] [JsonSerializable(typeof(GetCustomerRequest))] [JsonSerializable(typeof(CustomerResponse))] [JsonSerializable(typeof(OrderPlacedEvent))] internal partial class AppJsonContext : JsonSerializerContext; ``` The `[ModuleInitializer]` emitted by the extensions generator registers this context with `Shiny.Json` before `Main` runs — no `services.AddJsonContext(...)` call needed. **Opt-in auto-generation (default off).** Setting `<ShinyMediatorGenerateJsonContext>true</ShinyMediatorGenerateJsonContext>` in the project file makes the mediator source generator emit a per-assembly `__ShinyMediatorContractsJsonResolver` covering every registered handler's request, response, command, event, and stream contract types (transitively, including their public property types), plus a `[ModuleInitializer]` that registers it. When enabled you don't need to hand-declare a `[ShinyJsonContext]` for in-assembly handler contracts. It is **opt-in** so there's always an escape hatch if the generated resolver misbehaves — when generating contracts, keep emitting an explicit `[ShinyJsonContext]` as the default unless the consumer has turned this property on. Cross-assembly contract types still need registration in their owning assembly. **Collections (`List<T>`, `T[]`, `IAsyncEnumerable<T>`, etc.) of a contract type.** Mark the element type with `[ShinyJsonInclude]`: ```csharp [ShinyJsonInclude] public partial class Customer { /* ... */ } ``` **OpenAPI HTTP clients with `GenerateJsonConverters="true"`:** the OpenAPI generator emits a custom `IJsonTypeInfoResolver` covering every generated model + contract + enum (including `Nullable<TEnum>` / `List<T>` / `T[]` shapes) plus a `[ModuleInitializer]`. Users don't need `[ShinyJsonContext]` for OpenAPI-generated types. **Attribute-driven HTTP clients (`[Get]` / `[Post]` / `[Body]`):** user-written types; require explicit `[ShinyJsonContext]` registration. **Migration from v5/early-v6:** - `ISerializerService` was removed — replace with `Shiny.ISerializer` (Shiny namespace, from `Shiny.Extensions.Serialization`). - `SysTextJsonSerializerService` was removed — DI registration is automatic via `AddShinyMediator`. - `ShinyMediatorBuilder.SetSerializer<T>()` was removed — replace by either registering a different `Shiny.ISerializer` in DI before `AddShinyMediator`, or calling `Shiny.Json.AddContext` / `Shiny.Json.AddResolver`. - The legacy `[SourceGenerateJsonConverter]` attribute still works for backward compatibility but new code should use `[ShinyJsonContext]` + `[JsonSerializable]` instead — it gives first-class AOT coverage of collection shapes. **Tests / development scenarios that need reflection fallback** (ad-hoc / anonymous types): ```csharp // In an [assembly: ...] or a [ModuleInitializer] Shiny.Json.AddResolver(new System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver()); ``` This is not AOT-safe — use only in test fixtures or non-production code. ## Code Generation Instructions When generating Shiny Mediator code: ### 1. Contracts Always use records for immutability: ```csharp public record GetUserRequest(int UserId) : IRequest<UserDto>; public record CreateUserCommand(string Name, string Email) : ICommand; public record UserCreatedEvent(int UserId, string Name) : IEvent; ``` ### 2. Handlers Include all three parameters in Handle method: ```csharp [MediatorScoped] public class GetUserRequestHandler : IRequestHandler<GetUserRequest, UserDto> { public Task<UserDto> Handle( GetUserRequest request, IMediatorContext context, CancellationToken cancellationToken) { // Implementation } } ``` ### 3. Middleware Attributes Apply to handler methods as needed: - `[Cache(AbsoluteExpirationSeconds = N)]` - Cacheable queries - `[OfflineAvailable]` - Offline storage for mobile - `[Resilient("policyName")]` - Retry/timeout policies - `[MainThread]` - MAUI main thread execution - `[TimerRefresh(milliseconds)]` - Auto-refresh streams - `[Sample(milliseconds)]` - Fixed-window sampling (last event in window executes) - `[Throttle(milliseconds)]` - True throttle (first event executes, cooldown discards rest) - `[Validate]` - Data annotation validation **When using ANY of these attributes, the handler class MUST be `partial`:** ```csharp [MediatorSingleton] public partial class CachedHandler : IRequestHandler<MyRequest, MyData> { [Cache(AbsoluteExpirationSeconds = 60)] [OfflineAvailable] public Task<MyData> Handle(...) { } } ``` ### 4. Middleware Ordering Use `[MiddlewareOrder(int)]` on custom middleware classes to control execution order. Lower values run first (outermost). Default is 0. ```csharp [MiddlewareOrder(-100)] // Runs before middleware with higher order values [MediatorSingleton] public class EarlyMiddleware<TRequest, TResult> : IRequestMiddleware<TRequest, TResult> where TRequest : IRequest<TResult> { ... } ``` ### 5. File Organization Place files in appropriate folders: - Contracts: `Contracts/{Name}Request.cs`, `Contracts/{Name}Command.cs` - Handlers: `Handlers/{Name}Handler.cs` - Middleware: `Middleware/{Name}Middleware.cs` ## Usage Examples **Request:** ```csharp var response = await mediator.Request(new GetUserRequest(1)); var user = response.Result; ``` **Command:** ```csharp await mediator.Send(new CreateUserCommand("John", "john@example.com")); ``` **Event:** ```csharp await mediator.Publish(new UserCreatedEvent(1, "John")); ``` **Chaining via Context:** ```csharp public async Task<UserDto> Handle(GetUserRequest request, IMediatorContext context, CancellationToken ct) { // Use context to chain operations (shares scope) await context.Publish(new UserAccessedEvent(request.UserId)); return new UserDto(...); } ``` ### Event Subscriptions & Streaming **WaitForSingleEvent** - Await a single event occurrence (with optional filter): ```csharp // Wait for a specific event (blocks until event fires or cancellation) var evt = await mediator.WaitForSingleEvent<OrderCompletedEvent>( filter: e => e.OrderId == orderId, cancellationToken: ct ); ``` **EventStream** - Continuous IAsyncEnumerable stream of events (uses Channels internally): ```csharp // Consume events as an async stream await foreach (var evt in mediator.EventStream<PriceUpdatedEvent>(cancellationToken: ct)) { Console.WriteLine($"New price: {evt.Price}"); } ``` **Subscribe** - Manual subscription returning IDisposable: ```csharp var sub = mediator.Subscribe<MyEvent>((ev, ctx, ct) => { Console.WriteLine($"Event received: {ev}"); return Task.CompletedTask; }); // Later: sub.Dispose() to unsubscribe ``` ### ASP.NET Server-Sent Events (SSE) Stream handlers decorated with `[MediatorHttpGet]` or `[MediatorHttpPost]` on an `IStreamRequestHandler` are **automatically generated as SSE endpoints** by the source generator via `MapGeneratedMediatorEndpoints()`. **Manual SSE endpoint with EventStream:** ```csharp app.MapGet("/events", ([FromServices] IMediator mediator) => TypedResults.ServerSentEvents(mediator.EventStream<MyEvent>()) ); ``` **Stream handler as auto-generated SSE endpoint:** ```csharp public record TickerStreamRequest : IStreamRequest<int>; [MediatorScoped] public class TickerStreamHandler : IStreamRequestHandler<TickerStreamRequest, int> { [MediatorHttpGet("/ticker")] public async IAsyncEnumerable<int> Handle( TickerStreamRequest request, IMediatorContext context, [EnumeratorCancellation] CancellationToken cancellationToken) { var i = 0; while (!cancellationToken.IsCancellationRequested) { yield return i++; await Task.Delay(1000, cancellationToken); } } } ``` **HTTP client-side SSE consumption:** Implement `IServerSentEventsStream` marker on the contract to indicate the server returns SSE format. The generated HTTP handler will use `ReadServerSentEvents<T>()` to parse the `data:` prefixed SSE lines. ```csharp public record TickerStreamRequest : IStreamRequest<int>, IServerSentEventsStream; ``` ## Contract-First HTTP Clients Shiny Mediator generates strongly-typed HTTP client handlers from contract classes decorated with HTTP method attributes. No manual `HttpClient` code needed. ### Manual HTTP Contracts Decorate request classes with `[Get]`, `[Post]`, `[Put]`, `[Delete]`, `[Patch]` and use `[Query]`, `[Header]`, `[Body]` on properties: ```csharp [Get("/api/orders/{OrderId}")] public class GetOrderRequest : IRequest<OrderDto> { public int OrderId { get; set; } // Route parameter (matches {OrderId}) [Query("status")] public string? Status { get; set; } // ?status=value [Header("Authorization")] public string? AuthToken { get; set; } // HTTP header } [Post("/api/orders")] public class CreateOrderRequest : IRequest<OrderDto> { [Body] public CreateOrderBody? Body { get; set; } // JSON request body } ``` The source generator creates handler classes inheriting `BaseHttpRequestHandler` that build routes, add query/header parameters, serialize bodies, and call `IHttpClientFactory`.
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub