@ngxtm/devkit 3.4.0 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/rules/README.md +141 -0
- package/rules/dart/best-practices/SKILL.md +23 -0
- package/rules/dart/language/SKILL.md +52 -0
- package/rules/dart/tooling/SKILL.md +43 -0
- package/rules/dotnet/aspnet-core/SKILL.md +92 -0
- package/rules/dotnet/aspnet-core/references/REFERENCE.md +335 -0
- package/rules/dotnet/best-practices/SKILL.md +101 -0
- package/rules/dotnet/best-practices/references/REFERENCE.md +256 -0
- package/rules/dotnet/blazor/SKILL.md +146 -0
- package/rules/dotnet/blazor/references/REFERENCE.md +392 -0
- package/rules/dotnet/language/SKILL.md +82 -0
- package/rules/dotnet/language/references/REFERENCE.md +222 -0
- package/rules/dotnet/patterns.rule.md +388 -0
- package/rules/dotnet/razor-pages/SKILL.md +124 -0
- package/rules/dotnet/razor-pages/references/REFERENCE.md +321 -0
- package/rules/dotnet/security/SKILL.md +89 -0
- package/rules/dotnet/security/references/REFERENCE.md +295 -0
- package/rules/dotnet/tooling/SKILL.md +92 -0
- package/rules/dotnet/tooling/references/REFERENCE.md +300 -0
- package/rules/flutter/auto-route-navigation/SKILL.md +43 -0
- package/rules/flutter/auto-route-navigation/references/REFERENCE.md +19 -0
- package/rules/flutter/auto-route-navigation/references/router-config.md +62 -0
- package/rules/flutter/bloc-state-management/SKILL.md +64 -0
- package/rules/flutter/bloc-state-management/references/REFERENCE.md +20 -0
- package/rules/flutter/bloc-state-management/references/auth-bloc-example.md +52 -0
- package/rules/flutter/bloc-state-management/references/equatable-usage.md +56 -0
- package/rules/flutter/bloc-state-management/references/property-based-state.md +68 -0
- package/rules/flutter/bloc.rule.md +76 -0
- package/rules/flutter/cicd/SKILL.md +48 -0
- package/rules/flutter/cicd/references/advanced-workflow.md +66 -0
- package/rules/flutter/cicd/references/fastlane.md +139 -0
- package/rules/flutter/cicd/references/github-actions.md +59 -0
- package/rules/flutter/dependency-injection/SKILL.md +42 -0
- package/rules/flutter/dependency-injection/references/REFERENCE.md +15 -0
- package/rules/flutter/dependency-injection/references/modules.md +37 -0
- package/rules/flutter/error-handling/SKILL.md +32 -0
- package/rules/flutter/error-handling/references/REFERENCE.md +19 -0
- package/rules/flutter/error-handling/references/error-mapping.md +31 -0
- package/rules/flutter/feature-based-clean-architecture/SKILL.md +46 -0
- package/rules/flutter/feature-based-clean-architecture/references/REFERENCE.md +14 -0
- package/rules/flutter/feature-based-clean-architecture/references/folder-structure.md +36 -0
- package/rules/flutter/getx-navigation/SKILL.md +70 -0
- package/rules/flutter/getx-navigation/references/app-pages.md +40 -0
- package/rules/flutter/getx-navigation/references/middleware-example.md +29 -0
- package/rules/flutter/getx-state-management/SKILL.md +76 -0
- package/rules/flutter/getx-state-management/references/binding-example.md +32 -0
- package/rules/flutter/getx-state-management/references/reactive-vs-simple.md +39 -0
- package/rules/flutter/go-router-navigation/SKILL.md +57 -0
- package/rules/flutter/idiomatic-flutter/SKILL.md +20 -0
- package/rules/flutter/layer-based-clean-architecture/SKILL.md +50 -0
- package/rules/flutter/layer-based-clean-architecture/references/REFERENCE.md +60 -0
- package/rules/flutter/layer-based-clean-architecture/references/repository-mapping.md +50 -0
- package/rules/flutter/localization/SKILL.md +50 -0
- package/rules/flutter/localization/references/REFERENCE.md +48 -0
- package/rules/flutter/localization/references/sheet-loader.md +33 -0
- package/rules/flutter/navigator-v1-navigation/SKILL.md +71 -0
- package/rules/flutter/navigator-v1-navigation/references/on-generate-route.md +48 -0
- package/rules/flutter/performance/SKILL.md +24 -0
- package/rules/flutter/retrofit-networking/SKILL.md +51 -0
- package/rules/flutter/retrofit-networking/references/REFERENCE.md +19 -0
- package/rules/flutter/retrofit-networking/references/token-refresh.md +40 -0
- package/rules/flutter/riverpod-state-management/SKILL.md +53 -0
- package/rules/flutter/riverpod-state-management/references/architecture.md +124 -0
- package/rules/flutter/riverpod-state-management/references/best-practices.md +89 -0
- package/rules/flutter/riverpod-state-management/references/testing.md +73 -0
- package/rules/flutter/riverpod.rule.md +78 -0
- package/rules/flutter/security/SKILL.md +33 -0
- package/rules/flutter/security/references/REFERENCE.md +15 -0
- package/rules/flutter/security/references/network-security.md +28 -0
- package/rules/flutter/testing/SKILL.md +44 -0
- package/rules/flutter/testing/references/REFERENCE.md +21 -0
- package/rules/flutter/testing/references/bloc-testing.md +38 -0
- package/rules/flutter/testing/references/integration-testing.md +128 -0
- package/rules/flutter/testing/references/robot-pattern.md +82 -0
- package/rules/flutter/testing/references/unit-testing.md +130 -0
- package/rules/flutter/testing/references/widget-testing.md +120 -0
- package/rules/flutter/widgets/SKILL.md +37 -0
- package/rules/golang/chi-router/SKILL.md +219 -0
- package/rules/golang/chi-router/references/REFERENCE.md +13 -0
- package/rules/golang/chi-router/references/routing-patterns.md +205 -0
- package/rules/golang/cobra-cli/SKILL.md +227 -0
- package/rules/golang/cobra-cli/references/REFERENCE.md +13 -0
- package/rules/golang/cobra-cli/references/command-patterns.md +224 -0
- package/rules/golang/core/SKILL.md +210 -0
- package/rules/golang/core/references/REFERENCE.md +14 -0
- package/rules/golang/core/references/concurrency-patterns.md +114 -0
- package/rules/golang/core/references/error-handling.md +87 -0
- package/rules/golang/echo-framework/SKILL.md +215 -0
- package/rules/golang/echo-framework/references/REFERENCE.md +14 -0
- package/rules/golang/echo-framework/references/middleware-patterns.md +141 -0
- package/rules/golang/echo-framework/references/routing-patterns.md +140 -0
- package/rules/golang/ent-orm/SKILL.md +239 -0
- package/rules/golang/ent-orm/references/REFERENCE.md +13 -0
- package/rules/golang/ent-orm/references/schema-patterns.md +255 -0
- package/rules/golang/fiber-framework/SKILL.md +196 -0
- package/rules/golang/fiber-framework/references/REFERENCE.md +13 -0
- package/rules/golang/fiber-framework/references/routing-patterns.md +191 -0
- package/rules/golang/gin-framework/SKILL.md +205 -0
- package/rules/golang/gin-framework/references/REFERENCE.md +14 -0
- package/rules/golang/gin-framework/references/middleware-patterns.md +119 -0
- package/rules/golang/gorm-orm/SKILL.md +196 -0
- package/rules/golang/gorm-orm/references/REFERENCE.md +14 -0
- package/rules/golang/gorm-orm/references/model-definitions.md +167 -0
- package/rules/golang/gorm-orm/references/query-patterns.md +161 -0
- package/rules/golang/grpc/SKILL.md +231 -0
- package/rules/golang/grpc/references/REFERENCE.md +13 -0
- package/rules/golang/grpc/references/service-patterns.md +276 -0
- package/rules/golang/testify/SKILL.md +239 -0
- package/rules/golang/testify/references/REFERENCE.md +13 -0
- package/rules/golang/testify/references/assert-patterns.md +170 -0
- package/rules/golang/validator/SKILL.md +234 -0
- package/rules/golang/validator/references/REFERENCE.md +13 -0
- package/rules/golang/validator/references/validation-tags.md +211 -0
- package/rules/golang/viper-config/SKILL.md +244 -0
- package/rules/golang/viper-config/references/REFERENCE.md +13 -0
- package/rules/golang/viper-config/references/config-loading.md +181 -0
- package/rules/golang/wire-di/SKILL.md +243 -0
- package/rules/golang/wire-di/references/REFERENCE.md +13 -0
- package/rules/golang/wire-di/references/provider-patterns.md +193 -0
- package/rules/golang/zap-logging/SKILL.md +203 -0
- package/rules/golang/zap-logging/references/REFERENCE.md +13 -0
- package/rules/golang/zap-logging/references/logger-config.md +165 -0
- package/rules/java/build-gradle/SKILL.md +92 -0
- package/rules/java/build-gradle/references/REFERENCE.md +14 -0
- package/rules/java/build-gradle/references/kotlin-dsl.md +118 -0
- package/rules/java/build-gradle/references/task-configuration.md +132 -0
- package/rules/java/build-maven/SKILL.md +86 -0
- package/rules/java/build-maven/references/REFERENCE.md +14 -0
- package/rules/java/build-maven/references/dependency-management.md +111 -0
- package/rules/java/build-maven/references/lifecycle-phases.md +114 -0
- package/rules/java/graalvm-native/SKILL.md +105 -0
- package/rules/java/graalvm-native/references/REFERENCE.md +12 -0
- package/rules/java/java-collections-streams/SKILL.md +148 -0
- package/rules/java/java-collections-streams/references/REFERENCE.md +15 -0
- package/rules/java/java-collections-streams/references/collectors-patterns.md +178 -0
- package/rules/java/java-collections-streams/references/stream-pipelines.md +165 -0
- package/rules/java/java-concurrency/SKILL.md +187 -0
- package/rules/java/java-concurrency/references/REFERENCE.md +17 -0
- package/rules/java/java-concurrency/references/completable-future.md +165 -0
- package/rules/java/java-concurrency/references/executor-patterns.md +176 -0
- package/rules/java/java-concurrency/references/virtual-threads.md +190 -0
- package/rules/java/java-core-language/SKILL.md +121 -0
- package/rules/java/java-core-language/references/REFERENCE.md +15 -0
- package/rules/java/java-core-language/references/jvm-memory-model.md +160 -0
- package/rules/java/java-core-language/references/modern-java-features.md +168 -0
- package/rules/java/java-project-structure/SKILL.md +195 -0
- package/rules/java/java-project-structure/references/REFERENCE.md +15 -0
- package/rules/java/java-project-structure/references/maven-project-layout.md +199 -0
- package/rules/java/java-project-structure/references/module-system.md +159 -0
- package/rules/java/micronaut-core/SKILL.md +99 -0
- package/rules/java/micronaut-core/references/REFERENCE.md +12 -0
- package/rules/java/micronaut-reactive/SKILL.md +68 -0
- package/rules/java/micronaut-reactive/references/REFERENCE.md +12 -0
- package/rules/java/quarkus-core/SKILL.md +85 -0
- package/rules/java/quarkus-core/references/REFERENCE.md +12 -0
- package/rules/java/quarkus-reactive/SKILL.md +67 -0
- package/rules/java/quarkus-reactive/references/REFERENCE.md +12 -0
- package/rules/java/spring-batch/SKILL.md +102 -0
- package/rules/java/spring-batch/references/REFERENCE.md +12 -0
- package/rules/java/spring-boot-architecture/SKILL.md +206 -0
- package/rules/java/spring-boot-architecture/references/REFERENCE.md +15 -0
- package/rules/java/spring-boot-architecture/references/auto-configuration.md +158 -0
- package/rules/java/spring-boot-architecture/references/configuration-properties.md +202 -0
- package/rules/java/spring-boot-web/SKILL.md +217 -0
- package/rules/java/spring-boot-web/references/REFERENCE.md +17 -0
- package/rules/java/spring-cloud/SKILL.md +109 -0
- package/rules/java/spring-cloud/references/REFERENCE.md +13 -0
- package/rules/java/spring-data-jpa/SKILL.md +241 -0
- package/rules/java/spring-data-jpa/references/REFERENCE.md +16 -0
- package/rules/java/spring-security/SKILL.md +161 -0
- package/rules/java/spring-security/references/REFERENCE.md +16 -0
- package/rules/java/spring-security/references/jwt-auth-flow.md +213 -0
- package/rules/java/testing-junit-mockito/SKILL.md +135 -0
- package/rules/java/testing-junit-mockito/references/REFERENCE.md +15 -0
- package/rules/java/testing-junit-mockito/references/junit5-patterns.md +159 -0
- package/rules/java/testing-junit-mockito/references/mockito-patterns.md +148 -0
- package/rules/java/testing-junit-mockito/references/spring-boot-testing.md +152 -0
- package/rules/javascript/best-practices/SKILL.md +64 -0
- package/rules/javascript/best-practices/references/REFERENCE.md +91 -0
- package/rules/javascript/language/SKILL.md +71 -0
- package/rules/javascript/language/references/REFERENCE.md +106 -0
- package/rules/javascript/tooling/SKILL.md +60 -0
- package/rules/javascript/tooling/references/REFERENCE.md +107 -0
- package/rules/metadata.json +54 -0
- package/rules/nestjs/api-standards/SKILL.md +47 -0
- package/rules/nestjs/api-standards/references/pagination-wrapper.md +87 -0
- package/rules/nestjs/architecture/SKILL.md +68 -0
- package/rules/nestjs/architecture/references/dynamic-module.md +53 -0
- package/rules/nestjs/caching/SKILL.md +51 -0
- package/rules/nestjs/caching/references/REFERENCE.md +13 -0
- package/rules/nestjs/caching/references/cache-patterns.md +183 -0
- package/rules/nestjs/configuration/SKILL.md +41 -0
- package/rules/nestjs/configuration/references/REFERENCE.md +13 -0
- package/rules/nestjs/configuration/references/config-patterns.md +184 -0
- package/rules/nestjs/controllers-services/SKILL.md +63 -0
- package/rules/nestjs/controllers-services/references/REFERENCE.md +14 -0
- package/rules/nestjs/controllers-services/references/controller-patterns.md +119 -0
- package/rules/nestjs/controllers-services/references/service-patterns.md +129 -0
- package/rules/nestjs/database/SKILL.md +102 -0
- package/rules/nestjs/database/references/REFERENCE.md +14 -0
- package/rules/nestjs/database/references/typeorm-patterns.md +156 -0
- package/rules/nestjs/deployment/SKILL.md +36 -0
- package/rules/nestjs/deployment/references/REFERENCE.md +13 -0
- package/rules/nestjs/deployment/references/deployment-patterns.md +140 -0
- package/rules/nestjs/documentation/SKILL.md +64 -0
- package/rules/nestjs/documentation/references/REFERENCE.md +13 -0
- package/rules/nestjs/documentation/references/swagger-patterns.md +139 -0
- package/rules/nestjs/error-handling/SKILL.md +55 -0
- package/rules/nestjs/error-handling/references/REFERENCE.md +13 -0
- package/rules/nestjs/error-handling/references/exception-filters.md +152 -0
- package/rules/nestjs/file-uploads/SKILL.md +35 -0
- package/rules/nestjs/file-uploads/references/REFERENCE.md +13 -0
- package/rules/nestjs/file-uploads/references/upload-patterns.md +125 -0
- package/rules/nestjs/observability/SKILL.md +39 -0
- package/rules/nestjs/observability/references/REFERENCE.md +13 -0
- package/rules/nestjs/observability/references/logging-metrics.md +175 -0
- package/rules/nestjs/performance/SKILL.md +60 -0
- package/rules/nestjs/performance/references/REFERENCE.md +13 -0
- package/rules/nestjs/performance/references/performance-patterns.md +107 -0
- package/rules/nestjs/real-time/SKILL.md +45 -0
- package/rules/nestjs/real-time/references/REFERENCE.md +13 -0
- package/rules/nestjs/real-time/references/websocket-patterns.md +121 -0
- package/rules/nestjs/scheduling/SKILL.md +39 -0
- package/rules/nestjs/scheduling/references/REFERENCE.md +13 -0
- package/rules/nestjs/scheduling/references/scheduling-patterns.md +137 -0
- package/rules/nestjs/search/SKILL.md +41 -0
- package/rules/nestjs/search/references/REFERENCE.md +13 -0
- package/rules/nestjs/search/references/search-patterns.md +137 -0
- package/rules/nestjs/security/SKILL.md +87 -0
- package/rules/nestjs/security/references/REFERENCE.md +14 -0
- package/rules/nestjs/security/references/authentication.md +151 -0
- package/rules/nestjs/testing/SKILL.md +40 -0
- package/rules/nestjs/testing/references/REFERENCE.md +14 -0
- package/rules/nestjs/testing/references/unit-testing.md +179 -0
- package/rules/nestjs/transport/SKILL.md +45 -0
- package/rules/nestjs/transport/references/REFERENCE.md +13 -0
- package/rules/nestjs/transport/references/microservices-patterns.md +170 -0
- package/rules/nextjs/app-router/SKILL.md +46 -0
- package/rules/nextjs/app-router/references/REFERENCE.md +14 -0
- package/rules/nextjs/app-router/references/routing-patterns.md +182 -0
- package/rules/nextjs/architecture/SKILL.md +44 -0
- package/rules/nextjs/architecture/references/fsd-structure.md +77 -0
- package/rules/nextjs/authentication/SKILL.md +29 -0
- package/rules/nextjs/authentication/references/auth-implementation.md +73 -0
- package/rules/nextjs/caching/SKILL.md +66 -0
- package/rules/nextjs/caching/references/REFERENCE.md +13 -0
- package/rules/nextjs/caching/references/cache-strategies.md +168 -0
- package/rules/nextjs/data-access-layer/SKILL.md +33 -0
- package/rules/nextjs/data-access-layer/references/patterns.md +66 -0
- package/rules/nextjs/data-fetching/SKILL.md +59 -0
- package/rules/nextjs/data-fetching/references/REFERENCE.md +13 -0
- package/rules/nextjs/data-fetching/references/fetch-patterns.md +160 -0
- package/rules/nextjs/internationalization/SKILL.md +105 -0
- package/rules/nextjs/internationalization/references/REFERENCE.md +13 -0
- package/rules/nextjs/internationalization/references/i18n-patterns.md +180 -0
- package/rules/nextjs/optimization/SKILL.md +64 -0
- package/rules/nextjs/optimization/references/REFERENCE.md +13 -0
- package/rules/nextjs/optimization/references/optimization-patterns.md +190 -0
- package/rules/nextjs/rendering/SKILL.md +91 -0
- package/rules/nextjs/rendering/references/REFERENCE.md +13 -0
- package/rules/nextjs/rendering/references/rendering-modes.md +163 -0
- package/rules/nextjs/server-actions/SKILL.md +46 -0
- package/rules/nextjs/server-actions/references/REFERENCE.md +13 -0
- package/rules/nextjs/server-actions/references/action-patterns.md +188 -0
- package/rules/nextjs/server-components/SKILL.md +52 -0
- package/rules/nextjs/server-components/references/REFERENCE.md +13 -0
- package/rules/nextjs/server-components/references/component-patterns.md +175 -0
- package/rules/nextjs/state-management/SKILL.md +73 -0
- package/rules/nextjs/state-management/references/REFERENCE.md +13 -0
- package/rules/nextjs/state-management/references/state-patterns.md +218 -0
- package/rules/nextjs/styling/SKILL.md +31 -0
- package/rules/nextjs/styling/references/implementation.md +56 -0
- package/rules/react/component-patterns/SKILL.md +66 -0
- package/rules/react/component-patterns/references/REFERENCE.md +126 -0
- package/rules/react/hooks/SKILL.md +60 -0
- package/rules/react/hooks/references/REFERENCE.md +132 -0
- package/rules/react/hooks.rule.md +79 -0
- package/rules/react/performance/SKILL.md +69 -0
- package/rules/react/performance/references/REFERENCE.md +143 -0
- package/rules/react/security/SKILL.md +46 -0
- package/rules/react/security/references/REFERENCE.md +170 -0
- package/rules/react/state-management/SKILL.md +56 -0
- package/rules/react/state-management/references/REFERENCE.md +137 -0
- package/rules/react/testing/SKILL.md +45 -0
- package/rules/react/testing/references/REFERENCE.md +149 -0
- package/rules/react/tooling/SKILL.md +39 -0
- package/rules/react/typescript/SKILL.md +53 -0
- package/rules/rust/actix-web/SKILL.md +160 -0
- package/rules/rust/actix-web/references/REFERENCE.md +13 -0
- package/rules/rust/actix-web/references/handler-patterns.md +198 -0
- package/rules/rust/async-graphql/SKILL.md +228 -0
- package/rules/rust/async-graphql/references/REFERENCE.md +13 -0
- package/rules/rust/async-graphql/references/schema-patterns.md +215 -0
- package/rules/rust/axum/SKILL.md +161 -0
- package/rules/rust/axum/references/REFERENCE.md +14 -0
- package/rules/rust/axum/references/handler-patterns.md +97 -0
- package/rules/rust/bevy/SKILL.md +206 -0
- package/rules/rust/bevy/references/REFERENCE.md +13 -0
- package/rules/rust/bevy/references/ecs-patterns.md +226 -0
- package/rules/rust/clap/SKILL.md +217 -0
- package/rules/rust/clap/references/REFERENCE.md +13 -0
- package/rules/rust/clap/references/derive-patterns.md +205 -0
- package/rules/rust/core/SKILL.md +154 -0
- package/rules/rust/core/references/REFERENCE.md +14 -0
- package/rules/rust/core/references/error-handling.md +92 -0
- package/rules/rust/diesel-orm/SKILL.md +176 -0
- package/rules/rust/diesel-orm/references/REFERENCE.md +13 -0
- package/rules/rust/diesel-orm/references/schema-patterns.md +206 -0
- package/rules/rust/rocket/SKILL.md +182 -0
- package/rules/rust/rocket/references/REFERENCE.md +13 -0
- package/rules/rust/rocket/references/handler-patterns.md +209 -0
- package/rules/rust/sea-orm/SKILL.md +230 -0
- package/rules/rust/sea-orm/references/REFERENCE.md +13 -0
- package/rules/rust/sea-orm/references/entity-patterns.md +221 -0
- package/rules/rust/serde-serialization/SKILL.md +150 -0
- package/rules/rust/serde-serialization/references/REFERENCE.md +13 -0
- package/rules/rust/serde-serialization/references/serialization-patterns.md +199 -0
- package/rules/rust/sqlx-database/SKILL.md +140 -0
- package/rules/rust/sqlx-database/references/REFERENCE.md +13 -0
- package/rules/rust/sqlx-database/references/query-patterns.md +210 -0
- package/rules/rust/tauri/SKILL.md +180 -0
- package/rules/rust/tauri/references/REFERENCE.md +13 -0
- package/rules/rust/tauri/references/command-patterns.md +209 -0
- package/rules/rust/tokio-runtime/SKILL.md +167 -0
- package/rules/rust/tokio-runtime/references/REFERENCE.md +14 -0
- package/rules/rust/tokio-runtime/references/async-patterns.md +137 -0
- package/rules/rust/tokio-runtime/references/synchronization.md +152 -0
- package/rules/rust/tonic/SKILL.md +231 -0
- package/rules/rust/tonic/references/REFERENCE.md +13 -0
- package/rules/rust/tonic/references/service-patterns.md +213 -0
- package/rules/rust/tracing/SKILL.md +214 -0
- package/rules/rust/tracing/references/REFERENCE.md +13 -0
- package/rules/rust/tracing/references/instrumentation.md +187 -0
- package/rules/typescript/best-practices/SKILL.md +108 -0
- package/rules/typescript/best-practices/references/REFERENCE.md +68 -0
- package/rules/typescript/language/SKILL.md +72 -0
- package/rules/typescript/language/references/REFERENCE.md +67 -0
- package/rules/typescript/patterns.rule.md +85 -0
- package/rules/typescript/security/SKILL.md +59 -0
- package/rules/typescript/security/references/REFERENCE.md +113 -0
- package/rules/typescript/tooling/SKILL.md +52 -0
- package/rules/typescript/tooling/references/REFERENCE.md +110 -0
- package/skills/learn/SKILL.md +476 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# ASP.NET Core Web API Reference
|
|
2
|
+
|
|
3
|
+
Middleware, exception handling, and advanced patterns.
|
|
4
|
+
|
|
5
|
+
## References
|
|
6
|
+
|
|
7
|
+
- [**Middleware**](middleware.md) - Custom middleware patterns.
|
|
8
|
+
- [**Exception Handling**](exception-handling.md) - Global error handling.
|
|
9
|
+
- [**HttpClientFactory**](httpclient-factory.md) - Typed HTTP clients.
|
|
10
|
+
|
|
11
|
+
## Global Exception Handling Middleware
|
|
12
|
+
|
|
13
|
+
```csharp
|
|
14
|
+
public class ExceptionMiddleware(
|
|
15
|
+
RequestDelegate next,
|
|
16
|
+
ILogger<ExceptionMiddleware> logger,
|
|
17
|
+
IHostEnvironment env)
|
|
18
|
+
{
|
|
19
|
+
public async Task InvokeAsync(HttpContext context)
|
|
20
|
+
{
|
|
21
|
+
try
|
|
22
|
+
{
|
|
23
|
+
await next(context);
|
|
24
|
+
}
|
|
25
|
+
catch (Exception ex)
|
|
26
|
+
{
|
|
27
|
+
logger.LogError(ex, "Unhandled exception: {Message}", ex.Message);
|
|
28
|
+
await HandleExceptionAsync(context, ex);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private async Task HandleExceptionAsync(HttpContext context, Exception exception)
|
|
33
|
+
{
|
|
34
|
+
context.Response.ContentType = "application/problem+json";
|
|
35
|
+
|
|
36
|
+
var (statusCode, title) = exception switch
|
|
37
|
+
{
|
|
38
|
+
NotFoundException => (StatusCodes.Status404NotFound, "Not Found"),
|
|
39
|
+
ValidationException => (StatusCodes.Status400BadRequest, "Validation Error"),
|
|
40
|
+
UnauthorizedAccessException => (StatusCodes.Status401Unauthorized, "Unauthorized"),
|
|
41
|
+
ForbiddenException => (StatusCodes.Status403Forbidden, "Forbidden"),
|
|
42
|
+
_ => (StatusCodes.Status500InternalServerError, "Internal Server Error")
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
context.Response.StatusCode = statusCode;
|
|
46
|
+
|
|
47
|
+
var problem = new ProblemDetails
|
|
48
|
+
{
|
|
49
|
+
Status = statusCode,
|
|
50
|
+
Title = title,
|
|
51
|
+
Detail = env.IsDevelopment() ? exception.Message : null,
|
|
52
|
+
Instance = context.Request.Path
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
if (exception is ValidationException validationEx)
|
|
56
|
+
{
|
|
57
|
+
problem.Extensions["errors"] = validationEx.Errors;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
await context.Response.WriteAsJsonAsync(problem);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Registration
|
|
65
|
+
app.UseMiddleware<ExceptionMiddleware>();
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Minimal API Endpoint Filters
|
|
69
|
+
|
|
70
|
+
```csharp
|
|
71
|
+
// Validation filter
|
|
72
|
+
public class ValidationFilter<T> : IEndpointFilter where T : class
|
|
73
|
+
{
|
|
74
|
+
public async ValueTask<object?> InvokeAsync(
|
|
75
|
+
EndpointFilterInvocationContext context,
|
|
76
|
+
EndpointFilterDelegate next)
|
|
77
|
+
{
|
|
78
|
+
var model = context.Arguments.OfType<T>().FirstOrDefault();
|
|
79
|
+
|
|
80
|
+
if (model is null)
|
|
81
|
+
return TypedResults.BadRequest("Request body is required");
|
|
82
|
+
|
|
83
|
+
var validator = context.HttpContext.RequestServices.GetService<IValidator<T>>();
|
|
84
|
+
|
|
85
|
+
if (validator is not null)
|
|
86
|
+
{
|
|
87
|
+
var result = await validator.ValidateAsync(model);
|
|
88
|
+
if (!result.IsValid)
|
|
89
|
+
{
|
|
90
|
+
return TypedResults.ValidationProblem(
|
|
91
|
+
result.ToDictionary());
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return await next(context);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Logging filter
|
|
100
|
+
public class LoggingFilter(ILogger<LoggingFilter> logger) : IEndpointFilter
|
|
101
|
+
{
|
|
102
|
+
public async ValueTask<object?> InvokeAsync(
|
|
103
|
+
EndpointFilterInvocationContext context,
|
|
104
|
+
EndpointFilterDelegate next)
|
|
105
|
+
{
|
|
106
|
+
var path = context.HttpContext.Request.Path;
|
|
107
|
+
logger.LogInformation("Request started: {Path}", path);
|
|
108
|
+
|
|
109
|
+
var sw = Stopwatch.StartNew();
|
|
110
|
+
var result = await next(context);
|
|
111
|
+
sw.Stop();
|
|
112
|
+
|
|
113
|
+
logger.LogInformation("Request completed: {Path} in {ElapsedMs}ms",
|
|
114
|
+
path, sw.ElapsedMilliseconds);
|
|
115
|
+
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Usage
|
|
121
|
+
app.MapPost("/api/users", handler)
|
|
122
|
+
.AddEndpointFilter<ValidationFilter<CreateUserDto>>()
|
|
123
|
+
.AddEndpointFilter<LoggingFilter>();
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## IHttpClientFactory Typed Clients
|
|
127
|
+
|
|
128
|
+
```csharp
|
|
129
|
+
// Typed client
|
|
130
|
+
public class PaymentClient(HttpClient httpClient)
|
|
131
|
+
{
|
|
132
|
+
public async Task<PaymentResult?> ProcessPaymentAsync(PaymentRequest request)
|
|
133
|
+
{
|
|
134
|
+
var response = await httpClient.PostAsJsonAsync("/payments", request);
|
|
135
|
+
response.EnsureSuccessStatusCode();
|
|
136
|
+
return await response.Content.ReadFromJsonAsync<PaymentResult>();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
public async Task<PaymentStatus?> GetStatusAsync(string paymentId)
|
|
140
|
+
{
|
|
141
|
+
return await httpClient.GetFromJsonAsync<PaymentStatus>($"/payments/{paymentId}");
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Registration with resilience
|
|
146
|
+
builder.Services.AddHttpClient<PaymentClient>(client =>
|
|
147
|
+
{
|
|
148
|
+
client.BaseAddress = new Uri(builder.Configuration["Payment:BaseUrl"]!);
|
|
149
|
+
client.DefaultRequestHeaders.Add("X-Api-Key",
|
|
150
|
+
builder.Configuration["Payment:ApiKey"]);
|
|
151
|
+
client.Timeout = TimeSpan.FromSeconds(30);
|
|
152
|
+
})
|
|
153
|
+
.AddStandardResilienceHandler(); // Polly retry, circuit breaker
|
|
154
|
+
|
|
155
|
+
// Manual resilience configuration
|
|
156
|
+
builder.Services.AddHttpClient<PaymentClient>()
|
|
157
|
+
.AddResilienceHandler("payment-pipeline", builder =>
|
|
158
|
+
{
|
|
159
|
+
builder
|
|
160
|
+
.AddRetry(new HttpRetryStrategyOptions
|
|
161
|
+
{
|
|
162
|
+
MaxRetryAttempts = 3,
|
|
163
|
+
Delay = TimeSpan.FromSeconds(1),
|
|
164
|
+
BackoffType = DelayBackoffType.Exponential
|
|
165
|
+
})
|
|
166
|
+
.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
|
|
167
|
+
{
|
|
168
|
+
SamplingDuration = TimeSpan.FromSeconds(30),
|
|
169
|
+
FailureRatio = 0.5,
|
|
170
|
+
MinimumThroughput = 10,
|
|
171
|
+
BreakDuration = TimeSpan.FromSeconds(30)
|
|
172
|
+
})
|
|
173
|
+
.AddTimeout(TimeSpan.FromSeconds(10));
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Background Services
|
|
178
|
+
|
|
179
|
+
```csharp
|
|
180
|
+
public class OrderProcessingService(
|
|
181
|
+
IServiceScopeFactory scopeFactory,
|
|
182
|
+
ILogger<OrderProcessingService> logger) : BackgroundService
|
|
183
|
+
{
|
|
184
|
+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
185
|
+
{
|
|
186
|
+
logger.LogInformation("Order processing service started");
|
|
187
|
+
|
|
188
|
+
while (!stoppingToken.IsCancellationRequested)
|
|
189
|
+
{
|
|
190
|
+
try
|
|
191
|
+
{
|
|
192
|
+
await ProcessPendingOrdersAsync(stoppingToken);
|
|
193
|
+
}
|
|
194
|
+
catch (Exception ex)
|
|
195
|
+
{
|
|
196
|
+
logger.LogError(ex, "Error processing orders");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private async Task ProcessPendingOrdersAsync(CancellationToken ct)
|
|
204
|
+
{
|
|
205
|
+
using var scope = scopeFactory.CreateScope();
|
|
206
|
+
var orderService = scope.ServiceProvider.GetRequiredService<IOrderService>();
|
|
207
|
+
|
|
208
|
+
var pendingOrders = await orderService.GetPendingOrdersAsync(ct);
|
|
209
|
+
|
|
210
|
+
foreach (var order in pendingOrders)
|
|
211
|
+
{
|
|
212
|
+
await orderService.ProcessAsync(order.Id, ct);
|
|
213
|
+
logger.LogInformation("Processed order {OrderId}", order.Id);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Registration
|
|
219
|
+
builder.Services.AddHostedService<OrderProcessingService>();
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Health Checks
|
|
223
|
+
|
|
224
|
+
```csharp
|
|
225
|
+
// Registration
|
|
226
|
+
builder.Services.AddHealthChecks()
|
|
227
|
+
.AddDbContextCheck<AppDbContext>("database")
|
|
228
|
+
.AddRedis(builder.Configuration.GetConnectionString("Redis")!, "redis")
|
|
229
|
+
.AddUrlGroup(new Uri("https://api.external.com/health"), "external-api")
|
|
230
|
+
.AddCheck<CustomHealthCheck>("custom");
|
|
231
|
+
|
|
232
|
+
// Custom health check
|
|
233
|
+
public class CustomHealthCheck(IOrderService orderService) : IHealthCheck
|
|
234
|
+
{
|
|
235
|
+
public async Task<HealthCheckResult> CheckHealthAsync(
|
|
236
|
+
HealthCheckContext context,
|
|
237
|
+
CancellationToken ct = default)
|
|
238
|
+
{
|
|
239
|
+
try
|
|
240
|
+
{
|
|
241
|
+
var count = await orderService.GetPendingCountAsync(ct);
|
|
242
|
+
|
|
243
|
+
if (count > 1000)
|
|
244
|
+
{
|
|
245
|
+
return HealthCheckResult.Degraded(
|
|
246
|
+
$"High pending orders: {count}");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return HealthCheckResult.Healthy();
|
|
250
|
+
}
|
|
251
|
+
catch (Exception ex)
|
|
252
|
+
{
|
|
253
|
+
return HealthCheckResult.Unhealthy("Order service unavailable", ex);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Endpoints
|
|
259
|
+
app.MapHealthChecks("/health", new HealthCheckOptions
|
|
260
|
+
{
|
|
261
|
+
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
app.MapHealthChecks("/health/ready", new HealthCheckOptions
|
|
265
|
+
{
|
|
266
|
+
Predicate = check => check.Tags.Contains("ready")
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
app.MapHealthChecks("/health/live", new HealthCheckOptions
|
|
270
|
+
{
|
|
271
|
+
Predicate = _ => false // Just checks if app is running
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Model Binding and Validation
|
|
276
|
+
|
|
277
|
+
```csharp
|
|
278
|
+
// FluentValidation
|
|
279
|
+
public class CreateOrderDtoValidator : AbstractValidator<CreateOrderDto>
|
|
280
|
+
{
|
|
281
|
+
public CreateOrderDtoValidator()
|
|
282
|
+
{
|
|
283
|
+
RuleFor(x => x.CustomerId)
|
|
284
|
+
.NotEmpty()
|
|
285
|
+
.WithMessage("Customer ID is required");
|
|
286
|
+
|
|
287
|
+
RuleFor(x => x.Items)
|
|
288
|
+
.NotEmpty()
|
|
289
|
+
.WithMessage("Order must have at least one item");
|
|
290
|
+
|
|
291
|
+
RuleForEach(x => x.Items).ChildRules(item =>
|
|
292
|
+
{
|
|
293
|
+
item.RuleFor(x => x.ProductId).NotEmpty();
|
|
294
|
+
item.RuleFor(x => x.Quantity).GreaterThan(0);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
RuleFor(x => x.ShippingAddress)
|
|
298
|
+
.NotNull()
|
|
299
|
+
.SetValidator(new AddressValidator());
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Registration
|
|
304
|
+
builder.Services.AddValidatorsFromAssemblyContaining<CreateOrderDtoValidator>();
|
|
305
|
+
|
|
306
|
+
// Pipeline behavior for MediatR
|
|
307
|
+
public class ValidationBehavior<TRequest, TResponse>(
|
|
308
|
+
IEnumerable<IValidator<TRequest>> validators)
|
|
309
|
+
: IPipelineBehavior<TRequest, TResponse>
|
|
310
|
+
where TRequest : notnull
|
|
311
|
+
{
|
|
312
|
+
public async Task<TResponse> Handle(
|
|
313
|
+
TRequest request,
|
|
314
|
+
RequestHandlerDelegate<TResponse> next,
|
|
315
|
+
CancellationToken ct)
|
|
316
|
+
{
|
|
317
|
+
if (!validators.Any())
|
|
318
|
+
return await next();
|
|
319
|
+
|
|
320
|
+
var context = new ValidationContext<TRequest>(request);
|
|
321
|
+
var results = await Task.WhenAll(
|
|
322
|
+
validators.Select(v => v.ValidateAsync(context, ct)));
|
|
323
|
+
|
|
324
|
+
var failures = results
|
|
325
|
+
.SelectMany(r => r.Errors)
|
|
326
|
+
.Where(f => f != null)
|
|
327
|
+
.ToList();
|
|
328
|
+
|
|
329
|
+
if (failures.Count != 0)
|
|
330
|
+
throw new ValidationException(failures);
|
|
331
|
+
|
|
332
|
+
return await next();
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
```
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: C# Best Practices
|
|
3
|
+
description: Idiomatic C# patterns for clean, maintainable code.
|
|
4
|
+
metadata:
|
|
5
|
+
labels: [csharp, conventions, naming, structure, di]
|
|
6
|
+
triggers:
|
|
7
|
+
files: ['**/*.cs']
|
|
8
|
+
keywords: [class, namespace, using, public, private, internal]
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# C# Best Practices
|
|
12
|
+
|
|
13
|
+
## **Priority: P1 (OPERATIONAL)**
|
|
14
|
+
|
|
15
|
+
Idiomatic patterns for clean, maintainable C# code.
|
|
16
|
+
|
|
17
|
+
## Implementation Guidelines
|
|
18
|
+
|
|
19
|
+
- **Naming Conventions**:
|
|
20
|
+
- `PascalCase`: Types, methods, properties, events, namespaces
|
|
21
|
+
- `camelCase`: Parameters, local variables
|
|
22
|
+
- `_camelCase`: Private fields
|
|
23
|
+
- `IPascalCase`: Interfaces (prefix with `I`)
|
|
24
|
+
- `TPascalCase`: Type parameters (prefix with `T`)
|
|
25
|
+
- **Project Structure**: Clean/Onion architecture. Feature folders over layer folders.
|
|
26
|
+
- **Dependency Injection**: Constructor injection only. Register in `IServiceCollection`.
|
|
27
|
+
- **Logging**: `ILogger<T>` with structured logging. Use appropriate log levels.
|
|
28
|
+
- **Configuration**: `IOptions<T>` pattern with validation. Never hardcode settings.
|
|
29
|
+
- **File Organization**: One type per file. Use `global using` for common namespaces.
|
|
30
|
+
- **Immutability**: Prefer `readonly`, `init`, `record` for data integrity.
|
|
31
|
+
- **Expression-bodied Members**: Use for simple single-line methods/properties.
|
|
32
|
+
|
|
33
|
+
## Anti-Patterns
|
|
34
|
+
|
|
35
|
+
- **No static services**: Use DI instead of `static class ServiceHelper`.
|
|
36
|
+
- **No service locator**: Avoid `IServiceProvider.GetService()` in business logic.
|
|
37
|
+
- **No `new()` for dependencies**: Inject via constructor.
|
|
38
|
+
- **No magic strings**: Use `nameof()`, constants, or configuration.
|
|
39
|
+
- **No God classes**: Split large classes by responsibility.
|
|
40
|
+
- **No regions**: Use partial classes or extract types instead.
|
|
41
|
+
|
|
42
|
+
## Code
|
|
43
|
+
|
|
44
|
+
```csharp
|
|
45
|
+
// Proper DI registration
|
|
46
|
+
public static class ServiceCollectionExtensions
|
|
47
|
+
{
|
|
48
|
+
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
|
|
49
|
+
{
|
|
50
|
+
services.AddScoped<IUserService, UserService>();
|
|
51
|
+
services.AddScoped<IOrderService, OrderService>();
|
|
52
|
+
|
|
53
|
+
services.AddOptions<EmailSettings>()
|
|
54
|
+
.BindConfiguration("Email")
|
|
55
|
+
.ValidateDataAnnotations()
|
|
56
|
+
.ValidateOnStart();
|
|
57
|
+
|
|
58
|
+
return services;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Structured logging with semantic names
|
|
63
|
+
public class OrderService(IOrderRepository repo, ILogger<OrderService> logger)
|
|
64
|
+
{
|
|
65
|
+
public async Task<Order?> GetOrderAsync(int orderId, CancellationToken ct)
|
|
66
|
+
{
|
|
67
|
+
logger.LogDebug("Fetching order {OrderId}", orderId);
|
|
68
|
+
|
|
69
|
+
var order = await repo.GetByIdAsync(orderId, ct);
|
|
70
|
+
|
|
71
|
+
if (order is null)
|
|
72
|
+
logger.LogWarning("Order {OrderId} not found", orderId);
|
|
73
|
+
|
|
74
|
+
return order;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Options pattern with validation
|
|
79
|
+
public class EmailSettings
|
|
80
|
+
{
|
|
81
|
+
public const string SectionName = "Email";
|
|
82
|
+
|
|
83
|
+
[Required]
|
|
84
|
+
public string SmtpHost { get; init; } = string.Empty;
|
|
85
|
+
|
|
86
|
+
[Range(1, 65535)]
|
|
87
|
+
public int SmtpPort { get; init; } = 587;
|
|
88
|
+
|
|
89
|
+
[Required, EmailAddress]
|
|
90
|
+
public string FromAddress { get; init; } = string.Empty;
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Reference & Examples
|
|
95
|
+
|
|
96
|
+
For project structure templates and DI patterns:
|
|
97
|
+
See [references/REFERENCE.md](references/REFERENCE.md).
|
|
98
|
+
|
|
99
|
+
## Related Topics
|
|
100
|
+
|
|
101
|
+
language | security | tooling
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# C# Best Practices Reference
|
|
2
|
+
|
|
3
|
+
Project structure, dependency injection, and configuration patterns.
|
|
4
|
+
|
|
5
|
+
## References
|
|
6
|
+
|
|
7
|
+
- [**Project Structure**](project-structure.md) - Clean Architecture layout.
|
|
8
|
+
- [**DI Lifetimes**](di-lifetimes.md) - Transient, Scoped, Singleton comparison.
|
|
9
|
+
- [**Logging**](logging.md) - Structured logging best practices.
|
|
10
|
+
|
|
11
|
+
## Project Structure (Clean Architecture)
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
src/
|
|
15
|
+
├── Domain/ # Core business logic (no dependencies)
|
|
16
|
+
│ ├── Entities/
|
|
17
|
+
│ │ ├── User.cs
|
|
18
|
+
│ │ └── Order.cs
|
|
19
|
+
│ ├── ValueObjects/
|
|
20
|
+
│ │ └── Money.cs
|
|
21
|
+
│ ├── Enums/
|
|
22
|
+
│ │ └── OrderStatus.cs
|
|
23
|
+
│ └── Interfaces/
|
|
24
|
+
│ ├── IUserRepository.cs
|
|
25
|
+
│ └── IOrderRepository.cs
|
|
26
|
+
│
|
|
27
|
+
├── Application/ # Use cases (depends on Domain)
|
|
28
|
+
│ ├── Common/
|
|
29
|
+
│ │ ├── Interfaces/
|
|
30
|
+
│ │ │ └── IEmailService.cs
|
|
31
|
+
│ │ └── Behaviors/
|
|
32
|
+
│ │ └── ValidationBehavior.cs
|
|
33
|
+
│ ├── Users/
|
|
34
|
+
│ │ ├── Commands/
|
|
35
|
+
│ │ │ └── CreateUserCommand.cs
|
|
36
|
+
│ │ └── Queries/
|
|
37
|
+
│ │ └── GetUserQuery.cs
|
|
38
|
+
│ └── DependencyInjection.cs
|
|
39
|
+
│
|
|
40
|
+
├── Infrastructure/ # External concerns (DB, Email, etc.)
|
|
41
|
+
│ ├── Data/
|
|
42
|
+
│ │ ├── AppDbContext.cs
|
|
43
|
+
│ │ └── Repositories/
|
|
44
|
+
│ │ └── UserRepository.cs
|
|
45
|
+
│ ├── Services/
|
|
46
|
+
│ │ └── EmailService.cs
|
|
47
|
+
│ └── DependencyInjection.cs
|
|
48
|
+
│
|
|
49
|
+
└── WebApi/ # Presentation layer
|
|
50
|
+
├── Controllers/
|
|
51
|
+
│ └── UsersController.cs
|
|
52
|
+
├── Middleware/
|
|
53
|
+
│ └── ExceptionMiddleware.cs
|
|
54
|
+
└── Program.cs
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## DI Lifetime Comparison
|
|
58
|
+
|
|
59
|
+
| Lifetime | Created | Disposed | Use Case |
|
|
60
|
+
|----------|---------|----------|----------|
|
|
61
|
+
| **Transient** | Every request | After use | Lightweight, stateless services |
|
|
62
|
+
| **Scoped** | Once per HTTP request | End of request | DbContext, UnitOfWork |
|
|
63
|
+
| **Singleton** | Once per app | App shutdown | Caches, HttpClientFactory |
|
|
64
|
+
|
|
65
|
+
```csharp
|
|
66
|
+
// Registration examples
|
|
67
|
+
services.AddTransient<IEmailSender, EmailSender>(); // New instance each time
|
|
68
|
+
services.AddScoped<IUserRepository, UserRepository>(); // Per HTTP request
|
|
69
|
+
services.AddSingleton<ICacheService, MemoryCacheService>(); // App lifetime
|
|
70
|
+
|
|
71
|
+
// Common mistake: Scoped in Singleton (captive dependency)
|
|
72
|
+
// ❌ DON'T: Singleton depending on Scoped
|
|
73
|
+
public class SingletonService(IScopedService scoped) { } // Runtime error!
|
|
74
|
+
|
|
75
|
+
// ✅ DO: Use IServiceScopeFactory for scoped in singleton
|
|
76
|
+
public class SingletonService(IServiceScopeFactory scopeFactory)
|
|
77
|
+
{
|
|
78
|
+
public async Task DoWork()
|
|
79
|
+
{
|
|
80
|
+
using var scope = scopeFactory.CreateScope();
|
|
81
|
+
var scoped = scope.ServiceProvider.GetRequiredService<IScopedService>();
|
|
82
|
+
await scoped.ProcessAsync();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Logging Best Practices
|
|
88
|
+
|
|
89
|
+
```csharp
|
|
90
|
+
// Log levels usage
|
|
91
|
+
public class OrderProcessor(ILogger<OrderProcessor> logger)
|
|
92
|
+
{
|
|
93
|
+
public async Task ProcessAsync(Order order)
|
|
94
|
+
{
|
|
95
|
+
// Trace: Very detailed, only for debugging specific issues
|
|
96
|
+
logger.LogTrace("Processing order with data: {@Order}", order);
|
|
97
|
+
|
|
98
|
+
// Debug: Development diagnostics
|
|
99
|
+
logger.LogDebug("Starting order processing for {OrderId}", order.Id);
|
|
100
|
+
|
|
101
|
+
// Information: Normal operation milestones
|
|
102
|
+
logger.LogInformation("Order {OrderId} processed successfully", order.Id);
|
|
103
|
+
|
|
104
|
+
// Warning: Unexpected but handled situations
|
|
105
|
+
if (order.Items.Count == 0)
|
|
106
|
+
logger.LogWarning("Order {OrderId} has no items", order.Id);
|
|
107
|
+
|
|
108
|
+
// Error: Failures that need attention
|
|
109
|
+
try { await SendEmailAsync(order); }
|
|
110
|
+
catch (Exception ex)
|
|
111
|
+
{
|
|
112
|
+
logger.LogError(ex, "Failed to send email for order {OrderId}", order.Id);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Critical: App-breaking failures
|
|
116
|
+
// logger.LogCritical("Database connection lost");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// High-performance logging with source generators
|
|
121
|
+
public static partial class LogMessages
|
|
122
|
+
{
|
|
123
|
+
[LoggerMessage(Level = LogLevel.Information, Message = "Order {OrderId} created by {UserId}")]
|
|
124
|
+
public static partial void OrderCreated(this ILogger logger, int orderId, int userId);
|
|
125
|
+
|
|
126
|
+
[LoggerMessage(Level = LogLevel.Error, Message = "Payment failed for order {OrderId}")]
|
|
127
|
+
public static partial void PaymentFailed(this ILogger logger, int orderId, Exception ex);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Usage
|
|
131
|
+
logger.OrderCreated(order.Id, user.Id);
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Configuration Patterns
|
|
135
|
+
|
|
136
|
+
```csharp
|
|
137
|
+
// appsettings.json structure
|
|
138
|
+
{
|
|
139
|
+
"Database": {
|
|
140
|
+
"ConnectionString": "...",
|
|
141
|
+
"CommandTimeout": 30
|
|
142
|
+
},
|
|
143
|
+
"Email": {
|
|
144
|
+
"SmtpHost": "smtp.example.com",
|
|
145
|
+
"SmtpPort": 587,
|
|
146
|
+
"FromAddress": "noreply@example.com"
|
|
147
|
+
},
|
|
148
|
+
"Features": {
|
|
149
|
+
"EnableNewDashboard": true
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Strongly-typed options
|
|
154
|
+
public class DatabaseSettings
|
|
155
|
+
{
|
|
156
|
+
public const string SectionName = "Database";
|
|
157
|
+
|
|
158
|
+
[Required]
|
|
159
|
+
public string ConnectionString { get; init; } = string.Empty;
|
|
160
|
+
|
|
161
|
+
[Range(1, 300)]
|
|
162
|
+
public int CommandTimeout { get; init; } = 30;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Registration with validation
|
|
166
|
+
builder.Services.AddOptions<DatabaseSettings>()
|
|
167
|
+
.BindConfiguration(DatabaseSettings.SectionName)
|
|
168
|
+
.ValidateDataAnnotations()
|
|
169
|
+
.ValidateOnStart(); // Fail fast at startup
|
|
170
|
+
|
|
171
|
+
// Usage patterns
|
|
172
|
+
public class DataService(IOptions<DatabaseSettings> options)
|
|
173
|
+
{
|
|
174
|
+
// IOptions<T> - Singleton, read once at startup
|
|
175
|
+
private readonly DatabaseSettings _settings = options.Value;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
public class FeatureService(IOptionsSnapshot<DatabaseSettings> options)
|
|
179
|
+
{
|
|
180
|
+
// IOptionsSnapshot<T> - Scoped, re-reads on each request
|
|
181
|
+
public void DoWork() => Console.WriteLine(options.Value.CommandTimeout);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
public class BackgroundService(IOptionsMonitor<DatabaseSettings> options)
|
|
185
|
+
{
|
|
186
|
+
// IOptionsMonitor<T> - Singleton with change notifications
|
|
187
|
+
public BackgroundService()
|
|
188
|
+
{
|
|
189
|
+
options.OnChange(settings => Console.WriteLine("Config changed!"));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Extension Method Patterns
|
|
195
|
+
|
|
196
|
+
```csharp
|
|
197
|
+
// Service registration extensions
|
|
198
|
+
public static class ServiceCollectionExtensions
|
|
199
|
+
{
|
|
200
|
+
public static IServiceCollection AddInfrastructure(
|
|
201
|
+
this IServiceCollection services,
|
|
202
|
+
IConfiguration configuration)
|
|
203
|
+
{
|
|
204
|
+
// Database
|
|
205
|
+
services.AddDbContext<AppDbContext>(options =>
|
|
206
|
+
options.UseSqlServer(configuration.GetConnectionString("Default")));
|
|
207
|
+
|
|
208
|
+
// Repositories
|
|
209
|
+
services.AddScoped<IUserRepository, UserRepository>();
|
|
210
|
+
services.AddScoped<IOrderRepository, OrderRepository>();
|
|
211
|
+
|
|
212
|
+
// External services
|
|
213
|
+
services.AddHttpClient<IPaymentClient, PaymentClient>(client =>
|
|
214
|
+
{
|
|
215
|
+
client.BaseAddress = new Uri(configuration["Payment:BaseUrl"]!);
|
|
216
|
+
client.Timeout = TimeSpan.FromSeconds(30);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
return services;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Usage in Program.cs
|
|
224
|
+
builder.Services
|
|
225
|
+
.AddInfrastructure(builder.Configuration)
|
|
226
|
+
.AddApplication();
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Naming Convention Examples
|
|
230
|
+
|
|
231
|
+
```csharp
|
|
232
|
+
// ✅ Correct naming
|
|
233
|
+
public interface IUserRepository { } // Interface: IPascalCase
|
|
234
|
+
public class UserRepository : IUserRepository // Class: PascalCase
|
|
235
|
+
{
|
|
236
|
+
private readonly DbContext _context; // Private field: _camelCase
|
|
237
|
+
private readonly ILogger<UserRepository> _logger;
|
|
238
|
+
|
|
239
|
+
public string ConnectionString { get; } // Property: PascalCase
|
|
240
|
+
public event EventHandler? UserCreated; // Event: PascalCase
|
|
241
|
+
|
|
242
|
+
public async Task<User?> GetByIdAsync( // Method: PascalCase
|
|
243
|
+
int userId, // Parameter: camelCase
|
|
244
|
+
CancellationToken cancellationToken)
|
|
245
|
+
{
|
|
246
|
+
var user = await _context.Users // Local: camelCase
|
|
247
|
+
.FindAsync(userId, cancellationToken);
|
|
248
|
+
return user;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
public class Repository<TEntity> // Type parameter: TPascalCase
|
|
253
|
+
where TEntity : class { }
|
|
254
|
+
|
|
255
|
+
public const string DefaultRole = "User"; // Constant: PascalCase
|
|
256
|
+
```
|