Skip to content

v0.7.0

Choose a tag to compare

@charleypeng charleypeng released this 18 Jul 07:46
· 17 commits to main since this release

feat

  • Plugin system: ISharkPlugin interface with three lifecycle hooks — ConfigureServices (AddShark), ConfigurePipeline (UseShark), ConfigureOpenApi. Three discovery paths: NuGet/assembly auto-scanning, hot-plug folder scanning (opt.ConfigurePlugins(p => { p.ScanOnStartup = true; })), and manual opt.RegisterPlugin(). Per-folder AssemblyLoadContext isolation. opt.DisablePlugin("name") for opt-out. AOT-safe for NuGet/manual paths; folder scanning is JIT-only.

fix

  • P1.1 — Dispose AuditLogBuffer after FlushRemaining() during shutdown — CTS leak eliminated
  • P1.2 — Dispose CronScheduler renewCts after lock renewal task completes
  • P1.3 — Unify ResponseSizeExceededException — single shared type instead of two duplicate inner-class definitions in ETag and Idempotency middleware
  • P1.4 — Move exception handler to end of pipeline (just before MapEndpoints) — catches exceptions from ALL middleware, not just auth and beyond
  • P1.5 — Guard AutoCrudExtension dynamic assembly loading behind AotMode check — prevents reflection-based plugin discovery in AOT

refactor

  • P2.1 — Extract ISagaExecutor interface; register via TryAddSingleton<ISagaExecutor, SagaExecutor>; add SagaExecutorFactory on SharkOption
  • P2.2 — Expose CronLockTtl on ICronScheduler interface (was only on concrete CronScheduler)
  • P2.3 — Normalize ConfigureJwt to ConfigureJwt(Action<JwtOptions>) pattern (consistent with every other ConfigureXxx method). Old ConfigureJwt(string, string[], Action<JwtBearerOptions>?) marked [Obsolete].
  • P2.4 — Normalize ConfigureAuthorization from property to method ConfigureAuthorization(Action<AuthorizationOptions>) (consistent with ConfigureRateLimiter, ConfigureOutputCache, etc.)
  • P2.5 — ICronJobStore.RenewJobLockAsync has default implementation => Task.CompletedTask (matching ISagaStore.RenewLockAsync pattern)
  • P2.6 — Seal SqlSugarOptions, Shark, AssemblyContext, UnifiedResult<T> — public classes not designed for inheritance
  • P2.7 — Remove dead code: Utils.SetupModules() (never called), obsolete .SharkRequestTimeout(int, string?) DSL overload

feat

  • P3.1 — Health checks enabled by default (EnableHealthChecks = true) — every deployed app gets /healthz out of the box
  • P3.2 — Cron job concurrency default changed to SkipIfRunning (was AllowConcurrent) — safer default, prevents overlapping runs
  • P3.3 — Startup warning when UnifiedResultFactory is set without WrapSchemaFactory — prevents OpenAPI schema mismatch
  • P3.4 — IUnifiedResult XML docs cross-reference UnifiedResult<T> and IUnifiedResultFactory with usage guidance and WrapSchemaFactory tip

feat

  • FEAT-01 — Security headers middleware (ConfigureSecurityHeaders()) — opt-in support for X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Content-Security-Policy, Permissions-Policy, Strict-Transport-Security, Cross-Origin-Resource-Policy, Cross-Origin-Opener-Policy, Cross-Origin-Embedder-Policy
  • FEAT-02 — Framework metrics via System.Diagnostics.Metrics — ISharkMetrics interface with MetricsFactory on SharkOption (consistent with existing IdempotencyStoreFactory/RateLimitStoreFactory patterns); default SharkMetrics uses Meter("Sharkable") with counters for requests, rate-limit rejections, idempotency hit/miss/conflict, auth failures, audit drops, cron runs/failures, saga completed/compensated. Configurable via ConfigureMetrics(). Zero dependencies, AOT-compatible.
  • FEAT-03 — IAuditSink abstraction — pluggable audit sink for shipping entries to Seq/Elastic/Kafka. AuditSinkFactory on SharkOption, default LoggingAuditSink preserves the current structured-logging behavior. AuditLogEntry record made public.
  • FEAT-05 — Per-endpoint distributed rate-limit policies — [SharkRateLimit(limit, windowSeconds)] attribute on ISharkEndpoint classes, .SharkRateLimit(limit, windowSeconds) DSL, SharkRateLimitMetadata read by SharkRateLimiterMiddleware overriding global defaults.
  • FEAT-06 — Per-endpoint idempotency via [SharkIdempotent(ttlSeconds)] attribute — opt-in idempotency per endpoint when global is disabled. [SharkNoIdempotency] attribute unscoped (already supported per-class). Middleware now checks SharkIdempotentMetadata.
  • FEAT-07 — Response cache profile via [SharkCacheProfile(durationSeconds, varyByHeader, privateOnly)] attribute — emits Cache-Control and optional Vary headers per endpoint group via SharkCacheProfileFilter. Composes with ETag and Output Cache.
  • FEAT-08 — Request-timeout DSL — opt.RequestTimeoutsConfigure + .SharkRequestTimeout(policyName) + .SharkRequestTimeout(ms) DSL. Thin wrapper over ASP.NET Core AddRequestTimeouts. DefaultRequestTimeoutPolicy for global fallback.
  • FEAT-09 — Unified validation error shape — ValidationErrorMode.Messages (default, joins with "; ") and ValidationErrorMode.ProblemDetails (RFC 7807 with errors object mapping field → messages). Configurable via SharkOption.ValidationErrorMode.
  • FEAT-10 — Parallel warmup services — ConfigureWarmup<T>() now callable multiple times; all registered IWarmupService instances run in parallel with individual timeouts during startup. Aggregate failures propagated via AggregateException.
  • FEAT-11 — Sharkable.Testing NuGet package — SharkTestFactory<TEntryPoint> base WebApplicationFactory, FakeIdempotencyStore, FakeRateLimitStore, UnifiedResultAssertions for fluent test assertions. Separate package.
  • FEAT-12 — SharkOption.GroupConvention (per-group) + EndpointConvention (per-endpoint) — single place for auth policies, filters, ProducesResponseType conventions.
  • FEAT-13 — OpenAPI ergonomics — AddOpenApiOperationTransformer() / AddOpenApiSchemaTransformer() methods on SharkOption (no need to touch OpenApiOptions directly). OpenApiExampleFactory hook for example generation.
  • FEAT-15 — IApiKeyValidator interface with ApiKeyValidationResult — carries claims/roles + optional per-key RateLimitMultiplier. DefaultApiKeyValidator wraps the existing static-array behavior. ApiKeyFilter now accepts the custom validator via DI.
  • FEAT-14 — Source generator for discovery (tracked as separate RFC; not yet implemented)
  • Add ISingleton, IScoped, ITransient marker interfaces — classes implementing these are auto-registered in DI (same as [SingletonService] etc.)

perf

  • PERF-01 — DefaultUnifiedResultFactory.Instance singleton; UnifiedResultFactoryHelper.ResolveFactory() caches configured factory at 5 hot-path call sites (P1)
  • PERF-02 — Source-generated JSON context defaults to WriteIndented=false for compact production output (P1)
  • PERF-04 — Audit middleware caches _redactQueryParams HashSet in ctor instead of allocating per request (P2)
  • PERF-05 — Audit middleware checks _logger.IsEnabled() before building log strings, avoiding allocations when the category is filtered out (P2)
  • PERF-06 — Shared ApiKeyValidator service caches stored-key hashes and invalidates via IOptionsMonitor.OnChange, replacing O(keys) SHA-256 per request (P2)
  • PERF-07 — Validation filter caches ConcurrentDictionary<Type, Func<IServiceProvider, IValidator?>> per argument type, avoiding MakeGenericType + DI lookup per request (P2)
  • PERF-08 — ETag middleware uses Convert.ToHexString() instead of per-byte StringBuilder + ToString("x2") — one allocation instead of 33 (P3)
  • PERF-09 — Idempotency middleware uses HashSet<string> for method check and TryGetBuffer() to avoid double body copy (P3)
  • PERF-10 — Profiler TrackMemory option (default false) gates per-request GC.GetTotalMemory(false) calls (P3)
  • PERF-11 — HttpResponseExtension.WriteJsonAsync caches static JsonSerializerOptions instead of allocating per call (P3)

fix (memory & lifetime)

  • MEM-01 — MemoryIdempotencyStore now owns a private MemoryCache instead of hijacking the app-wide IMemoryCache registration (P1)
  • MEM-02 — AdaptiveLimitMonitor registered as singleton in DI with autoStart:true; DI handles disposal of the timer + process handle (P2)
  • MEM-03 — AuditLogBuffer consumer survives flush failures with try/catch + Interlocked-tracked dropped-entry counter (P2)
  • MEM-04 — MemoryRateLimitStore and MemoryIdempotencyStore implement IDisposable so MemoryCache expiration-scan timers are properly disposed (P2)
  • MEM-07 — ETag CountingResponseBody disposes its internal MemoryStream spool; using var counting in middleware (P3)

security

  • SEC-01 — HealthCheckDetailLevel enum (StatusOnly/Description/Full); /healthz defaults to StatusOnly in non-Development, never leaking Exception.Message (P1)
  • SEC-02 — ExceptionHandlerOptions.IncludeExceptionMessage (default false); production error responses return generic "An error occurred." (P2)
  • SEC-03 — Startup warning when OpenAPI/Scalar is enabled outside Development (P2)
  • SEC-04 — Admin endpoints (/_sharkable/jobs, profiler gate) unified on shared ApiKeyValidator service with hot-reload support (P3)
  • SEC-05 — Configuration validator warns when rate limiting is enabled without proxy ForwardedHeaders configuration (P3)
  • SEC-06 — JwtHealthCheck caches result for 30 seconds to avoid hammering OIDC discovery endpoint on k8s probes (P3)

fix

  • UnifiedResultExtension and UnifiedResult static methods now respect UnifiedResultFactory — custom factory implementations are used for all .AsNotFound(), UnifiedResult.Ok(), etc.
  • BUG-01 — Cron shutdown cancellation token wired into ExecuteJobAsync so in-flight jobs are cancelled on app.StopAsync() (P1)
  • BUG-02 — Exception handler no longer logs client disconnects as errors and avoids writing to already-started responses (P1)
  • BUG-03 — Saga compensation now runs with its own CTS (CompensationTimeout, default 60s) — never cancelled by the execution token (P1)
  • BUG-04 — Cron job timeouts are recorded as "Job timed out" with zero retries; retry delay now respects shutdown via linked token (P2)
  • BUG-05 — Graceful shutdown middleware honors configured ShutdownStatusCode instead of hard-coded 503 (P2)
  • BUG-06 — Auto-wrap docs fixed; UseSharkOptions.EnableAutoWrap is now nullable tri-state overriding SharkOption.EnableAutoWrap; [SharkDontWrap] evaluated per endpoint class instead of per merged group (P2)
  • BUG-07 — MemoryRateLimitStore now supports MaxEntries = -1 to disable the cap (uncapped MemoryCache) (P2)
  • BUG-08 — Route formatting uses ToLowerInvariant() / char.ToLowerInvariant() instead of culture-sensitive ToLower() (P2)
  • BUG-09 — Rate-limit and idempotency Retry-After headers formatted with CultureInfo.InvariantCulture (P2)
  • BUG-10 — Add [SharkNoIdempotency] attribute; idempotency middleware skips response buffering for marked streaming/SSE endpoints (P2)
  • BUG-11 — Cron job state mutations (IsRunning, LastRun, LastError, etc.) now guarded with _entries lock (P3)
  • BUG-12 — Legacy attribute mapper copies formatted values to locals instead of mutating runtime-cached attribute instances (P3)
    Full Changelog: v0.6.1...v0.7.0