moai-adk 0.4.5__py3-none-any.whl → 0.20.1__py3-none-any.whl

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.

Potentially problematic release.


This version of moai-adk might be problematic. Click here for more details.

Files changed (433) hide show
  1. moai_adk/__init__.py +1 -1
  2. moai_adk/__main__.py +74 -1
  3. moai_adk/cli/commands/__init__.py +1 -1
  4. moai_adk/cli/commands/analyze.py +119 -0
  5. moai_adk/cli/commands/backup.py +25 -1
  6. moai_adk/cli/commands/doctor.py +31 -5
  7. moai_adk/cli/commands/improve_user_experience.py +307 -0
  8. moai_adk/cli/commands/init.py +111 -10
  9. moai_adk/cli/commands/status.py +33 -3
  10. moai_adk/cli/commands/update.py +921 -130
  11. moai_adk/cli/commands/validate_links.py +120 -0
  12. moai_adk/cli/prompts/init_prompts.py +22 -87
  13. moai_adk/core/analysis/__init__.py +9 -0
  14. moai_adk/core/analysis/session_analyzer.py +388 -0
  15. moai_adk/core/analysis/tag_chain_analyzer.py +344 -0
  16. moai_adk/core/analysis/tag_chain_repair.py +879 -0
  17. moai_adk/core/config/__init__.py +19 -0
  18. moai_adk/core/config/migration.py +235 -0
  19. moai_adk/core/git/__init__.py +1 -1
  20. moai_adk/core/git/branch.py +1 -1
  21. moai_adk/core/git/commit.py +1 -1
  22. moai_adk/core/git/manager.py +1 -1
  23. moai_adk/core/issue_creator.py +313 -0
  24. moai_adk/core/mcp/setup.py +56 -0
  25. moai_adk/core/mcp/setup_old.py +296 -0
  26. moai_adk/core/project/backup_utils.py +1 -1
  27. moai_adk/core/project/checker.py +2 -2
  28. moai_adk/core/project/detector.py +211 -12
  29. moai_adk/core/project/initializer.py +85 -15
  30. moai_adk/core/project/phase_executor.py +76 -13
  31. moai_adk/core/project/validator.py +13 -13
  32. moai_adk/core/quality/__init__.py +1 -1
  33. moai_adk/core/quality/trust_checker.py +1 -1
  34. moai_adk/core/quality/validators/__init__.py +1 -1
  35. moai_adk/core/quality/validators/base_validator.py +1 -1
  36. moai_adk/core/tags/__init__.py +86 -0
  37. moai_adk/core/tags/auto_corrector.py +693 -0
  38. moai_adk/core/tags/ci_validator.py +463 -0
  39. moai_adk/core/tags/cli.py +283 -0
  40. moai_adk/core/tags/generator.py +109 -0
  41. moai_adk/core/tags/inserter.py +99 -0
  42. moai_adk/core/tags/mapper.py +126 -0
  43. moai_adk/core/tags/parser.py +76 -0
  44. moai_adk/core/tags/policy_validator.py +580 -0
  45. moai_adk/core/tags/pre_commit_validator.py +421 -0
  46. moai_adk/core/tags/reporter.py +956 -0
  47. moai_adk/core/tags/rollback_manager.py +525 -0
  48. moai_adk/core/tags/tags.py +149 -0
  49. moai_adk/core/tags/validator.py +897 -0
  50. moai_adk/core/template/__init__.py +1 -1
  51. moai_adk/core/template/backup.py +1 -1
  52. moai_adk/core/template/merger.py +50 -1
  53. moai_adk/core/template/processor.py +119 -13
  54. moai_adk/core/template_engine.py +268 -0
  55. moai_adk/templates/.claude/agents/alfred/backend-expert.md +348 -0
  56. moai_adk/templates/.claude/agents/alfred/cc-manager.md +209 -944
  57. moai_adk/templates/.claude/agents/alfred/database-expert.md +352 -0
  58. moai_adk/templates/.claude/agents/alfred/debug-helper.md +34 -5
  59. moai_adk/templates/.claude/agents/alfred/devops-expert.md +464 -0
  60. moai_adk/templates/.claude/agents/alfred/doc-syncer.md +38 -8
  61. moai_adk/templates/.claude/agents/alfred/format-expert.md +469 -0
  62. moai_adk/templates/.claude/agents/alfred/frontend-expert.md +357 -0
  63. moai_adk/templates/.claude/agents/alfred/git-manager.md +128 -9
  64. moai_adk/templates/.claude/agents/alfred/implementation-planner.md +104 -6
  65. moai_adk/templates/.claude/agents/alfred/project-manager.md +88 -16
  66. moai_adk/templates/.claude/agents/alfred/quality-gate.md +36 -9
  67. moai_adk/templates/.claude/agents/alfred/security-expert.md +270 -0
  68. moai_adk/templates/.claude/agents/alfred/skill-factory.md +865 -0
  69. moai_adk/templates/.claude/agents/alfred/spec-builder.md +214 -43
  70. moai_adk/templates/.claude/agents/alfred/tag-agent.md +111 -9
  71. moai_adk/templates/.claude/agents/alfred/tdd-implementer.md +309 -160
  72. moai_adk/templates/.claude/agents/alfred/trust-checker.md +36 -7
  73. moai_adk/templates/.claude/agents/alfred/ui-ux-expert.md +605 -0
  74. moai_adk/templates/.claude/commands/alfred/0-project.md +393 -966
  75. moai_adk/templates/.claude/commands/alfred/1-plan.md +651 -367
  76. moai_adk/templates/.claude/commands/alfred/2-run.md +388 -241
  77. moai_adk/templates/.claude/commands/alfred/3-sync.md +1921 -410
  78. moai_adk/templates/.claude/commands/alfred/9-feedback.md +153 -0
  79. moai_adk/templates/.claude/commands/alfred/release-new.md +3604 -0
  80. moai_adk/templates/.claude/hooks/alfred/core/project.py +484 -20
  81. moai_adk/templates/.claude/hooks/alfred/core/timeout.py +136 -0
  82. moai_adk/templates/.claude/hooks/alfred/core/ttl_cache.py +108 -0
  83. moai_adk/templates/.claude/hooks/alfred/core/version_cache.py +198 -0
  84. moai_adk/templates/.claude/hooks/alfred/handlers/__init__.py +14 -6
  85. moai_adk/templates/.claude/hooks/alfred/post_tool__enable_streaming_ui.py +50 -0
  86. moai_adk/templates/.claude/hooks/alfred/post_tool__log_changes.py +93 -0
  87. moai_adk/templates/.claude/hooks/alfred/post_tool__tag_auto_corrector.py +407 -0
  88. moai_adk/templates/.claude/hooks/alfred/pre_tool__auto_checkpoint.py +99 -0
  89. moai_adk/templates/.claude/hooks/alfred/pre_tool__realtime_tag_monitor.py +335 -0
  90. moai_adk/templates/.claude/hooks/alfred/pre_tool__tag_policy_validator.py +325 -0
  91. moai_adk/templates/.claude/hooks/alfred/session_end__cleanup.py +93 -0
  92. moai_adk/templates/.claude/hooks/alfred/session_start__auto_cleanup.py +580 -0
  93. moai_adk/templates/.claude/hooks/alfred/session_start__show_project_info.py +298 -0
  94. moai_adk/templates/.claude/hooks/alfred/shared/core/__init__.py +170 -0
  95. moai_adk/templates/.claude/hooks/alfred/{core → shared/core}/checkpoint.py +3 -3
  96. moai_adk/templates/.claude/hooks/alfred/{core → shared/core}/context.py +5 -5
  97. moai_adk/templates/.claude/hooks/alfred/shared/core/project.py +749 -0
  98. moai_adk/templates/.claude/hooks/alfred/shared/core/tags.py +230 -0
  99. moai_adk/templates/.claude/hooks/alfred/shared/core/version_cache.py +198 -0
  100. moai_adk/templates/.claude/hooks/alfred/shared/handlers/__init__.py +21 -0
  101. moai_adk/templates/.claude/hooks/alfred/shared/handlers/daily_analysis.py +351 -0
  102. moai_adk/templates/.claude/hooks/alfred/shared/handlers/notification.py +154 -0
  103. moai_adk/templates/.claude/hooks/alfred/shared/handlers/session.py +174 -0
  104. moai_adk/templates/.claude/hooks/alfred/shared/handlers/tool.py +87 -0
  105. moai_adk/templates/.claude/hooks/alfred/shared/handlers/user.py +61 -0
  106. moai_adk/templates/.claude/hooks/alfred/user_prompt__jit_load_docs.py +111 -0
  107. moai_adk/templates/.claude/hooks/alfred/utils/__init__.py +1 -0
  108. moai_adk/templates/.claude/hooks/alfred/utils/hook_config.py +94 -0
  109. moai_adk/templates/.claude/hooks/alfred/utils/timeout.py +161 -0
  110. moai_adk/templates/.claude/output-styles/alfred/alfred-moai-adk-beginner.md +267 -0
  111. moai_adk/templates/.claude/output-styles/alfred/keating-personal-tutor.md +440 -0
  112. moai_adk/templates/.claude/output-styles/alfred/r2d2-agentic-coding.md +583 -0
  113. moai_adk/templates/.claude/settings.json +96 -14
  114. moai_adk/templates/.claude/skills/moai-alfred-agent-guide/SKILL.md +70 -0
  115. moai_adk/templates/.claude/skills/moai-alfred-agent-guide/examples.md +62 -0
  116. moai_adk/templates/.claude/skills/moai-alfred-agent-guide/reference.md +242 -0
  117. moai_adk/templates/.claude/skills/moai-alfred-ask-user-questions/SKILL.md +237 -0
  118. moai_adk/templates/.claude/skills/moai-alfred-ask-user-questions/examples.md +871 -0
  119. moai_adk/templates/.claude/skills/moai-alfred-ask-user-questions/reference.md +653 -0
  120. moai_adk/templates/.claude/skills/moai-alfred-clone-pattern/README.md +162 -0
  121. moai_adk/templates/.claude/skills/moai-alfred-clone-pattern/SKILL.md +227 -0
  122. moai_adk/templates/.claude/skills/moai-alfred-clone-pattern/examples.md +354 -0
  123. moai_adk/templates/.claude/skills/moai-alfred-clone-pattern/reference.md +158 -0
  124. moai_adk/templates/.claude/skills/moai-alfred-code-reviewer/SKILL.md +179 -79
  125. moai_adk/templates/.claude/skills/moai-alfred-code-reviewer/examples.md +117 -0
  126. moai_adk/templates/.claude/skills/moai-alfred-code-reviewer/scripts/pre-review-check.sh +62 -0
  127. moai_adk/templates/.claude/skills/moai-alfred-config-schema/SKILL.md +132 -0
  128. moai_adk/templates/.claude/skills/moai-alfred-config-schema/examples.md +28 -0
  129. moai_adk/templates/.claude/skills/moai-alfred-config-schema/reference.md +444 -0
  130. moai_adk/templates/.claude/skills/moai-alfred-context-budget/SKILL.md +62 -0
  131. moai_adk/templates/.claude/skills/moai-alfred-context-budget/examples.md +28 -0
  132. moai_adk/templates/.claude/skills/moai-alfred-context-budget/reference.md +405 -0
  133. moai_adk/templates/.claude/skills/moai-alfred-dev-guide/SKILL.md +51 -0
  134. moai_adk/templates/.claude/skills/moai-alfred-dev-guide/examples.md +355 -0
  135. moai_adk/templates/.claude/skills/moai-alfred-dev-guide/reference.md +239 -0
  136. moai_adk/templates/.claude/skills/moai-alfred-expertise-detection/SKILL.md +323 -0
  137. moai_adk/templates/.claude/skills/moai-alfred-expertise-detection/examples.md +286 -0
  138. moai_adk/templates/.claude/skills/moai-alfred-expertise-detection/reference.md +126 -0
  139. moai_adk/templates/.claude/skills/moai-alfred-issue-labels/SKILL.md +229 -0
  140. moai_adk/templates/.claude/skills/moai-alfred-issue-labels/examples.md +4 -0
  141. moai_adk/templates/.claude/skills/moai-alfred-issue-labels/reference.md +150 -0
  142. moai_adk/templates/.claude/skills/moai-alfred-language-detection/SKILL.md +87 -73
  143. moai_adk/templates/.claude/skills/moai-alfred-language-detection/examples.md +29 -0
  144. moai_adk/templates/.claude/skills/moai-alfred-language-detection/reference.md +28 -0
  145. moai_adk/templates/.claude/skills/moai-alfred-personas/README.md +42 -0
  146. moai_adk/templates/.claude/skills/moai-alfred-personas/SKILL.md +429 -0
  147. moai_adk/templates/.claude/skills/moai-alfred-personas/examples.md +520 -0
  148. moai_adk/templates/.claude/skills/moai-alfred-personas/reference.md +405 -0
  149. moai_adk/templates/.claude/skills/moai-alfred-practices/SKILL.md +89 -0
  150. moai_adk/templates/.claude/skills/moai-alfred-practices/examples.md +122 -0
  151. moai_adk/templates/.claude/skills/moai-alfred-practices/reference.md +369 -0
  152. moai_adk/templates/.claude/skills/moai-alfred-proactive-suggestions/SKILL.md +508 -0
  153. moai_adk/templates/.claude/skills/moai-alfred-proactive-suggestions/examples.md +481 -0
  154. moai_adk/templates/.claude/skills/moai-alfred-proactive-suggestions/reference.md +100 -0
  155. moai_adk/templates/.claude/skills/moai-alfred-rules/SKILL.md +77 -0
  156. moai_adk/templates/.claude/skills/moai-alfred-rules/examples.md +265 -0
  157. moai_adk/templates/.claude/skills/moai-alfred-rules/reference.md +539 -0
  158. moai_adk/templates/.claude/skills/moai-alfred-session-state/SKILL.md +320 -0
  159. moai_adk/templates/.claude/skills/moai-alfred-session-state/examples.md +4 -0
  160. moai_adk/templates/.claude/skills/moai-alfred-session-state/reference.md +84 -0
  161. moai_adk/templates/.claude/skills/moai-alfred-spec-authoring/README.md +137 -0
  162. moai_adk/templates/.claude/skills/moai-alfred-spec-authoring/SKILL.md +219 -0
  163. moai_adk/templates/.claude/skills/moai-alfred-spec-authoring/examples/validate-spec.sh +161 -0
  164. moai_adk/templates/.claude/skills/moai-alfred-spec-authoring/examples.md +541 -0
  165. moai_adk/templates/.claude/skills/moai-alfred-spec-authoring/reference.md +622 -0
  166. moai_adk/templates/.claude/skills/moai-alfred-todowrite-pattern/SKILL.md +19 -0
  167. moai_adk/templates/.claude/skills/moai-alfred-todowrite-pattern/examples.md +4 -0
  168. moai_adk/templates/.claude/skills/moai-alfred-todowrite-pattern/reference.md +211 -0
  169. moai_adk/templates/.claude/skills/moai-alfred-workflow/SKILL.md +288 -0
  170. moai_adk/templates/.claude/skills/moai-cc-agents/SKILL.md +269 -0
  171. moai_adk/templates/.claude/skills/moai-cc-agents/templates/agent-template.md +32 -0
  172. moai_adk/templates/.claude/skills/moai-cc-claude-md/SKILL.md +298 -0
  173. moai_adk/templates/.claude/skills/moai-cc-claude-md/templates/CLAUDE-template.md +26 -0
  174. moai_adk/templates/.claude/skills/moai-cc-commands/SKILL.md +307 -0
  175. moai_adk/templates/.claude/skills/moai-cc-commands/templates/command-template.md +21 -0
  176. moai_adk/templates/.claude/skills/moai-cc-hooks/SKILL.md +252 -0
  177. moai_adk/templates/.claude/skills/moai-cc-hooks/scripts/pre-bash-check.sh +19 -0
  178. moai_adk/templates/.claude/skills/moai-cc-hooks/scripts/preserve-permissions.sh +19 -0
  179. moai_adk/templates/.claude/skills/moai-cc-hooks/scripts/validate-bash-command.py +24 -0
  180. moai_adk/templates/.claude/skills/moai-cc-mcp-plugins/SKILL.md +199 -0
  181. moai_adk/templates/.claude/skills/moai-cc-mcp-plugins/templates/settings-mcp-template.json +39 -0
  182. moai_adk/templates/.claude/skills/moai-cc-memory/SKILL.md +316 -0
  183. moai_adk/templates/.claude/skills/moai-cc-memory/templates/session-summary-template.md +18 -0
  184. moai_adk/templates/.claude/skills/moai-cc-settings/SKILL.md +263 -0
  185. moai_adk/templates/.claude/skills/moai-cc-settings/templates/settings-complete-template.json +30 -0
  186. moai_adk/templates/.claude/skills/moai-cc-skill-factory/CHECKLIST.md +482 -0
  187. moai_adk/templates/.claude/skills/moai-cc-skill-factory/EXAMPLES.md +303 -0
  188. moai_adk/templates/.claude/skills/moai-cc-skill-factory/INTERACTIVE-DISCOVERY.md +524 -0
  189. moai_adk/templates/.claude/skills/moai-cc-skill-factory/METADATA.md +477 -0
  190. moai_adk/templates/.claude/skills/moai-cc-skill-factory/PARALLEL-ANALYSIS-REPORT.md +429 -0
  191. moai_adk/templates/.claude/skills/moai-cc-skill-factory/PYTHON-VERSION-MATRIX.md +391 -0
  192. moai_adk/templates/.claude/skills/moai-cc-skill-factory/SKILL-FACTORY-WORKFLOW.md +431 -0
  193. moai_adk/templates/.claude/skills/moai-cc-skill-factory/SKILL-UPDATE-ADVISOR.md +577 -0
  194. moai_adk/templates/.claude/skills/moai-cc-skill-factory/SKILL.md +273 -0
  195. moai_adk/templates/.claude/skills/moai-cc-skill-factory/STEP-BY-STEP-GUIDE.md +466 -0
  196. moai_adk/templates/.claude/skills/moai-cc-skill-factory/STRUCTURE.md +583 -0
  197. moai_adk/templates/.claude/skills/moai-cc-skill-factory/WEB-RESEARCH.md +526 -0
  198. moai_adk/templates/.claude/skills/moai-cc-skill-factory/reference.md +608 -0
  199. moai_adk/templates/.claude/skills/moai-cc-skill-factory/scripts/generate-structure.sh +328 -0
  200. moai_adk/templates/.claude/skills/moai-cc-skill-factory/scripts/validate-skill.sh +312 -0
  201. moai_adk/templates/.claude/skills/moai-cc-skill-factory/templates/SKILL_TEMPLATE.md +245 -0
  202. moai_adk/templates/.claude/skills/moai-cc-skill-factory/templates/examples-template.md +285 -0
  203. moai_adk/templates/.claude/skills/moai-cc-skill-factory/templates/reference-template.md +278 -0
  204. moai_adk/templates/.claude/skills/moai-cc-skill-factory/templates/scripts-template.sh +303 -0
  205. moai_adk/templates/.claude/skills/moai-cc-skills/SKILL.md +291 -0
  206. moai_adk/templates/.claude/skills/moai-cc-skills/templates/SKILL-template.md +15 -0
  207. moai_adk/templates/.claude/skills/moai-change-logger/SKILL.md +563 -0
  208. moai_adk/templates/.claude/skills/moai-design-systems/SKILL.md +802 -0
  209. moai_adk/templates/.claude/skills/moai-design-systems/examples.md +1238 -0
  210. moai_adk/templates/.claude/skills/moai-design-systems/reference.md +673 -0
  211. moai_adk/templates/.claude/skills/moai-domain-backend/SKILL.md +234 -43
  212. moai_adk/templates/.claude/skills/moai-domain-backend/examples.md +1633 -0
  213. moai_adk/templates/.claude/skills/moai-domain-backend/reference.md +660 -0
  214. moai_adk/templates/.claude/skills/moai-domain-cli-tool/SKILL.md +97 -69
  215. moai_adk/templates/.claude/skills/moai-domain-cli-tool/examples.md +29 -0
  216. moai_adk/templates/.claude/skills/moai-domain-cli-tool/reference.md +30 -0
  217. moai_adk/templates/.claude/skills/moai-domain-data-science/SKILL.md +97 -72
  218. moai_adk/templates/.claude/skills/moai-domain-data-science/examples.md +29 -0
  219. moai_adk/templates/.claude/skills/moai-domain-data-science/reference.md +30 -0
  220. moai_adk/templates/.claude/skills/moai-domain-database/SKILL.md +97 -74
  221. moai_adk/templates/.claude/skills/moai-domain-database/examples.md +29 -0
  222. moai_adk/templates/.claude/skills/moai-domain-database/reference.md +30 -0
  223. moai_adk/templates/.claude/skills/moai-domain-devops/SKILL.md +98 -74
  224. moai_adk/templates/.claude/skills/moai-domain-devops/examples.md +29 -0
  225. moai_adk/templates/.claude/skills/moai-domain-devops/reference.md +31 -0
  226. moai_adk/templates/.claude/skills/moai-domain-frontend/SKILL.md +102 -73
  227. moai_adk/templates/.claude/skills/moai-domain-frontend/examples.md +29 -0
  228. moai_adk/templates/.claude/skills/moai-domain-frontend/reference.md +31 -0
  229. moai_adk/templates/.claude/skills/moai-domain-ml/SKILL.md +97 -73
  230. moai_adk/templates/.claude/skills/moai-domain-ml/examples.md +29 -0
  231. moai_adk/templates/.claude/skills/moai-domain-ml/reference.md +30 -0
  232. moai_adk/templates/.claude/skills/moai-domain-mobile-app/SKILL.md +97 -67
  233. moai_adk/templates/.claude/skills/moai-domain-mobile-app/examples.md +29 -0
  234. moai_adk/templates/.claude/skills/moai-domain-mobile-app/reference.md +30 -0
  235. moai_adk/templates/.claude/skills/moai-domain-security/SKILL.md +97 -79
  236. moai_adk/templates/.claude/skills/moai-domain-security/examples.md +29 -0
  237. moai_adk/templates/.claude/skills/moai-domain-security/reference.md +30 -0
  238. moai_adk/templates/.claude/skills/moai-domain-web-api/SKILL.md +97 -71
  239. moai_adk/templates/.claude/skills/moai-domain-web-api/examples.md +29 -0
  240. moai_adk/templates/.claude/skills/moai-domain-web-api/reference.md +30 -0
  241. moai_adk/templates/.claude/skills/moai-essentials-debug/SKILL.md +265 -64
  242. moai_adk/templates/.claude/skills/moai-essentials-debug/examples.md +1064 -0
  243. moai_adk/templates/.claude/skills/moai-essentials-debug/reference.md +1047 -0
  244. moai_adk/templates/.claude/skills/moai-essentials-perf/SKILL.md +87 -78
  245. moai_adk/templates/.claude/skills/moai-essentials-perf/examples.md +29 -0
  246. moai_adk/templates/.claude/skills/moai-essentials-perf/reference.md +28 -0
  247. moai_adk/templates/.claude/skills/moai-essentials-refactor/SKILL.md +87 -70
  248. moai_adk/templates/.claude/skills/moai-essentials-refactor/examples.md +29 -0
  249. moai_adk/templates/.claude/skills/moai-essentials-refactor/reference.md +28 -0
  250. moai_adk/templates/.claude/skills/moai-essentials-review/SKILL.md +87 -86
  251. moai_adk/templates/.claude/skills/moai-essentials-review/examples.md +29 -0
  252. moai_adk/templates/.claude/skills/moai-essentials-review/reference.md +28 -0
  253. moai_adk/templates/.claude/skills/moai-foundation-ears/SKILL.md +80 -62
  254. moai_adk/templates/.claude/skills/moai-foundation-ears/examples.md +29 -0
  255. moai_adk/templates/.claude/skills/moai-foundation-ears/reference.md +28 -0
  256. moai_adk/templates/.claude/skills/moai-foundation-git/SKILL.md +207 -50
  257. moai_adk/templates/.claude/skills/moai-foundation-git/examples.md +29 -0
  258. moai_adk/templates/.claude/skills/moai-foundation-git/reference.md +29 -0
  259. moai_adk/templates/.claude/skills/moai-foundation-langs/SKILL.md +90 -71
  260. moai_adk/templates/.claude/skills/moai-foundation-langs/examples.md +29 -0
  261. moai_adk/templates/.claude/skills/moai-foundation-langs/reference.md +28 -0
  262. moai_adk/templates/.claude/skills/moai-foundation-specs/SKILL.md +78 -58
  263. moai_adk/templates/.claude/skills/moai-foundation-specs/examples.md +29 -0
  264. moai_adk/templates/.claude/skills/moai-foundation-specs/reference.md +28 -0
  265. moai_adk/templates/.claude/skills/moai-foundation-tags/SKILL.md +78 -51
  266. moai_adk/templates/.claude/skills/moai-foundation-tags/examples.md +29 -0
  267. moai_adk/templates/.claude/skills/moai-foundation-tags/reference.md +28 -0
  268. moai_adk/templates/.claude/skills/moai-foundation-trust/.!11330!examples.md +0 -0
  269. moai_adk/templates/.claude/skills/moai-foundation-trust/SKILL.md +253 -32
  270. moai_adk/templates/.claude/skills/moai-foundation-trust/examples.md +0 -0
  271. moai_adk/templates/.claude/skills/moai-foundation-trust/reference.md +1099 -0
  272. moai_adk/templates/.claude/skills/moai-jit-docs-enhanced/SKILL.md +460 -0
  273. moai_adk/templates/.claude/skills/moai-lang-c/SKILL.md +98 -74
  274. moai_adk/templates/.claude/skills/moai-lang-c/examples.md +29 -0
  275. moai_adk/templates/.claude/skills/moai-lang-c/reference.md +31 -0
  276. moai_adk/templates/.claude/skills/moai-lang-cpp/SKILL.md +98 -76
  277. moai_adk/templates/.claude/skills/moai-lang-cpp/examples.md +29 -0
  278. moai_adk/templates/.claude/skills/moai-lang-cpp/reference.md +31 -0
  279. moai_adk/templates/.claude/skills/moai-lang-csharp/SKILL.md +2358 -70
  280. moai_adk/templates/.claude/skills/moai-lang-csharp/examples.md +29 -0
  281. moai_adk/templates/.claude/skills/moai-lang-csharp/reference.md +30 -0
  282. moai_adk/templates/.claude/skills/moai-lang-dart/SKILL.md +2962 -68
  283. moai_adk/templates/.claude/skills/moai-lang-dart/examples.md +29 -0
  284. moai_adk/templates/.claude/skills/moai-lang-dart/reference.md +30 -0
  285. moai_adk/templates/.claude/skills/moai-lang-go/SKILL.md +1898 -70
  286. moai_adk/templates/.claude/skills/moai-lang-go/examples.md +29 -0
  287. moai_adk/templates/.claude/skills/moai-lang-go/reference.md +31 -0
  288. moai_adk/templates/.claude/skills/moai-lang-java/SKILL.md +1465 -68
  289. moai_adk/templates/.claude/skills/moai-lang-java/examples.md +29 -0
  290. moai_adk/templates/.claude/skills/moai-lang-java/reference.md +31 -0
  291. moai_adk/templates/.claude/skills/moai-lang-javascript/SKILL.md +2364 -66
  292. moai_adk/templates/.claude/skills/moai-lang-javascript/examples.md +29 -0
  293. moai_adk/templates/.claude/skills/moai-lang-javascript/reference.md +32 -0
  294. moai_adk/templates/.claude/skills/moai-lang-kotlin/SKILL.md +1630 -69
  295. moai_adk/templates/.claude/skills/moai-lang-kotlin/examples.md +29 -0
  296. moai_adk/templates/.claude/skills/moai-lang-kotlin/reference.md +31 -0
  297. moai_adk/templates/.claude/skills/moai-lang-php/SKILL.md +89 -61
  298. moai_adk/templates/.claude/skills/moai-lang-php/examples.md +29 -0
  299. moai_adk/templates/.claude/skills/moai-lang-php/reference.md +30 -0
  300. moai_adk/templates/.claude/skills/moai-lang-python/SKILL.md +735 -66
  301. moai_adk/templates/.claude/skills/moai-lang-python/examples.md +624 -0
  302. moai_adk/templates/.claude/skills/moai-lang-python/reference.md +316 -0
  303. moai_adk/templates/.claude/skills/moai-lang-r/SKILL.md +97 -73
  304. moai_adk/templates/.claude/skills/moai-lang-r/examples.md +29 -0
  305. moai_adk/templates/.claude/skills/moai-lang-r/reference.md +30 -0
  306. moai_adk/templates/.claude/skills/moai-lang-ruby/SKILL.md +98 -73
  307. moai_adk/templates/.claude/skills/moai-lang-ruby/examples.md +29 -0
  308. moai_adk/templates/.claude/skills/moai-lang-ruby/reference.md +31 -0
  309. moai_adk/templates/.claude/skills/moai-lang-rust/SKILL.md +1834 -70
  310. moai_adk/templates/.claude/skills/moai-lang-rust/examples.md +29 -0
  311. moai_adk/templates/.claude/skills/moai-lang-rust/reference.md +31 -0
  312. moai_adk/templates/.claude/skills/moai-lang-scala/SKILL.md +99 -74
  313. moai_adk/templates/.claude/skills/moai-lang-scala/examples.md +29 -0
  314. moai_adk/templates/.claude/skills/moai-lang-scala/reference.md +30 -0
  315. moai_adk/templates/.claude/skills/moai-lang-shell/SKILL.md +97 -74
  316. moai_adk/templates/.claude/skills/moai-lang-shell/examples.md +29 -0
  317. moai_adk/templates/.claude/skills/moai-lang-shell/reference.md +30 -0
  318. moai_adk/templates/.claude/skills/moai-lang-sql/SKILL.md +98 -74
  319. moai_adk/templates/.claude/skills/moai-lang-sql/examples.md +29 -0
  320. moai_adk/templates/.claude/skills/moai-lang-sql/reference.md +31 -0
  321. moai_adk/templates/.claude/skills/moai-lang-swift/SKILL.md +1959 -69
  322. moai_adk/templates/.claude/skills/moai-lang-swift/examples.md +29 -0
  323. moai_adk/templates/.claude/skills/moai-lang-swift/reference.md +30 -0
  324. moai_adk/templates/.claude/skills/moai-lang-template/SKILL.md +348 -0
  325. moai_adk/templates/.claude/skills/moai-lang-template/VARIABLES.md +98 -0
  326. moai_adk/templates/.claude/skills/moai-lang-typescript/SKILL.md +1230 -66
  327. moai_adk/templates/.claude/skills/moai-lang-typescript/examples.md +29 -0
  328. moai_adk/templates/.claude/skills/moai-lang-typescript/reference.md +34 -0
  329. moai_adk/templates/.claude/skills/moai-learning-optimizer/SKILL.md +575 -0
  330. moai_adk/templates/.claude/skills/moai-project-batch-questions/README.md +50 -0
  331. moai_adk/templates/.claude/skills/moai-project-batch-questions/SKILL.md +304 -0
  332. moai_adk/templates/.claude/skills/moai-project-batch-questions/examples.md +417 -0
  333. moai_adk/templates/.claude/skills/moai-project-batch-questions/reference.md +704 -0
  334. moai_adk/templates/.claude/skills/moai-project-config-manager/README.md +87 -0
  335. moai_adk/templates/.claude/skills/moai-project-config-manager/SKILL.md +552 -0
  336. moai_adk/templates/.claude/skills/moai-project-config-manager/examples.md +1109 -0
  337. moai_adk/templates/.claude/skills/moai-project-config-manager/reference.md +514 -0
  338. moai_adk/templates/.claude/skills/moai-project-config-manager/validate.py +106 -0
  339. moai_adk/templates/.claude/skills/moai-project-documentation/README.md +11 -0
  340. moai_adk/templates/.claude/skills/moai-project-documentation/SKILL.md +622 -0
  341. moai_adk/templates/.claude/skills/moai-project-documentation/examples.md +20 -0
  342. moai_adk/templates/.claude/skills/moai-project-documentation/reference.md +12 -0
  343. moai_adk/templates/.claude/skills/moai-project-language-initializer/README.md +152 -0
  344. moai_adk/templates/.claude/skills/moai-project-language-initializer/SKILL.md +285 -0
  345. moai_adk/templates/.claude/skills/moai-project-language-initializer/examples.md +333 -0
  346. moai_adk/templates/.claude/skills/moai-project-language-initializer/reference.md +386 -0
  347. moai_adk/templates/.claude/skills/moai-project-template-optimizer/README.md +49 -0
  348. moai_adk/templates/.claude/skills/moai-project-template-optimizer/SKILL.md +319 -0
  349. moai_adk/templates/.claude/skills/moai-project-template-optimizer/examples.md +58 -0
  350. moai_adk/templates/.claude/skills/moai-project-template-optimizer/reference.md +123 -0
  351. moai_adk/templates/.claude/skills/moai-session-info/SKILL.md +314 -0
  352. moai_adk/templates/.claude/skills/moai-streaming-ui/SKILL.md +552 -0
  353. moai_adk/templates/.claude/skills/moai-tag-policy-validator/SKILL.md +570 -0
  354. moai_adk/templates/.git-hooks/pre-commit +66 -0
  355. moai_adk/templates/.git-hooks/pre-push +255 -0
  356. moai_adk/templates/.github/workflows/c-tag-validation.yml +11 -0
  357. moai_adk/templates/.github/workflows/cpp-tag-validation.yml +11 -0
  358. moai_adk/templates/.github/workflows/csharp-tag-validation.yml +11 -0
  359. moai_adk/templates/.github/workflows/dart-tag-validation.yml +11 -0
  360. moai_adk/templates/.github/workflows/go-tag-validation.yml +130 -0
  361. moai_adk/templates/.github/workflows/java-tag-validation.yml +11 -0
  362. moai_adk/templates/.github/workflows/javascript-tag-validation.yml +135 -0
  363. moai_adk/templates/.github/workflows/kotlin-tag-validation.yml +11 -0
  364. moai_adk/templates/.github/workflows/moai-gitflow.yml +166 -3
  365. moai_adk/templates/.github/workflows/moai-release-create.yml +100 -0
  366. moai_adk/templates/.github/workflows/moai-release-pipeline.yml +188 -0
  367. moai_adk/templates/.github/workflows/php-tag-validation.yml +11 -0
  368. moai_adk/templates/.github/workflows/python-tag-validation.yml +118 -0
  369. moai_adk/templates/.github/workflows/release.yml +118 -0
  370. moai_adk/templates/.github/workflows/ruby-tag-validation.yml +11 -0
  371. moai_adk/templates/.github/workflows/rust-tag-validation.yml +11 -0
  372. moai_adk/templates/.github/workflows/shell-tag-validation.yml +11 -0
  373. moai_adk/templates/.github/workflows/spec-issue-sync.yml +338 -0
  374. moai_adk/templates/.github/workflows/swift-tag-validation.yml +11 -0
  375. moai_adk/templates/.github/workflows/tag-report.yml +269 -0
  376. moai_adk/templates/.github/workflows/tag-validation.yml +186 -0
  377. moai_adk/templates/.github/workflows/typescript-tag-validation.yml +154 -0
  378. moai_adk/templates/.mcp.json +31 -0
  379. moai_adk/templates/.moai/config.json +80 -7
  380. moai_adk/templates/CLAUDE.md +562 -546
  381. moai_adk/utils/banner.py +5 -5
  382. moai_adk/utils/common.py +294 -0
  383. moai_adk/utils/link_validator.py +235 -0
  384. moai_adk/utils/logger.py +8 -8
  385. moai_adk/utils/user_experience.py +451 -0
  386. moai_adk-0.20.1.dist-info/METADATA +233 -0
  387. moai_adk-0.20.1.dist-info/RECORD +404 -0
  388. moai_adk/templates/.claude/hooks/alfred/README.md +0 -230
  389. moai_adk/templates/.claude/hooks/alfred/alfred_hooks.py +0 -156
  390. moai_adk/templates/.claude/hooks/alfred/core/__init__.py +0 -85
  391. moai_adk/templates/.claude/hooks/alfred/handlers/notification.py +0 -25
  392. moai_adk/templates/.claude/hooks/alfred/handlers/session.py +0 -92
  393. moai_adk/templates/.claude/hooks/alfred/handlers/tool.py +0 -70
  394. moai_adk/templates/.claude/hooks/alfred/handlers/user.py +0 -41
  395. moai_adk/templates/.claude/output-styles/alfred/agentic-coding.md +0 -636
  396. moai_adk/templates/.claude/output-styles/alfred/moai-adk-learning.md +0 -692
  397. moai_adk/templates/.claude/output-styles/alfred/study-with-alfred.md +0 -470
  398. moai_adk/templates/.claude/skills/moai-alfred-debugger-pro/SKILL.md +0 -103
  399. moai_adk/templates/.claude/skills/moai-alfred-ears-authoring/SKILL.md +0 -103
  400. moai_adk/templates/.claude/skills/moai-alfred-git-workflow/SKILL.md +0 -95
  401. moai_adk/templates/.claude/skills/moai-alfred-performance-optimizer/SKILL.md +0 -105
  402. moai_adk/templates/.claude/skills/moai-alfred-refactoring-coach/SKILL.md +0 -97
  403. moai_adk/templates/.claude/skills/moai-alfred-spec-metadata-validation/SKILL.md +0 -97
  404. moai_adk/templates/.claude/skills/moai-alfred-tag-scanning/SKILL.md +0 -90
  405. moai_adk/templates/.claude/skills/moai-alfred-trust-validation/SKILL.md +0 -99
  406. moai_adk/templates/.claude/skills/moai-alfred-tui-survey/SKILL.md +0 -87
  407. moai_adk/templates/.claude/skills/moai-alfred-tui-survey/examples.md +0 -62
  408. moai_adk/templates/.claude/skills/moai-claude-code/SKILL.md +0 -94
  409. moai_adk/templates/.claude/skills/moai-claude-code/examples.md +0 -513
  410. moai_adk/templates/.claude/skills/moai-claude-code/reference.md +0 -433
  411. moai_adk/templates/.claude/skills/moai-claude-code/templates/agent-full.md +0 -332
  412. moai_adk/templates/.claude/skills/moai-claude-code/templates/command-full.md +0 -384
  413. moai_adk/templates/.claude/skills/moai-claude-code/templates/plugin-full.json +0 -363
  414. moai_adk/templates/.claude/skills/moai-claude-code/templates/settings-full.json +0 -595
  415. moai_adk/templates/.claude/skills/moai-claude-code/templates/skill-full.md +0 -496
  416. moai_adk/templates/.claude/skills/moai-lang-clojure/SKILL.md +0 -100
  417. moai_adk/templates/.claude/skills/moai-lang-elixir/SKILL.md +0 -99
  418. moai_adk/templates/.claude/skills/moai-lang-haskell/SKILL.md +0 -100
  419. moai_adk/templates/.claude/skills/moai-lang-julia/SKILL.md +0 -98
  420. moai_adk/templates/.claude/skills/moai-lang-lua/SKILL.md +0 -98
  421. moai_adk/templates/.github/PULL_REQUEST_TEMPLATE.md +0 -69
  422. moai_adk/templates/.moai/memory/development-guide.md +0 -344
  423. moai_adk/templates/.moai/memory/gitflow-protection-policy.md +0 -220
  424. moai_adk/templates/.moai/memory/spec-metadata.md +0 -356
  425. moai_adk/templates/.moai/project/product.md +0 -161
  426. moai_adk/templates/.moai/project/structure.md +0 -156
  427. moai_adk/templates/.moai/project/tech.md +0 -227
  428. moai_adk/templates/__init__.py +0 -2
  429. moai_adk-0.4.5.dist-info/METADATA +0 -369
  430. moai_adk-0.4.5.dist-info/RECORD +0 -152
  431. {moai_adk-0.4.5.dist-info → moai_adk-0.20.1.dist-info}/WHEEL +0 -0
  432. {moai_adk-0.4.5.dist-info → moai_adk-0.20.1.dist-info}/entry_points.txt +0 -0
  433. {moai_adk-0.4.5.dist-info → moai_adk-0.20.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,100 +1,2388 @@
1
1
  ---
2
-
3
2
  name: moai-lang-csharp
4
- description: C# best practices with xUnit, .NET tooling, LINQ, and async/await patterns. Use when writing or reviewing C# code in project workflows.
3
+ version: 2.0.0
4
+ created: 2025-11-06
5
+ updated: 2025-11-06
6
+ status: active
7
+ description: "C# best practices with .NET 8, ASP.NET Core, Entity Framework, and modern async programming for 2025"
8
+ keywords: [csharp, programming, dotnet, aspnetcore, entityframework, backend, async]
5
9
  allowed-tools:
6
10
  - Read
11
+ - Write
12
+ - Edit
7
13
  - Bash
14
+ - WebFetch
15
+ - WebSearch
8
16
  ---
9
17
 
10
- # C# Expert
18
+ # C# Development Mastery
19
+
20
+ **Modern C# Development with 2025 Best Practices**
21
+
22
+ > Comprehensive C# development guidance covering .NET 8 applications, ASP.NET Core APIs, Entity Framework Core with modern async/await patterns, and cross-platform development using the latest tools and frameworks.
23
+
24
+ ## What It Does
25
+
26
+ ### Backend Development
27
+ - **Web API Development**: ASP.NET Core with minimal APIs, controllers, and modern routing
28
+ - **Database Integration**: Entity Framework Core with LINQ, migrations, and performance optimization
29
+ - **Microservices**: gRPC, message queuing, distributed systems patterns
30
+ - **Real-time Communication**: SignalR, WebSockets with async/await
31
+ - **Testing**: xUnit, Moq, FluentAssertions with integration testing
11
32
 
12
- ## Skill Metadata
13
- | Field | Value |
14
- | ----- | ----- |
15
- | Allowed tools | Read (read_file), Bash (terminal) |
16
- | Auto-load | On demand when language keywords are detected |
17
- | Trigger cues | C# code discussions, framework guidance, or file extensions such as .cs. |
18
- | Tier | 3 |
33
+ ### Cross-Platform Development
34
+ - **Desktop Applications**: WPF, MAUI, WinUI 3 for Windows and cross-platform
35
+ - **Mobile Applications**: .NET MAUI for iOS, Android, Windows
36
+ - **Console Applications**: Modern CLI apps with DI and configuration
37
+ - **Background Services**: .NET Core Workers, Hosted Services
19
38
 
20
- ## What it does
39
+ ### Cloud Integration
40
+ - **Azure Integration**: Azure Functions, App Service, Blob Storage
41
+ - **Docker & Kubernetes**: Containerization and orchestration
42
+ - **DevOps**: CI/CD pipelines, health checks, monitoring
43
+ - **Performance**: Profiling, optimization, memory management
21
44
 
22
- Provides C#-specific expertise for TDD development, including xUnit testing, .NET CLI tooling, LINQ query expressions, and async/await patterns.
45
+ ## When to Use
23
46
 
24
- ## When to use
47
+ ### Perfect Scenarios
48
+ - **Building REST APIs and microservices with ASP.NET Core**
49
+ - **Developing enterprise applications with Entity Framework Core**
50
+ - **Creating cross-platform mobile apps with .NET MAUI**
51
+ - **Implementing real-time applications with SignalR**
52
+ - **Building cloud-native applications with Azure integration**
53
+ - **Developing high-performance backend services**
54
+ - **Creating modern desktop applications with WPF/MAUI**
25
55
 
26
- - Engages when the conversation references C# work, frameworks, or files like .cs.
27
- - "Writing C# tests", "How to use xUnit", "LINQ queries"
28
- - Automatically invoked when working with .NET projects
29
- - C# SPEC implementation (`/alfred:2-run`)
56
+ ### Common Triggers
57
+ - "Create C# web API"
58
+ - "Build ASP.NET Core application"
59
+ - "Set up Entity Framework Core"
60
+ - "Implement async/await patterns"
61
+ - "Optimize C# performance"
62
+ - "Test C# application"
63
+ - "C# best practices"
30
64
 
31
- ## How it works
65
+ ## Tool Version Matrix (2025-11-06)
32
66
 
33
- **TDD Framework**:
34
- - **xUnit**: Modern .NET testing framework
35
- - **Moq**: Mocking library for interfaces
36
- - **FluentAssertions**: Expressive assertions
37
- - Test coverage ≥85% with Coverlet
67
+ ### Core .NET
68
+ - **.NET**: 8.0 (current LTS) / 9.0 Preview
69
+ - **C#**: 12.0 (current) / 13.0 Preview
70
+ - **Package Managers**: NuGet, .NET CLI
71
+ - **Runtime**: .NET 8.0 LTS
38
72
 
39
- **Build Tools**:
40
- - **.NET CLI**: dotnet build, test, run
41
- - **NuGet**: Package management
42
- - **MSBuild**: Build system
73
+ ### Web Frameworks
74
+ - **ASP.NET Core**: 8.0 - Web framework
75
+ - **Entity Framework Core**: 8.0 - ORM framework
76
+ - **Blazor**: 8.0 - Web UI framework
77
+ - **SignalR**: 8.0 - Real-time communication
78
+ - **gRPC**: 2.57.x - High-performance RPC
43
79
 
44
- **Code Quality**:
45
- - **StyleCop**: C# style checker
46
- - **SonarAnalyzer**: Static code analysis
47
- - **EditorConfig**: Code formatting rules
80
+ ### Testing Tools
81
+ - **xUnit**: 2.6.x - Testing framework
82
+ - **Moq**: 4.20.x - Mocking framework
83
+ - **FluentAssertions**: 6.12.x - Assertion library
84
+ - **Bogus**: 35.5.x - Test data generation
85
+ - **Microsoft.AspNetCore.Mvc.Testing**: 8.0 - Integration testing
48
86
 
49
- **C# Patterns**:
50
- - **LINQ**: Query expressions for collections
51
- - **Async/await**: Asynchronous programming
52
- - **Properties**: Get/set accessors
53
- - **Extension methods**: Add methods to existing types
54
- - **Nullable reference types**: Null safety (C# 8+)
87
+ ### Development Tools
88
+ - **Visual Studio 2022**: 17.10+
89
+ - **Visual Studio Code**: C# Dev Kit extension
90
+ - **Rider**: 2024.2+
91
+ - **.NET CLI**: 8.0.400+
55
92
 
56
- **Best Practices**:
57
- - File ≤300 LOC, method ≤50 LOC
58
- - Use PascalCase for public members
59
- - Prefer `var` for local variables when type is obvious
60
- - Async methods should end with "Async" suffix
61
- - Use string interpolation ($"") over concatenation
93
+ ### Database Tools
94
+ - **SQL Server**: 2022 / Azure SQL
95
+ - **PostgreSQL**: 16.x
96
+ - **SQLite**: 3.45.x
97
+ - **MongoDB**: 7.0.x (with MongoDB.Driver)
98
+
99
+ ## Ecosystem Overview
100
+
101
+ ### Package Management
62
102
 
63
- ## Examples
64
103
  ```bash
65
- dotnet test && dotnet format --verify-no-changes
104
+ # Create new projects
105
+ dotnet new webapi -n MyApi
106
+ dotnet new mvc -n MyMvcApp
107
+ dotnet new maui -n MyMauiApp
108
+ dotnet new worker -n MyWorkerService
109
+ dotnet new classlib -n MyLibrary
110
+
111
+ # Add packages
112
+ dotnet add package Microsoft.EntityFrameworkCore.SqlServer
113
+ dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
114
+ dotnet add package Swashbuckle.AspNetCore
115
+ dotnet add package xunit
116
+
117
+ # Build and run
118
+ dotnet build
119
+ dotnet run --project MyApi.csproj
120
+ dotnet test
121
+
122
+ # Global tools
123
+ dotnet tool install --global dotnet-ef
124
+ dotnet tool install --global dotnet-aspnet-codegenerator
125
+ ```
126
+
127
+ ### Project Structure (2025 Best Practice)
128
+
129
+ ```
130
+ MyDotNetSolution/
131
+ ├── src/
132
+ │ ├── MyApi/ # Web API project
133
+ │ │ ├── Controllers/ # API controllers
134
+ │ │ ├── Endpoints/ # Minimal API endpoints
135
+ │ │ ├── Services/ # Business logic services
136
+ │ │ ├── Models/ # Data models and DTOs
137
+ │ │ ├── Data/ # Data access layer
138
+ │ │ ├── Configuration/ # Configuration classes
139
+ │ │ ├── Filters/ # Action filters and middleware
140
+ │ │ └── Program.cs # Application entry point
141
+ │ ├── MyCore/ # Core business logic
142
+ │ │ ├── Entities/ # Domain entities
143
+ │ │ ├── Interfaces/ # Service interfaces
144
+ │ │ ├── ValueObjects/ # Value objects
145
+ │ │ └── Enums/ # Enumerations
146
+ │ ├── MyInfrastructure/ # Infrastructure concerns
147
+ │ │ ├── Persistence/ # Database implementations
148
+ │ │ ├── ExternalServices/ # External API clients
149
+ │ │ ├── Messaging/ # Message queue implementations
150
+ │ │ └── Caching/ # Cache implementations
151
+ │ └── MyTests/ # Test projects
152
+ │ ├── Unit/ # Unit tests
153
+ │ ├── Integration/ # Integration tests
154
+ │ └── Functional/ # Functional tests
155
+ ├── tests/ # Additional test projects
156
+ ├── docs/ # Documentation
157
+ ├── docker/ # Docker configurations
158
+ ├── .github/workflows/ # GitHub Actions
159
+ ├── Directory.Build.props # Solution-wide MSBuild properties
160
+ └── MySolution.sln # Solution file
161
+ ```
162
+
163
+ ## Modern Development Patterns
164
+
165
+ ### C# 12.0 Language Features
166
+
167
+ ```csharp
168
+ // Primary constructors for classes
169
+ public class UserService(
170
+ IUserRepository userRepository,
171
+ IEmailService emailService,
172
+ ILogger<UserService> logger) : IUserService
173
+ {
174
+ // Fields are automatically created from constructor parameters
175
+ public async Task<User> CreateUserAsync(CreateUserRequest request, CancellationToken cancellationToken = default)
176
+ {
177
+ logger.LogInformation("Creating user with username: {Username}", request.Username);
178
+
179
+ var user = new User(request.Username, request.Email);
180
+
181
+ await userRepository.AddAsync(user, cancellationToken);
182
+ await emailService.SendWelcomeEmailAsync(user.Email, cancellationToken);
183
+
184
+ logger.LogInformation("User created successfully with ID: {UserId}", user.Id);
185
+ return user;
186
+ }
187
+ }
188
+
189
+ // Collection expressions
190
+ public class DataProcessor
191
+ {
192
+ public int[] ProcessNumbers(IEnumerable<int> numbers)
193
+ {
194
+ return numbers
195
+ .Where(n => n > 0)
196
+ .OrderByDescending(n => n)
197
+ .Take(10)
198
+ .ToArray();
199
+ }
200
+
201
+ public List<string> GetDefaultPermissions()
202
+ {
203
+ return ["read", "write", "delete"]; // Collection expression
204
+ }
205
+ }
206
+
207
+ // Required members
208
+ public class CreateUserRequest
209
+ {
210
+ public required string Username { get; init; }
211
+ public required string Email { get; init; }
212
+ public string? FirstName { get; init; }
213
+ public string? LastName { get; init; }
214
+ }
215
+
216
+ // Raw string literals
217
+ public class EmailService
218
+ {
219
+ private readonly string _welcomeEmailTemplate = """
220
+ Welcome to our platform!
221
+
222
+ Hello {FirstName} {LastName},
223
+
224
+ Thank you for registering with us. Your account has been created successfully.
225
+
226
+ Best regards,
227
+ The Team
228
+ """;
229
+
230
+ public async Task SendWelcomeEmailAsync(string email, string? firstName = null, string? lastName = null)
231
+ {
232
+ var emailBody = _welcomeEmailTemplate
233
+ .Replace("{FirstName}", firstName ?? "User")
234
+ .Replace("{LastName}", lastName ?? "");
235
+
236
+ // Send email logic
237
+ }
238
+ }
239
+
240
+ // Using aliases for numeric types
241
+ using Age = int;
242
+ using UserId = System.Guid;
243
+ using Price = decimal;
244
+
245
+ public class User
246
+ {
247
+ public UserId Id { get; set; }
248
+ public required string Username { get; set; }
249
+ public Age Age { get; set; }
250
+ public Price AccountBalance { get; set; }
251
+ }
252
+
253
+ // List patterns
254
+ public class NotificationService
255
+ {
256
+ public string ProcessMessage(string[] messageParts)
257
+ {
258
+ return messageParts switch
259
+ {
260
+ [] => "Empty message",
261
+ [var single] => $"Single part: {single}",
262
+ [var first, var second] => $"Two parts: {first}, {second}",
263
+ [var first, .. var middle, var last] => $"Multiple parts: {first} ... {last}",
264
+ _ => "Complex message"
265
+ };
266
+ }
267
+ }
268
+ ```
269
+
270
+ ### ASP.NET Core 8.0 Minimal APIs
271
+
272
+ ```csharp
273
+ var builder = WebApplication.CreateBuilder(args);
274
+
275
+ // Add services
276
+ builder.Services.AddEndpointsApiExplorer();
277
+ builder.Services.AddSwaggerGen();
278
+ builder.Services.AddDbContext<AppDbContext>(options =>
279
+ options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
280
+ builder.Services.AddScoped<IUserRepository, UserRepository>();
281
+ builder.Services.AddScoped<IUserService, UserService>();
282
+ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
283
+ .AddJwtBearer(options =>
284
+ {
285
+ options.TokenValidationParameters = new TokenValidationParameters
286
+ {
287
+ ValidateIssuer = true,
288
+ ValidateAudience = true,
289
+ ValidateLifetime = true,
290
+ ValidateIssuerSigningKey = true,
291
+ ValidIssuer = builder.Configuration["Jwt:Issuer"],
292
+ ValidAudience = builder.Configuration["Jwt:Audience"],
293
+ IssuerSigningKey = new SymmetricSecurityKey(
294
+ Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!))
295
+ };
296
+ });
297
+
298
+ builder.Services.AddRateLimiter(options =>
299
+ {
300
+ options.AddPolicy("Default", context =>
301
+ RateLimitPartition.GetSlidingWindowLimiter(
302
+ partitionKey: context.Connection.RemoteIpAddress?.ToString() ?? "anonymous",
303
+ factory: _ => new SlidingWindowRateLimiterOptions
304
+ {
305
+ PermitLimit = 100,
306
+ Window = TimeSpan.FromMinutes(1),
307
+ SegmentsPerWindow = 2
308
+ }));
309
+ });
310
+
311
+ var app = builder.Build();
312
+
313
+ // Middleware pipeline
314
+ if (app.Environment.IsDevelopment())
315
+ {
316
+ app.UseSwagger();
317
+ app.UseSwaggerUI();
318
+ }
319
+
320
+ app.UseHttpsRedirection();
321
+ app.UseAuthentication();
322
+ app.UseAuthorization();
323
+ app.UseRateLimiter();
324
+
325
+ // API Endpoint Groups
326
+ var userGroup = app.MapGroup("/api/users")
327
+ .RequireAuthorization()
328
+ .AddEndpointFilter<ValidationFilter>();
329
+
330
+ // Minimal API endpoints
331
+ userGroup.MapGet("/", async (IUserService userService, CancellationToken cancellationToken) =>
332
+ {
333
+ var users = await userService.GetAllUsersAsync(cancellationToken);
334
+ return Results.Ok(users);
335
+ })
336
+ .WithName("GetAllUsers")
337
+ .WithOpenApi();
338
+
339
+ userGroup.MapGet("/{id:guid}", async (Guid id, IUserService userService, CancellationToken cancellationToken) =>
340
+ {
341
+ var user = await userService.GetUserByIdAsync(id, cancellationToken);
342
+ return user is not null ? Results.Ok(user) : Results.NotFound();
343
+ })
344
+ .WithName("GetUserById")
345
+ .WithOpenApi();
346
+
347
+ userGroup.MapPost("/", async (CreateUserRequest request, IUserService userService,
348
+ IValidator<CreateUserRequest> validator, CancellationToken cancellationToken) =>
349
+ {
350
+ var validationResult = await validator.ValidateAsync(request, cancellationToken);
351
+ if (!validationResult.IsValid)
352
+ {
353
+ return Results.ValidationProblem(validationResult.ToDictionary());
354
+ }
355
+
356
+ var user = await userService.CreateUserAsync(request, cancellationToken);
357
+ return Results.Created($"/api/users/{user.Id}", user);
358
+ })
359
+ .WithName("CreateUser")
360
+ .WithOpenApi()
361
+ .RequireRateLimiting("Default");
362
+
363
+ userGroup.MapPut("/{id:guid}", async (Guid id, UpdateUserRequest request,
364
+ IUserService userService, CancellationToken cancellationToken) =>
365
+ {
366
+ var user = await userService.UpdateUserAsync(id, request, cancellationToken);
367
+ return user is not null ? Results.Ok(user) : Results.NotFound();
368
+ })
369
+ .WithName("UpdateUser")
370
+ .WithOpenApi();
371
+
372
+ userGroup.MapDelete("/{id:guid}", async (Guid id, IUserService userService,
373
+ CancellationToken cancellationToken) =>
374
+ {
375
+ var success = await userService.DeleteUserAsync(id, cancellationToken);
376
+ return success ? Results.NoContent() : Results.NotFound();
377
+ })
378
+ .WithName("DeleteUser")
379
+ .WithOpenApi();
380
+
381
+ // Real-time endpoints
382
+ var chatGroup = app.MapGroup("/api/chat")
383
+ .RequireAuthorization();
384
+
385
+ app.MapHub<ChatHub>("/chatHub");
386
+
387
+ app.Run();
388
+
389
+ // Custom endpoint filters
390
+ public class ValidationFilter : IEndpointFilter
391
+ {
392
+ public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
393
+ {
394
+ // Add global validation logic here
395
+ return await next(context);
396
+ }
397
+ }
398
+ ```
399
+
400
+ ### Entity Framework Core 8.0 Patterns
401
+
402
+ ```csharp
403
+ // Domain entities with value objects
404
+ public class User
405
+ {
406
+ public Guid Id { get; private set; }
407
+ public string Username { get; private set; } = string.Empty;
408
+ public Email Email { get; private set; } = null!;
409
+ public UserProfile Profile { get; private set; } = null!;
410
+ public IReadOnlyCollection<UserRole> Roles => _roles.AsReadOnly();
411
+
412
+ private readonly List<UserRole> _roles = new();
413
+
414
+ // Private constructor for EF Core
415
+ private User() { }
416
+
417
+ public User(string username, Email email, UserProfile profile)
418
+ {
419
+ Id = Guid.NewGuid();
420
+ Username = username;
421
+ Email = email;
422
+ Profile = profile;
423
+ CreatedAt = DateTime.UtcNow;
424
+ }
425
+
426
+ public void UpdateProfile(UserProfile newProfile)
427
+ {
428
+ Profile = newProfile;
429
+ UpdatedAt = DateTime.UtcNow;
430
+ }
431
+
432
+ public void AddRole(UserRole role)
433
+ {
434
+ if (!_roles.Contains(role))
435
+ {
436
+ _roles.Add(role);
437
+ UpdatedAt = DateTime.UtcNow;
438
+ }
439
+ }
440
+
441
+ public DateTime CreatedAt { get; private set; }
442
+ public DateTime? UpdatedAt { get; private set; }
443
+ }
444
+
445
+ // Value object
446
+ public readonly record struct Email(string Value)
447
+ {
448
+ public static Email Create(string email)
449
+ {
450
+ if (!Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$"))
451
+ throw new ArgumentException("Invalid email format", nameof(email));
452
+
453
+ return new Email(email.ToLowerInvariant());
454
+ }
455
+ }
456
+
457
+ // EF Core configuration
458
+ public class AppDbContext : DbContext
459
+ {
460
+ public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
461
+
462
+ public DbSet<User> Users => Set<User>();
463
+ public DbSet<UserRole> UserRoles => Set<UserRole>();
464
+ public DbSet<Role> Roles => Set<Role>();
465
+
466
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
467
+ {
468
+ modelBuilder.Entity<User>(builder =>
469
+ {
470
+ builder.HasKey(u => u.Id);
471
+ builder.Property(u => u.Username).IsRequired().HasMaxLength(50);
472
+ builder.Property(u => u.Email)
473
+ .HasConversion(
474
+ email => email.Value,
475
+ value => Email.Create(value))
476
+ .IsRequired()
477
+ .HasMaxLength(100);
478
+
479
+ builder.OwnsOne(u => u.Profile, profile =>
480
+ {
481
+ profile.Property(p => p.FirstName).HasMaxLength(50);
482
+ profile.Property(p => p.LastName).HasMaxLength(50);
483
+ profile.Property(p => p.Bio).HasMaxLength(500);
484
+ });
485
+
486
+ builder.Property(u => u.CreatedAt).HasDefaultValueSql("GETUTCDATE()");
487
+
488
+ builder.HasMany(u => u.Roles)
489
+ .WithMany(r => r.Users)
490
+ .UsingEntity<UserRole>(
491
+ join => join.HasOne<Role>().WithMany(),
492
+ join => join.HasOne<User>().WithMany());
493
+ });
494
+
495
+ modelBuilder.Entity<Role>(builder =>
496
+ {
497
+ builder.HasKey(r => r.Id);
498
+ builder.Property(r => r.Name).IsRequired().HasMaxLength(50);
499
+
500
+ builder.HasData(
501
+ new Role { Id = 1, Name = "Admin" },
502
+ new Role { Id = 2, Name = "User" },
503
+ new Role { Id = 3, Name = "Moderator" });
504
+ });
505
+ }
506
+
507
+ public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
508
+ {
509
+ UpdateTimestamps();
510
+ return await base.SaveChangesAsync(cancellationToken);
511
+ }
512
+
513
+ private void UpdateTimestamps()
514
+ {
515
+ var entries = ChangeTracker
516
+ .Entries()
517
+ .Where(e => e.Entity is User && (e.State == EntityState.Added || e.State == EntityState.Modified));
518
+
519
+ foreach (var entry in entries)
520
+ {
521
+ var user = (User)entry.Entity;
522
+
523
+ if (entry.State == EntityState.Added)
524
+ {
525
+ user.CreatedAt = DateTime.UtcNow;
526
+ }
527
+ else
528
+ {
529
+ user.UpdatedAt = DateTime.UtcNow;
530
+ }
531
+ }
532
+ }
533
+ }
534
+
535
+ // Repository pattern with async/await
536
+ public interface IUserRepository
537
+ {
538
+ Task<User?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
539
+ Task<User?> GetByUsernameAsync(string username, CancellationToken cancellationToken = default);
540
+ Task<User?> GetByEmailAsync(Email email, CancellationToken cancellationToken = default);
541
+ Task<IReadOnlyList<User>> GetAllAsync(int page = 1, int pageSize = 20,
542
+ CancellationToken cancellationToken = default);
543
+ Task<User> AddAsync(User user, CancellationToken cancellationToken = default);
544
+ Task<User> UpdateAsync(User user, CancellationToken cancellationToken = default);
545
+ Task<bool> DeleteAsync(Guid id, CancellationToken cancellationToken = default);
546
+ Task<bool> ExistsAsync(Guid id, CancellationToken cancellationToken = default);
547
+ }
548
+
549
+ public class UserRepository : IUserRepository
550
+ {
551
+ private readonly AppDbContext _context;
552
+
553
+ public UserRepository(AppDbContext context)
554
+ {
555
+ _context = context;
556
+ }
557
+
558
+ public async Task<User?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
559
+ {
560
+ return await _context.Users
561
+ .Include(u => u.Roles)
562
+ .FirstOrDefaultAsync(u => u.Id == id, cancellationToken);
563
+ }
564
+
565
+ public async Task<User?> GetByUsernameAsync(string username, CancellationToken cancellationToken = default)
566
+ {
567
+ return await _context.Users
568
+ .Include(u => u.Roles)
569
+ .FirstOrDefaultAsync(u => u.Username == username, cancellationToken);
570
+ }
571
+
572
+ public async Task<User?> GetByEmailAsync(Email email, CancellationToken cancellationToken = default)
573
+ {
574
+ return await _context.Users
575
+ .Include(u => u.Roles)
576
+ .FirstOrDefaultAsync(u => u.Email == email, cancellationToken);
577
+ }
578
+
579
+ public async Task<IReadOnlyList<User>> GetAllAsync(int page = 1, int pageSize = 20,
580
+ CancellationToken cancellationToken = default)
581
+ {
582
+ return await _context.Users
583
+ .Include(u => u.Roles)
584
+ .OrderBy(u => u.Username)
585
+ .Skip((page - 1) * pageSize)
586
+ .Take(pageSize)
587
+ .AsNoTracking()
588
+ .ToListAsync(cancellationToken);
589
+ }
590
+
591
+ public async Task<User> AddAsync(User user, CancellationToken cancellationToken = default)
592
+ {
593
+ await _context.Users.AddAsync(user, cancellationToken);
594
+ await _context.SaveChangesAsync(cancellationToken);
595
+ return user;
596
+ }
597
+
598
+ public async Task<User> UpdateAsync(User user, CancellationToken cancellationToken = default)
599
+ {
600
+ _context.Users.Update(user);
601
+ await _context.SaveChangesAsync(cancellationToken);
602
+ return user;
603
+ }
604
+
605
+ public async Task<bool> DeleteAsync(Guid id, CancellationToken cancellationToken = default)
606
+ {
607
+ var user = await GetByIdAsync(id, cancellationToken);
608
+ if (user is null) return false;
609
+
610
+ _context.Users.Remove(user);
611
+ await _context.SaveChangesAsync(cancellationToken);
612
+ return true;
613
+ }
614
+
615
+ public async Task<bool> ExistsAsync(Guid id, CancellationToken cancellationToken = default)
616
+ {
617
+ return await _context.Users.AnyAsync(u => u.Id == id, cancellationToken);
618
+ }
619
+ }
620
+ ```
621
+
622
+ ### Modern Async Patterns
623
+
624
+ ```csharp
625
+ // Service layer with proper async handling
626
+ public class UserService : IUserService
627
+ {
628
+ private readonly IUserRepository _userRepository;
629
+ private readonly IEmailService _emailService;
630
+ private readonly ILogger<UserService> _logger;
631
+ private readonly IMemoryCache _cache;
632
+
633
+ public UserService(
634
+ IUserRepository userRepository,
635
+ IEmailService emailService,
636
+ ILogger<UserService> logger,
637
+ IMemoryCache cache)
638
+ {
639
+ _userRepository = userRepository;
640
+ _emailService = emailService;
641
+ _logger = logger;
642
+ _cache = cache;
643
+ }
644
+
645
+ public async Task<User> CreateUserAsync(CreateUserRequest request,
646
+ CancellationToken cancellationToken = default)
647
+ {
648
+ // Validate input
649
+ var email = Email.Create(request.Email);
650
+
651
+ // Check for existing user
652
+ var existingUser = await _userRepository.GetByEmailAsync(email, cancellationToken);
653
+ if (existingUser is not null)
654
+ {
655
+ throw new UserAlreadyExistsException($"User with email {email.Value} already exists");
656
+ }
657
+
658
+ // Create new user
659
+ var profile = new UserProfile(request.FirstName, request.LastName, request.Bio);
660
+ var user = new User(request.Username, email, profile);
661
+
662
+ // Save to database
663
+ var createdUser = await _userRepository.AddAsync(user, cancellationToken);
664
+
665
+ // Send welcome email (fire and forget)
666
+ _ = Task.Run(async () =>
667
+ {
668
+ try
669
+ {
670
+ await _emailService.SendWelcomeEmailAsync(email, profile.FirstName, profile.LastName);
671
+ }
672
+ catch (Exception ex)
673
+ {
674
+ _logger.LogError(ex, "Failed to send welcome email to {Email}", email.Value);
675
+ }
676
+ });
677
+
678
+ // Cache the user
679
+ _cache.Set($"user_{createdUser.Id}", createdUser, TimeSpan.FromMinutes(30));
680
+
681
+ return createdUser;
682
+ }
683
+
684
+ public async Task<User?> GetUserByIdAsync(Guid id, CancellationToken cancellationToken = default)
685
+ {
686
+ // Try cache first
687
+ if (_cache.TryGetValue($"user_{id}", out User? cachedUser))
688
+ {
689
+ return cachedUser;
690
+ }
691
+
692
+ // Fetch from database
693
+ var user = await _userRepository.GetByIdAsync(id, cancellationToken);
694
+
695
+ // Cache if found
696
+ if (user is not null)
697
+ {
698
+ _cache.Set($"user_{id}", user, TimeSpan.FromMinutes(30));
699
+ }
700
+
701
+ return user;
702
+ }
703
+
704
+ public async Task<IReadOnlyList<User>> GetAllUsersAsync(int page = 1, int pageSize = 20,
705
+ CancellationToken cancellationToken = default)
706
+ {
707
+ return await _userRepository.GetAllAsync(page, pageSize, cancellationToken);
708
+ }
709
+
710
+ public async Task<User?> UpdateUserAsync(Guid id, UpdateUserRequest request,
711
+ CancellationToken cancellationToken = default)
712
+ {
713
+ var user = await _userRepository.GetByIdAsync(id, cancellationToken);
714
+ if (user is null) return null;
715
+
716
+ var updatedProfile = new UserProfile(
717
+ request.FirstName ?? user.Profile.FirstName,
718
+ request.LastName ?? user.Profile.LastName,
719
+ request.Bio ?? user.Profile.Bio);
720
+
721
+ user.UpdateProfile(updatedProfile);
722
+
723
+ var updatedUser = await _userRepository.UpdateAsync(user, cancellationToken);
724
+
725
+ // Update cache
726
+ _cache.Set($"user_{id}", updatedUser, TimeSpan.FromMinutes(30));
727
+
728
+ return updatedUser;
729
+ }
730
+
731
+ public async Task<bool> DeleteUserAsync(Guid id, CancellationToken cancellationToken = default)
732
+ {
733
+ var success = await _userRepository.DeleteAsync(id, cancellationToken);
734
+
735
+ if (success)
736
+ {
737
+ // Remove from cache
738
+ _cache.Remove($"user_{id}");
739
+ }
740
+
741
+ return success;
742
+ }
743
+
744
+ // Parallel operations example
745
+ public async Task<UserProfileSummary> GetUserProfileSummaryAsync(Guid userId,
746
+ CancellationToken cancellationToken = default)
747
+ {
748
+ var userTask = GetUserByIdAsync(userId, cancellationToken);
749
+ var postsTask = GetUserPostsCountAsync(userId, cancellationToken);
750
+ var followersTask = GetUserFollowersCountAsync(userId, cancellationToken);
751
+
752
+ await Task.WhenAll(userTask, postsTask, followersTask);
753
+
754
+ var user = await userTask;
755
+ if (user is null) throw new UserNotFoundException(userId);
756
+
757
+ var postsCount = await postsTask;
758
+ var followersCount = await followersTask;
759
+
760
+ return new UserProfileSummary
761
+ {
762
+ User = user,
763
+ PostsCount = postsCount,
764
+ FollowersCount = followersCount
765
+ };
766
+ }
767
+
768
+ private async Task<int> GetUserPostsCountAsync(Guid userId, CancellationToken cancellationToken = default)
769
+ {
770
+ // Simulate external API call
771
+ await Task.Delay(100, cancellationToken);
772
+ return Random.Shared.Next(0, 100);
773
+ }
774
+
775
+ private async Task<int> GetUserFollowersCountAsync(Guid userId, CancellationToken cancellationToken = default)
776
+ {
777
+ // Simulate external API call
778
+ await Task.Delay(150, cancellationToken);
779
+ return Random.Shared.Next(0, 1000);
780
+ }
781
+ }
782
+
783
+ // Background service with async patterns
784
+ public class UserCleanupService : BackgroundService
785
+ {
786
+ private readonly ILogger<UserCleanupService> _logger;
787
+ private readonly IUserRepository _userRepository;
788
+ private readonly TimeSpan _cleanupInterval = TimeSpan.FromHours(1);
789
+
790
+ public UserCleanupService(
791
+ ILogger<UserCleanupService> logger,
792
+ IUserRepository userRepository)
793
+ {
794
+ _logger = logger;
795
+ _userRepository = userRepository;
796
+ }
797
+
798
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
799
+ {
800
+ while (!stoppingToken.IsCancellationRequested)
801
+ {
802
+ try
803
+ {
804
+ _logger.LogInformation("Starting user cleanup process");
805
+
806
+ await CleanupInactiveUsersAsync(stoppingToken);
807
+
808
+ _logger.LogInformation("User cleanup process completed");
809
+ }
810
+ catch (OperationCanceledException)
811
+ {
812
+ _logger.LogInformation("User cleanup service was cancelled");
813
+ break;
814
+ }
815
+ catch (Exception ex)
816
+ {
817
+ _logger.LogError(ex, "Error during user cleanup process");
818
+ }
819
+
820
+ await Task.Delay(_cleanupInterval, stoppingToken);
821
+ }
822
+ }
823
+
824
+ private async Task CleanupInactiveUsersAsync(CancellationToken cancellationToken)
825
+ {
826
+ const int batchSize = 100;
827
+ var cutoffDate = DateTime.UtcNow.AddDays(-365);
828
+
829
+ var inactiveUsers = await _userRepository.GetInactiveUsersBeforeAsync(cutoffDate, cancellationToken);
830
+
831
+ foreach (var batch in inactiveUsers.Chunk(batchSize))
832
+ {
833
+ await Parallel.ForEachAsync(batch, cancellationToken, async (user, ct) =>
834
+ {
835
+ try
836
+ {
837
+ await _userRepository.DeleteAsync(user.Id, ct);
838
+ _logger.LogInformation("Deleted inactive user {UserId} ({Username})", user.Id, user.Username);
839
+ }
840
+ catch (Exception ex)
841
+ {
842
+ _logger.LogError(ex, "Failed to delete user {UserId}", user.Id);
843
+ }
844
+ });
845
+ }
846
+ }
847
+ }
848
+ ```
849
+
850
+ ## Performance Considerations
851
+
852
+ ### Memory Management
853
+
854
+ ```csharp
855
+ // Efficient data structures and memory usage
856
+ public class DataProcessor
857
+ {
858
+ // Use Span<T> for zero-allocation string processing
859
+ public static bool IsValidEmail(ReadOnlySpan<char> email)
860
+ {
861
+ var atIdx = email.IndexOf('@');
862
+ if (atIdx <= 0 || atIdx == email.Length - 1) return false;
863
+
864
+ var dotIdx = email.LastIndexOf('.');
865
+ if (dotIdx <= atIdx + 1 || dotIdx == email.Length - 1) return false;
866
+
867
+ return true;
868
+ }
869
+
870
+ // Use ArrayPool for temporary arrays
871
+ public static byte[] ProcessLargeData(ReadOnlySpan<byte> data)
872
+ {
873
+ var buffer = ArrayPool<byte>.Shared.Rent(data.Length * 2);
874
+
875
+ try
876
+ {
877
+ // Process data into buffer
878
+ var processedLength = ProcessDataInternal(data, buffer);
879
+
880
+ var result = new byte[processedLength];
881
+ buffer.AsSpan(0, processedLength).CopyTo(result);
882
+
883
+ return result;
884
+ }
885
+ finally
886
+ {
887
+ ArrayPool<byte>.Shared.Return(buffer);
888
+ }
889
+ }
890
+
891
+ private static int ProcessDataInternal(ReadOnlySpan<byte> source, Span<byte> destination)
892
+ {
893
+ // Processing logic
894
+ return Math.Min(source.Length * 2, destination.Length);
895
+ }
896
+ }
897
+
898
+ // Efficient LINQ usage with immediate execution when needed
899
+ public class UserRepository
900
+ {
901
+ private readonly AppDbContext _context;
902
+
903
+ public UserRepository(AppDbContext context)
904
+ {
905
+ _context = context;
906
+ }
907
+
908
+ // Use AsNoTracking for read-only queries
909
+ public async Task<IReadOnlyList<User>> GetActiveUsersAsync(int page = 1, int pageSize = 20,
910
+ CancellationToken cancellationToken = default)
911
+ {
912
+ return await _context.Users
913
+ .AsNoTracking() // No change tracking for read-only
914
+ .Where(u => u.IsActive)
915
+ .OrderBy(u => u.Username)
916
+ .Select(u => new UserDto // Project only needed fields
917
+ {
918
+ Id = u.Id,
919
+ Username = u.Username,
920
+ Email = u.Email.Value,
921
+ CreatedAt = u.CreatedAt
922
+ })
923
+ .Skip((page - 1) * pageSize)
924
+ .Take(pageSize)
925
+ .ToListAsync(cancellationToken);
926
+ }
927
+
928
+ // Use compiled queries for frequently executed queries
929
+ private static readonly Func<AppDbContext, Guid, Task<User?>> GetUserByIdCompiled =
930
+ EF.CompileAsyncQuery((AppDbContext context, Guid id) =>
931
+ context.Users
932
+ .Include(u => u.Roles)
933
+ .FirstOrDefaultAsync(u => u.Id == id));
934
+
935
+ public async Task<User?> GetUserByIdAsync(Guid id, CancellationToken cancellationToken = default)
936
+ {
937
+ return await GetUserByIdCompiled(_context, id);
938
+ }
939
+
940
+ // Batch operations for better performance
941
+ public async Task UpdateUsersLastLoginAsync(IEnumerable<Guid> userIds,
942
+ CancellationToken cancellationToken = default)
943
+ {
944
+ const int batchSize = 1000;
945
+
946
+ foreach (var batch in userIds.Chunk(batchSize))
947
+ {
948
+ await _context.Users
949
+ .Where(u => batch.Contains(u.Id))
950
+ .ExecuteUpdateAsync(setters => setters
951
+ .SetProperty(u => u.LastLoginAt, DateTime.UtcNow), cancellationToken);
952
+ }
953
+ }
954
+ }
955
+
956
+ // Memory-efficient caching
957
+ public class CacheService
958
+ {
959
+ private readonly IMemoryCache _cache;
960
+ private readonly Timer _cleanupTimer;
961
+
962
+ public CacheService(IMemoryCache cache)
963
+ {
964
+ _cache = cache;
965
+
966
+ // Set up periodic cleanup
967
+ _cleanupTimer = new Timer(CleanupExpiredEntries, null,
968
+ TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
969
+ }
970
+
971
+ public async Task<T?> GetOrCreateAsync<T>(string key, Func<Task<T>> factory,
972
+ TimeSpan? absoluteExpiration = null, TimeSpan? slidingExpiration = null)
973
+ {
974
+ if (_cache.TryGetValue(key, out T? cachedValue))
975
+ {
976
+ return cachedValue;
977
+ }
978
+
979
+ var value = await factory();
980
+
981
+ var options = new MemoryCacheEntryOptions
982
+ {
983
+ AbsoluteExpirationRelativeToNow = absoluteExpiration,
984
+ SlidingExpiration = slidingExpiration,
985
+ Size = 1 // Enable size-based eviction
986
+ };
987
+
988
+ _cache.Set(key, value, options);
989
+ return value;
990
+ }
991
+
992
+ private void CleanupExpiredEntries(object? state)
993
+ {
994
+ // MemoryCache automatically removes expired entries, but we can
995
+ // implement additional cleanup logic here if needed
996
+ _cache.Compact(0.95); // Remove 5% of least recently used items
997
+ }
998
+
999
+ public void Remove(string key)
1000
+ {
1001
+ _cache.Remove(key);
1002
+ }
1003
+
1004
+ public void Clear()
1005
+ {
1006
+ _cache.Compact(1.0); // Remove all entries
1007
+ }
1008
+ }
1009
+ ```
1010
+
1011
+ ### Database Performance
1012
+
1013
+ ```csharp
1014
+ // Optimized database operations
1015
+ public class OptimizedDbContext : DbContext
1016
+ {
1017
+ private readonly ILogger<OptimizedDbContext> _logger;
1018
+
1019
+ public OptimizedDbContext(DbContextOptions<OptimizedDbContext> options,
1020
+ ILogger<OptimizedDbContext> logger) : base(options)
1021
+ {
1022
+ _logger = logger;
1023
+ }
1024
+
1025
+ // Use connection resiliency
1026
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
1027
+ {
1028
+ optionsBuilder.EnableRetryOnFailure(
1029
+ maxRetryCount: 3,
1030
+ maxRetryDelay: TimeSpan.FromSeconds(30),
1031
+ errorNumbersToAdd: null);
1032
+
1033
+ // Configure sensitive data logging for development
1034
+ optionsBuilder.EnableSensitiveDataLogging(false);
1035
+ optionsBuilder.EnableDetailedErrors(false);
1036
+
1037
+ // Set command timeout
1038
+ optionsBuilder.UseSqlServer(sqlOptions =>
1039
+ {
1040
+ sqlOptions.CommandTimeout(30);
1041
+ sqlOptions.EnableRetryOnFailure();
1042
+ });
1043
+ }
1044
+
1045
+ // Batch operations
1046
+ public async Task BulkInsertAsync<T>(IEnumerable<T> entities,
1047
+ CancellationToken cancellationToken = default) where T : class
1048
+ {
1049
+ const int batchSize = 1000;
1050
+
1051
+ foreach (var batch in entities.Chunk(batchSize))
1052
+ {
1053
+ await Set<T>().AddRangeAsync(batch, cancellationToken);
1054
+ await SaveChangesAsync(cancellationToken);
1055
+
1056
+ // Clear change tracker for memory efficiency
1057
+ ChangeTracker.Clear();
1058
+ }
1059
+ }
1060
+
1061
+ // Optimized queries with proper indexing hints
1062
+ public async Task<IReadOnlyList<User>> SearchUsersAsync(string searchTerm,
1063
+ int page = 1, int pageSize = 20, CancellationToken cancellationToken = default)
1064
+ {
1065
+ var normalizedSearch = searchTerm.Trim().ToUpperInvariant();
1066
+
1067
+ return await Users
1068
+ .AsNoTracking()
1069
+ .Where(u => EF.Functions.ILike(u.Username, $"%{normalizedSearch}%") ||
1070
+ EF.Functions.ILike(u.Profile.FirstName, $"%{normalizedSearch}%") ||
1071
+ EF.Functions.ILike(u.Profile.LastName, $"%{normalizedSearch}%"))
1072
+ .OrderBy(u => u.Username)
1073
+ .Select(u => new UserDto // Project to DTO for less data transfer
1074
+ {
1075
+ Id = u.Id,
1076
+ Username = u.Username,
1077
+ Email = u.Email.Value,
1078
+ FullName = $"{u.Profile.FirstName} {u.Profile.LastName}",
1079
+ CreatedAt = u.CreatedAt
1080
+ })
1081
+ .Skip((page - 1) * pageSize)
1082
+ .Take(pageSize)
1083
+ .ToListAsync(cancellationToken);
1084
+ }
1085
+
1086
+ // Use raw SQL for performance-critical operations
1087
+ public async Task<int> DeleteOldRecordsAsync(DateTime cutoffDate,
1088
+ CancellationToken cancellationToken = default)
1089
+ {
1090
+ var sql = """
1091
+ DELETE FROM UserLogs
1092
+ WHERE CreatedAt < @cutoffDate
1093
+ """;
1094
+
1095
+ return await Database.ExecuteSqlRawAsync(sql,
1096
+ new SqlParameter("@cutoffDate", cutoffDate));
1097
+ }
1098
+
1099
+ public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
1100
+ {
1101
+ try
1102
+ {
1103
+ return await base.SaveChangesAsync(cancellationToken);
1104
+ }
1105
+ catch (DbUpdateConcurrencyException ex)
1106
+ {
1107
+ _logger.LogWarning(ex, "Concurrency conflict detected");
1108
+ throw new ConcurrencyException("Data was modified by another user", ex);
1109
+ }
1110
+ catch (DbUpdateException ex)
1111
+ {
1112
+ _logger.LogError(ex, "Database update failed");
1113
+ throw new DataUpdateException("Failed to save changes to database", ex);
1114
+ }
1115
+ }
1116
+ }
1117
+
1118
+ // Connection pooling and transaction management
1119
+ public class UnitOfWork : IUnitOfWork, IDisposable
1120
+ {
1121
+ private readonly OptimizedDbContext _context;
1122
+ private IDbContextTransaction? _transaction;
1123
+
1124
+ public UnitOfWork(OptimizedDbContext context)
1125
+ {
1126
+ _context = context;
1127
+ }
1128
+
1129
+ public async Task BeginTransactionAsync(IsolationLevel isolationLevel = IsolationLevel.ReadCommitted,
1130
+ CancellationToken cancellationToken = default)
1131
+ {
1132
+ _transaction = await _context.Database.BeginTransactionAsync(isolationLevel, cancellationToken);
1133
+ }
1134
+
1135
+ public async Task CommitAsync(CancellationToken cancellationToken = default)
1136
+ {
1137
+ try
1138
+ {
1139
+ await _context.SaveChangesAsync(cancellationToken);
1140
+ await _transaction?.CommitAsync(cancellationToken)!;
1141
+ }
1142
+ catch
1143
+ {
1144
+ await RollbackAsync(cancellationToken);
1145
+ throw;
1146
+ }
1147
+ }
1148
+
1149
+ public async Task RollbackAsync(CancellationToken cancellationToken = default)
1150
+ {
1151
+ if (_transaction != null)
1152
+ {
1153
+ await _transaction.RollbackAsync(cancellationToken);
1154
+ }
1155
+ }
1156
+
1157
+ public void Dispose()
1158
+ {
1159
+ _transaction?.Dispose();
1160
+ _context.Dispose();
1161
+ }
1162
+ }
66
1163
  ```
67
1164
 
68
- ## Inputs
69
- - Language-specific source directories (e.g. `src/`, `app/`).
70
- - Language-specific build/test configuration files (e.g. `package.json`, `pyproject.toml`, `go.mod`).
71
- - Relevant test suites and sample data.
1165
+ ### HTTP Client Performance
72
1166
 
73
- ## Outputs
74
- - Test/lint execution plan tailored to the selected language.
75
- - List of key language idioms and review checkpoints.
1167
+ ```csharp
1168
+ // Optimized HTTP client with connection pooling and circuit breaker
1169
+ public class OptimizedHttpClient
1170
+ {
1171
+ private readonly HttpClient _httpClient;
1172
+ private readonly ILogger<OptimizedHttpClient> _logger;
1173
+
1174
+ public OptimizedHttpClient(IHttpClientFactory httpClientFactory,
1175
+ ILogger<OptimizedHttpClient> logger)
1176
+ {
1177
+ _httpClient = httpClientFactory.CreateClient("OptimizedClient");
1178
+ _logger = logger;
1179
+ }
1180
+
1181
+ public async Task<T?> GetAsync<T>(string uri, CancellationToken cancellationToken = default)
1182
+ {
1183
+ try
1184
+ {
1185
+ var response = await _httpClient.GetAsync(uri, cancellationToken);
1186
+ response.EnsureSuccessStatusCode();
1187
+
1188
+ return await response.Content.ReadFromJsonAsync<T>(cancellationToken: cancellationToken);
1189
+ }
1190
+ catch (HttpRequestException ex)
1191
+ {
1192
+ _logger.LogError(ex, "HTTP request failed for URI: {Uri}", uri);
1193
+ throw;
1194
+ }
1195
+ }
1196
+
1197
+ public async Task<T?> PostAsync<T>(string uri, T data, CancellationToken cancellationToken = default)
1198
+ {
1199
+ try
1200
+ {
1201
+ var response = await _httpClient.PostAsJsonAsync(uri, data, cancellationToken);
1202
+ response.EnsureSuccessStatusCode();
1203
+
1204
+ return await response.Content.ReadFromJsonAsync<T>(cancellationToken: cancellationToken);
1205
+ }
1206
+ catch (HttpRequestException ex)
1207
+ {
1208
+ _logger.LogError(ex, "HTTP POST request failed for URI: {Uri}", uri);
1209
+ throw;
1210
+ }
1211
+ }
1212
+ }
76
1213
 
77
- ## Failure Modes
78
- - When the language runtime or package manager is not installed.
79
- - When the main language cannot be determined in a multilingual project.
1214
+ // HTTP client configuration in Program.cs
1215
+ builder.Services.AddHttpClient("OptimizedClient", client =>
1216
+ {
1217
+ client.BaseAddress = new Uri("https://api.example.com/");
1218
+ client.Timeout = TimeSpan.FromSeconds(30);
1219
+ client.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");
1220
+ })
1221
+ .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
1222
+ {
1223
+ PooledConnectionLifetime = TimeSpan.FromMinutes(5),
1224
+ PooledConnectionIdleTimeout = TimeSpan.FromMinutes(1),
1225
+ MaxConnectionsPerServer = 100
1226
+ })
1227
+ .AddPolicyHandler(GetRetryPolicy())
1228
+ .AddPolicyHandler(GetCircuitBreakerPolicy());
80
1229
 
81
- ## Dependencies
82
- - Access to the project file is required using the Read/Grep tool.
83
- - When used with `Skill("moai-foundation-langs")`, it is easy to share cross-language conventions.
1230
+ static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
1231
+ {
1232
+ return HttpPolicyExtensions
1233
+ .HandleTransientHttpError()
1234
+ .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
1235
+ .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
1236
+ }
84
1237
 
85
- ## References
86
- - Microsoft. "C# Programming Guide." https://learn.microsoft.com/dotnet/csharp/ (accessed 2025-03-29).
87
- - Microsoft. ".NET Testing with dotnet test." https://learn.microsoft.com/dotnet/core/testing/ (accessed 2025-03-29).
1238
+ static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
1239
+ {
1240
+ return HttpPolicyExtensions
1241
+ .HandleTransientHttpError()
1242
+ .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));
1243
+ }
1244
+ ```
1245
+
1246
+ ## Testing Strategy
1247
+
1248
+ ### xUnit Configuration
1249
+
1250
+ ```csharp
1251
+ // Test project setup with dependency injection
1252
+ public class TestFixture : IDisposable
1253
+ {
1254
+ public ServiceProvider ServiceProvider { get; }
1255
+ public AppDbContext DbContext { get; }
1256
+
1257
+ public TestFixture()
1258
+ {
1259
+ var services = new ServiceCollection();
1260
+
1261
+ // Configure in-memory database
1262
+ services.AddDbContext<AppDbContext>(options =>
1263
+ options.UseInMemoryDatabase(Guid.NewGuid().ToString()));
1264
+
1265
+ // Register services
1266
+ services.AddScoped<IUserRepository, UserRepository>();
1267
+ services.AddScoped<IUserService, UserService>();
1268
+ services.AddScoped<IEmailService, MockEmailService>();
1269
+
1270
+ // Configure logging
1271
+ services.AddLogging(builder =>
1272
+ builder.SetMinimumLevel(LogLevel.Warning));
1273
+
1274
+ ServiceProvider = services.BuildServiceProvider();
1275
+ DbContext = ServiceProvider.GetRequiredService<AppDbContext>();
1276
+
1277
+ // Seed test data
1278
+ SeedTestData();
1279
+ }
1280
+
1281
+ private void SeedTestData()
1282
+ {
1283
+ var users = new List<User>
1284
+ {
1285
+ new User("admin", Email.Create("admin@example.com"),
1286
+ new UserProfile("Admin", "User", "System administrator")),
1287
+ new User("user1", Email.Create("user1@example.com"),
1288
+ new UserProfile("Regular", "User", "Regular user"))
1289
+ };
1290
+
1291
+ DbContext.Users.AddRange(users);
1292
+ DbContext.SaveChanges();
1293
+ }
1294
+
1295
+ public void Dispose()
1296
+ {
1297
+ DbContext.Dispose();
1298
+ ServiceProvider.Dispose();
1299
+ }
1300
+ }
1301
+
1302
+ // Integration tests with WebApplicationFactory
1303
+ public class UserIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
1304
+ {
1305
+ private readonly WebApplicationFactory<Program> _factory;
1306
+ private readonly HttpClient _client;
1307
+
1308
+ public UserIntegrationTests(WebApplicationFactory<Program> factory)
1309
+ {
1310
+ _factory = factory.WithWebHostBuilder(builder =>
1311
+ {
1312
+ builder.ConfigureServices(services =>
1313
+ {
1314
+ // Configure test services
1315
+ services.Remove(services.SingleOrDefault(
1316
+ d => d.ServiceType == typeof(DbContextOptions<AppDbContext>)));
1317
+
1318
+ services.AddDbContext<AppDbContext>(options =>
1319
+ options.UseInMemoryDatabase("TestDb"));
1320
+
1321
+ // Replace email service with mock
1322
+ services.AddScoped<IEmailService, MockEmailService>();
1323
+ });
1324
+ });
1325
+
1326
+ _client = _factory.CreateClient();
1327
+ }
1328
+
1329
+ [Fact]
1330
+ public async Task CreateUser_ValidInput_ReturnsCreated()
1331
+ {
1332
+ // Arrange
1333
+ var request = new CreateUserRequest
1334
+ {
1335
+ Username = "newuser",
1336
+ Email = "newuser@example.com",
1337
+ FirstName = "New",
1338
+ LastName = "User"
1339
+ };
1340
+
1341
+ // Act
1342
+ var response = await _client.PostAsJsonAsync("/api/users", request);
1343
+
1344
+ // Assert
1345
+ response.EnsureSuccessStatusCode();
1346
+
1347
+ var user = await response.Content.ReadFromJsonAsync<UserDto>();
1348
+ Assert.NotNull(user);
1349
+ Assert.Equal("newuser", user!.Username);
1350
+ Assert.Equal("newuser@example.com", user.Email);
1351
+ }
1352
+
1353
+ [Fact]
1354
+ public async Task GetUsers_ReturnsUserList()
1355
+ {
1356
+ // Act
1357
+ var response = await _client.GetAsync("/api/users");
1358
+
1359
+ // Assert
1360
+ response.EnsureSuccessStatusCode();
1361
+
1362
+ var users = await response.Content.ReadFromJsonAsync<List<UserDto>>();
1363
+ Assert.NotNull(users);
1364
+ Assert.True(users!.Count >= 2); // At least the seeded users
1365
+ }
1366
+
1367
+ [Fact]
1368
+ public async Task GetUser_ExistingId_ReturnsUser()
1369
+ {
1370
+ // Arrange
1371
+ var createResponse = await _client.PostAsJsonAsync("/api/users", new CreateUserRequest
1372
+ {
1373
+ Username = "testuser",
1374
+ Email = "test@example.com",
1375
+ FirstName = "Test",
1376
+ LastName = "User"
1377
+ });
1378
+
1379
+ var createdUser = await createResponse.Content.ReadFromJsonAsync<UserDto>();
1380
+ Assert.NotNull(createdUser);
1381
+
1382
+ // Act
1383
+ var response = await _client.GetAsync($"/api/users/{createdUser!.Id}");
1384
+
1385
+ // Assert
1386
+ response.EnsureSuccessStatusCode();
1387
+
1388
+ var user = await response.Content.ReadFromJsonAsync<UserDto>();
1389
+ Assert.NotNull(user);
1390
+ Assert.Equal(createdUser!.Id, user!.Id);
1391
+ }
1392
+ }
1393
+ ```
1394
+
1395
+ ### Unit Testing Patterns
1396
+
1397
+ ```csharp
1398
+ public class UserServiceTests : IClassFixture<TestFixture>
1399
+ {
1400
+ private readonly TestFixture _fixture;
1401
+ private readonly IUserService _userService;
1402
+ private readonly Mock<IEmailService> _mockEmailService;
1403
+
1404
+ public UserServiceTests(TestFixture fixture)
1405
+ {
1406
+ _fixture = fixture;
1407
+ _userService = fixture.ServiceProvider.GetRequiredService<IUserService>();
1408
+ _mockEmailService = new Mock<IEmailService>();
1409
+ _userService = new UserService(
1410
+ _fixture.ServiceProvider.GetRequiredService<IUserRepository>(),
1411
+ _mockEmailService.Object,
1412
+ Mock.Of<ILogger<UserService>>(),
1413
+ Mock.Of<IMemoryCache>());
1414
+ }
1415
+
1416
+ [Fact]
1417
+ public async Task CreateUser_ValidRequest_ReturnsUser()
1418
+ {
1419
+ // Arrange
1420
+ var request = new CreateUserRequest
1421
+ {
1422
+ Username = "newuser",
1423
+ Email = "newuser@example.com",
1424
+ FirstName = "New",
1425
+ LastName = "User"
1426
+ };
1427
+
1428
+ _mockEmailService
1429
+ .Setup(s => s.SendWelcomeEmailAsync(It.IsAny<Email>(), It.IsAny<string>(), It.IsAny<string>()))
1430
+ .Returns(Task.CompletedTask);
1431
+
1432
+ // Act
1433
+ var result = await _userService.CreateUserAsync(request);
1434
+
1435
+ // Assert
1436
+ Assert.NotNull(result);
1437
+ Assert.Equal("newuser", result.Username);
1438
+ Assert.Equal("newuser@example.com", result.Email.Value);
1439
+
1440
+ _mockEmailService.Verify(
1441
+ s => s.SendWelcomeEmailAsync(It.Is<Email>(e => e.Value == "newuser@example.com"), "New", "User"),
1442
+ Times.Once);
1443
+ }
1444
+
1445
+ [Fact]
1446
+ public async Task CreateUser_DuplicateEmail_ThrowsException()
1447
+ {
1448
+ // Arrange
1449
+ var request = new CreateUserRequest
1450
+ {
1451
+ Username = "duplicate",
1452
+ Email = "admin@example.com", // Already exists in seed data
1453
+ FirstName = "Duplicate",
1454
+ LastName = "User"
1455
+ };
1456
+
1457
+ // Act & Assert
1458
+ var exception = await Assert.ThrowsAsync<UserAlreadyExistsException>(
1459
+ () => _userService.CreateUserAsync(request));
1460
+
1461
+ Assert.Contains("already exists", exception.Message);
1462
+
1463
+ _mockEmailService.Verify(
1464
+ s => s.SendWelcomeEmailAsync(It.IsAny<Email>(), It.IsAny<string>(), It.IsAny<string>()),
1465
+ Times.Never);
1466
+ }
1467
+
1468
+ [Theory]
1469
+ [InlineData("test@example.com", true)]
1470
+ [InlineData("invalid-email", false)]
1471
+ [InlineData("test@.com", false)]
1472
+ [InlineData("@example.com", false)]
1473
+ public void EmailValidation_ValidatesEmailCorrectly(string email, bool expectedValid)
1474
+ {
1475
+ // Act
1476
+ var isValid = EmailValidator.IsValid(email);
1477
+
1478
+ // Assert
1479
+ Assert.Equal(expectedValid, isValid);
1480
+ }
1481
+
1482
+ [Fact]
1483
+ public async Task UpdateUser_ValidRequest_UpdatesUser()
1484
+ {
1485
+ // Arrange
1486
+ var existingUser = await _userService.GetUserByIdAsync(Guid.Parse("00000000-0000-0000-0000-000000000001"));
1487
+ Assert.NotNull(existingUser);
1488
+
1489
+ var updateRequest = new UpdateUserRequest
1490
+ {
1491
+ FirstName = "Updated",
1492
+ LastName = "Name",
1493
+ Bio = "Updated bio"
1494
+ };
1495
+
1496
+ // Act
1497
+ var result = await _userService.UpdateUserAsync(existingUser!.Id, updateRequest);
1498
+
1499
+ // Assert
1500
+ Assert.NotNull(result);
1501
+ Assert.Equal("Updated", result!.Profile.FirstName);
1502
+ Assert.Equal("Name", result.Profile.LastName);
1503
+ Assert.Equal("Updated bio", result.Profile.Bio);
1504
+ }
1505
+ }
1506
+
1507
+ // Performance testing
1508
+ public class PerformanceTests : IClassFixture<TestFixture>
1509
+ {
1510
+ private readonly TestFixture _fixture;
1511
+
1512
+ public PerformanceTests(TestFixture fixture)
1513
+ {
1514
+ _fixture = fixture;
1515
+ }
1516
+
1517
+ [Fact]
1518
+ public async Task BulkInsert_PerformanceTest()
1519
+ {
1520
+ // Arrange
1521
+ const int userCount = 1000;
1522
+ var users = Enumerable.Range(1, userCount)
1523
+ .Select(i => new User($"user{i}", Email.Create($"user{i}@example.com"),
1524
+ new UserProfile($"User{i}", $"Test{i}", $"Bio for user {i}")))
1525
+ .ToList();
1526
+
1527
+ var stopwatch = Stopwatch.StartNew();
1528
+
1529
+ // Act
1530
+ await _fixture.DbContext.BulkInsertAsync(users);
1531
+
1532
+ stopwatch.Stop();
1533
+
1534
+ // Assert
1535
+ Assert.True(stopwatch.ElapsedMilliseconds < 5000, // Should complete within 5 seconds
1536
+ $"Bulk insert took {stopwatch.ElapsedMilliseconds}ms, expected less than 5000ms");
1537
+
1538
+ var dbUsers = await _fixture.DbContext.Users.CountAsync();
1539
+ Assert.True(dbUsers >= userCount, $"Expected at least {userCount} users, got {dbUsers}");
1540
+ }
1541
+ }
1542
+ ```
1543
+
1544
+ ### API Testing
1545
+
1546
+ ```csharp
1547
+ // API contract testing
1548
+ public class ApiContractTests : IClassFixture<WebApplicationFactory<Program>>
1549
+ {
1550
+ private readonly WebApplicationFactory<Program> _factory;
1551
+ private readonly HttpClient _client;
1552
+
1553
+ public ApiContractTests(WebApplicationFactory<Program> factory)
1554
+ {
1555
+ _factory = factory;
1556
+ _client = _factory.CreateClient();
1557
+ }
1558
+
1559
+ [Fact]
1560
+ public async Task GetUsers_ReturnsCorrectContract()
1561
+ {
1562
+ // Act
1563
+ var response = await _client.GetAsync("/api/users");
1564
+
1565
+ // Assert
1566
+ response.EnsureSuccessStatusCode();
1567
+ Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType?.ToString());
1568
+
1569
+ var content = await response.Content.ReadAsStringAsync();
1570
+ Assert.NotEmpty(content);
1571
+
1572
+ // Validate JSON schema
1573
+ var users = JsonSerializer.Deserialize<List<UserDto>>(content);
1574
+ Assert.NotNull(users);
1575
+ }
1576
+
1577
+ [Fact]
1578
+ public async Task CreateUser_ValidInput_ReturnsCorrectContract()
1579
+ {
1580
+ // Arrange
1581
+ var request = new
1582
+ {
1583
+ Username = "newuser",
1584
+ Email = "newuser@example.com",
1585
+ FirstName = "New",
1586
+ LastName = "User"
1587
+ };
1588
+
1589
+ // Act
1590
+ var response = await _client.PostAsJsonAsync("/api/users", request);
1591
+
1592
+ // Assert
1593
+ Assert.Equal(HttpStatusCode.Created, response.StatusCode);
1594
+ Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType?.ToString());
1595
+
1596
+ var content = await response.Content.ReadAsStringAsync();
1597
+ var user = JsonSerializer.Deserialize<UserDto>(content, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
1598
+
1599
+ Assert.NotNull(user);
1600
+ Assert.Equal("newuser", user!.Username);
1601
+ Assert.Equal("newuser@example.com", user.Email);
1602
+ Assert.NotNull(user.Id);
1603
+ }
1604
+
1605
+ [Fact]
1606
+ public async Task GetUsers_WithoutAuthentication_ReturnsUnauthorized()
1607
+ {
1608
+ // Act
1609
+ var response = await _client.GetAsync("/api/users/protected-endpoint");
1610
+
1611
+ // Assert
1612
+ Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
1613
+ }
1614
+ }
1615
+ ```
1616
+
1617
+ ## Security Best Practices
1618
+
1619
+ ### Input Validation and Sanitization
1620
+
1621
+ ```csharp
1622
+ // Data annotations for validation
1623
+ public class CreateUserRequest
1624
+ {
1625
+ [Required(ErrorMessage = "Username is required")]
1626
+ [StringLength(50, MinimumLength = 3, ErrorMessage = "Username must be between 3 and 50 characters")]
1627
+ [RegularExpression(@"^[a-zA-Z0-9_]+$", ErrorMessage = "Username can only contain letters, numbers, and underscores")]
1628
+ public string Username { get; init; } = string.Empty;
1629
+
1630
+ [Required(ErrorMessage = "Email is required")]
1631
+ [EmailAddress(ErrorMessage = "Invalid email format")]
1632
+ [StringLength(100, ErrorMessage = "Email must be less than 100 characters")]
1633
+ public string Email { get; init; } = string.Empty;
1634
+
1635
+ [StringLength(50, ErrorMessage = "First name must be less than 50 characters")]
1636
+ [RegularExpression(@"^[a-zA-Z\s]+$", ErrorMessage = "First name can only contain letters and spaces")]
1637
+ public string? FirstName { get; init; }
1638
+
1639
+ [StringLength(50, ErrorMessage = "Last name must be less than 50 characters")]
1640
+ [RegularExpression(@"^[a-zA-Z\s]+$", ErrorMessage = "Last name can only contain letters and spaces")]
1641
+ public string? LastName { get; init; }
1642
+
1643
+ [StringLength(500, ErrorMessage = "Bio must be less than 500 characters")]
1644
+ [RegularExpression(@"^[^<>]*$", ErrorMessage = "Bio cannot contain HTML tags")]
1645
+ public string? Bio { get; init; }
1646
+ }
1647
+
1648
+ // Custom validation attributes
1649
+ public class StrongPasswordAttribute : ValidationAttribute
1650
+ {
1651
+ public StrongPasswordAttribute() : base("Password must be at least 8 characters and contain uppercase, lowercase, digit, and special character")
1652
+ {
1653
+ }
1654
+
1655
+ protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
1656
+ {
1657
+ if (value is string password)
1658
+ {
1659
+ if (password.Length < 8)
1660
+ {
1661
+ return new ValidationResult("Password must be at least 8 characters long");
1662
+ }
1663
+
1664
+ if (!Regex.IsMatch(password, @"[A-Z]"))
1665
+ {
1666
+ return new ValidationResult("Password must contain at least one uppercase letter");
1667
+ }
1668
+
1669
+ if (!Regex.IsMatch(password, @"[a-z]"))
1670
+ {
1671
+ return new ValidationResult("Password must contain at least one lowercase letter");
1672
+ }
1673
+
1674
+ if (!Regex.IsMatch(password, @"\d"))
1675
+ {
1676
+ return new ValidationResult("Password must contain at least one digit");
1677
+ }
1678
+
1679
+ if (!Regex.IsMatch(password, @"[!@#$%^&*(),.?""{}|<>]"))
1680
+ {
1681
+ return new ValidationResult("Password must contain at least one special character");
1682
+ }
1683
+
1684
+ return ValidationResult.Success;
1685
+ }
1686
+
1687
+ return new ValidationResult("Password is required");
1688
+ }
1689
+ }
88
1690
 
89
- ## Changelog
90
- - 2025-03-29: Input/output/failure response/reference information for each language has been specified.
1691
+ // HTML sanitization
1692
+ public static class HtmlSanitizer
1693
+ {
1694
+ private static readonly HtmlSanitizer _sanitizer = new HtmlSanitizer()
1695
+ .AllowTags("p", "br", "strong", "em", "u", "ol", "ul", "li")
1696
+ .AllowAttributes("class").OnAllTags()
1697
+ .RemoveAttributes("style", "onclick", "onload");
1698
+
1699
+ public static string Sanitize(string? html)
1700
+ {
1701
+ if (string.IsNullOrEmpty(html))
1702
+ {
1703
+ return string.Empty;
1704
+ }
1705
+
1706
+ return _sanitizer.Sanitize(html);
1707
+ }
1708
+ }
1709
+ ```
1710
+
1711
+ ### Authentication and Authorization
1712
+
1713
+ ```csharp
1714
+ // JWT authentication configuration
1715
+ public static class AuthenticationConfiguration
1716
+ {
1717
+ public static IServiceCollection AddJwtAuthentication(this IServiceCollection services,
1718
+ IConfiguration configuration)
1719
+ {
1720
+ var jwtSettings = configuration.GetSection("JwtSettings");
1721
+ var key = Encoding.UTF8.GetBytes(jwtSettings["Secret"]!);
1722
+
1723
+ services.AddAuthentication(options =>
1724
+ {
1725
+ options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
1726
+ options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
1727
+ })
1728
+ .AddJwtBearer(options =>
1729
+ {
1730
+ options.TokenValidationParameters = new TokenValidationParameters
1731
+ {
1732
+ ValidateIssuer = true,
1733
+ ValidateAudience = true,
1734
+ ValidateLifetime = true,
1735
+ ValidateIssuerSigningKey = true,
1736
+ ValidIssuer = jwtSettings["Issuer"],
1737
+ ValidAudience = jwtSettings["Audience"],
1738
+ IssuerSigningKey = new SymmetricSecurityKey(key),
1739
+ ClockSkew = TimeSpan.Zero
1740
+ };
1741
+ });
1742
+
1743
+ return services;
1744
+ }
1745
+ }
1746
+
1747
+ // JWT service
1748
+ public interface IJwtService
1749
+ {
1750
+ string GenerateToken(User user);
1751
+ ClaimsPrincipal? GetPrincipalFromToken(string token);
1752
+ }
1753
+
1754
+ public class JwtService : IJwtService
1755
+ {
1756
+ private readonly IConfiguration _configuration;
1757
+ private readonly string _secret;
1758
+ private readonly string _issuer;
1759
+ private readonly string _audience;
1760
+
1761
+ public JwtService(IConfiguration configuration)
1762
+ {
1763
+ _configuration = configuration;
1764
+ _secret = _configuration["JwtSettings:Secret"]!;
1765
+ _issuer = _configuration["JwtSettings:Issuer"]!;
1766
+ _audience = _configuration["JwtSettings:Audience"]!;
1767
+ }
1768
+
1769
+ public string GenerateToken(User user)
1770
+ {
1771
+ var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_secret));
1772
+ var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
1773
+
1774
+ var claims = new[]
1775
+ {
1776
+ new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
1777
+ new Claim(JwtRegisteredClaimNames.Email, user.Email.Value),
1778
+ new Claim(JwtRegisteredClaimNames.Name, user.Username),
1779
+ new Claim(ClaimTypes.Name, user.Username),
1780
+ new Claim(ClaimTypes.Email, user.Email.Value),
1781
+ new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
1782
+ new Claim("role", string.Join(",", user.Roles.Select(r => r.Name)))
1783
+ };
1784
+
1785
+ var token = new JwtSecurityToken(
1786
+ issuer: _issuer,
1787
+ audience: _audience,
1788
+ claims: claims,
1789
+ expires: DateTime.UtcNow.AddHours(24),
1790
+ signingCredentials: credentials);
1791
+
1792
+ return new JwtSecurityTokenHandler().WriteToken(token);
1793
+ }
1794
+
1795
+ public ClaimsPrincipal? GetPrincipalFromToken(string token)
1796
+ {
1797
+ try
1798
+ {
1799
+ var tokenHandler = new JwtSecurityTokenHandler();
1800
+ var key = Encoding.UTF8.GetBytes(_secret);
1801
+
1802
+ var validationParameters = new TokenValidationParameters
1803
+ {
1804
+ ValidateIssuer = true,
1805
+ ValidateAudience = true,
1806
+ ValidateLifetime = true,
1807
+ ValidateIssuerSigningKey = true,
1808
+ ValidIssuer = _issuer,
1809
+ ValidAudience = _audience,
1810
+ IssuerSigningKey = new SymmetricSecurityKey(key),
1811
+ ClockSkew = TimeSpan.Zero
1812
+ };
1813
+
1814
+ var principal = tokenHandler.ValidateToken(token, validationParameters, out var validatedToken);
1815
+
1816
+ return principal;
1817
+ }
1818
+ catch
1819
+ {
1820
+ return null;
1821
+ }
1822
+ }
1823
+ }
1824
+
1825
+ // Authorization policies
1826
+ public static class AuthorizationConfiguration
1827
+ {
1828
+ public static IServiceCollection AddAuthorizationPolicies(this IServiceCollection services)
1829
+ {
1830
+ services.AddAuthorization(options =>
1831
+ {
1832
+ options.AddPolicy("AdminOnly", policy =>
1833
+ policy.RequireRole("Admin"));
1834
+
1835
+ options.AddPolicy("UserOrAdmin", policy =>
1836
+ policy.RequireRole("User", "Admin"));
1837
+
1838
+ options.AddPolicy("CanManageUsers", policy =>
1839
+ policy.Requirements.Add(new CanManageUsersRequirement()));
1840
+
1841
+ options.AddPolicy("MinimumAge", policy =>
1842
+ policy.Requirements.Add(new MinimumAgeRequirement(18)));
1843
+ });
1844
+
1845
+ services.AddScoped<IAuthorizationHandler, CanManageUsersHandler>();
1846
+ services.AddScoped<IAuthorizationHandler, MinimumAgeHandler>();
1847
+
1848
+ return services;
1849
+ }
1850
+ }
1851
+
1852
+ // Custom authorization requirements
1853
+ public class CanManageUsersRequirement : IAuthorizationRequirement { }
1854
+
1855
+ public class MinimumAgeRequirement : IAuthorizationRequirement
1856
+ {
1857
+ public MinimumAgeRequirement(int minimumAge)
1858
+ {
1859
+ MinimumAge = minimumAge;
1860
+ }
1861
+
1862
+ public int MinimumAge { get; }
1863
+ }
1864
+
1865
+ // Authorization handlers
1866
+ public class CanManageUsersHandler : AuthorizationHandler<CanManageUsersRequirement>
1867
+ {
1868
+ protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
1869
+ CanManageUsersRequirement requirement)
1870
+ {
1871
+ if (context.User.IsInRole("Admin"))
1872
+ {
1873
+ context.Succeed(requirement);
1874
+ }
1875
+
1876
+ return Task.CompletedTask;
1877
+ }
1878
+ }
1879
+
1880
+ // Using authorization in controllers
1881
+ [ApiController]
1882
+ [Route("api/[controller]")]
1883
+ [Authorize]
1884
+ public class UsersController : ControllerBase
1885
+ {
1886
+ [HttpGet]
1887
+ [AllowAnonymous]
1888
+ public async Task<IActionResult> GetUsers()
1889
+ {
1890
+ // Public endpoint
1891
+ }
1892
+
1893
+ [HttpPost]
1894
+ [Authorize(Roles = "Admin")]
1895
+ public async Task<IActionResult> CreateUser()
1896
+ {
1897
+ // Admin only endpoint
1898
+ }
1899
+
1900
+ [HttpDelete("{id}")]
1901
+ [Authorize(Policy = "CanManageUsers")]
1902
+ public async Task<IActionResult> DeleteUser(Guid id)
1903
+ {
1904
+ // Users who can manage users
1905
+ }
1906
+ }
1907
+ ```
1908
+
1909
+ ### Security Headers and Middleware
1910
+
1911
+ ```csharp
1912
+ // Security headers middleware
1913
+ public class SecurityHeadersMiddleware
1914
+ {
1915
+ private readonly RequestDelegate _next;
1916
+
1917
+ public SecurityHeadersMiddleware(RequestDelegate next)
1918
+ {
1919
+ _next = next;
1920
+ }
1921
+
1922
+ public async Task InvokeAsync(HttpContext context)
1923
+ {
1924
+ // Add security headers
1925
+ context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
1926
+ context.Response.Headers.Add("X-Frame-Options", "DENY");
1927
+ context.Response.Headers.Add("X-XSS-Protection", "1; mode=block");
1928
+ context.Response.Headers.Add("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
1929
+ context.Response.Headers.Add("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'");
1930
+ context.Response.Headers.Add("Referrer-Policy", "strict-origin-when-cross-origin");
1931
+
1932
+ await _next(context);
1933
+ }
1934
+ }
1935
+
1936
+ // Rate limiting middleware
1937
+ public class RateLimitingMiddleware
1938
+ {
1939
+ private readonly RequestDelegate _next;
1940
+ private readonly IMemoryCache _cache;
1941
+ private readonly RateLimitOptions _options;
1942
+
1943
+ public RateLimitingMiddleware(RequestDelegate next, IMemoryCache cache, IOptions<RateLimitOptions> options)
1944
+ {
1945
+ _next = next;
1946
+ _cache = cache;
1947
+ _options = options.Value;
1948
+ }
1949
+
1950
+ public async Task InvokeAsync(HttpContext context)
1951
+ {
1952
+ var clientId = GetClientId(context);
1953
+ var cacheKey = $"rate_limit_{clientId}";
1954
+
1955
+ var requestCount = await _cache.GetOrCreateAsync(cacheKey, async () =>
1956
+ {
1957
+ await Task.CompletedTask;
1958
+ return 1;
1959
+ }, new MemoryCacheEntryOptions
1960
+ {
1961
+ AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1),
1962
+ SlidingExpiration = TimeSpan.FromSeconds(30)
1963
+ });
1964
+
1965
+ if (requestCount > _options.MaxRequestsPerMinute)
1966
+ {
1967
+ context.Response.StatusCode = StatusCodes.Status429TooManyRequests;
1968
+ await context.Response.WriteAsync("Rate limit exceeded");
1969
+ return;
1970
+ }
1971
+
1972
+ await _next(context);
1973
+ }
1974
+
1975
+ private string GetClientId(HttpContext context)
1976
+ {
1977
+ // Try to get user ID from claims, otherwise use IP address
1978
+ var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
1979
+ return userId ?? context.Connection.RemoteIpAddress?.ToString() ?? "anonymous";
1980
+ }
1981
+ }
1982
+
1983
+ public class RateLimitOptions
1984
+ {
1985
+ public int MaxRequestsPerMinute { get; set; } = 100;
1986
+ }
1987
+
1988
+ // Configure middleware in Program.cs
1989
+ app.UseMiddleware<SecurityHeadersMiddleware>();
1990
+ app.UseMiddleware<RateLimitingMiddleware>();
1991
+ ```
91
1992
 
92
- ## Works well with
1993
+ ## Integration Patterns
1994
+
1995
+ ### Entity Framework Core Integration
1996
+
1997
+ ```csharp
1998
+ // Repository pattern with Unit of Work
1999
+ public interface IUnitOfWork : IDisposable
2000
+ {
2001
+ IUserRepository Users { get; }
2002
+ Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
2003
+ Task BeginTransactionAsync(CancellationToken cancellationToken = default);
2004
+ Task CommitAsync(CancellationToken cancellationToken = default);
2005
+ Task RollbackAsync(CancellationToken cancellationToken = default);
2006
+ }
2007
+
2008
+ public class UnitOfWork : IUnitOfWork
2009
+ {
2010
+ private readonly AppDbContext _context;
2011
+ private IDbContextTransaction? _transaction;
2012
+
2013
+ public UnitOfWork(AppDbContext context)
2014
+ {
2015
+ _context = context;
2016
+ Users = new UserRepository(_context);
2017
+ }
2018
+
2019
+ public IUserRepository Users { get; }
2020
+
2021
+ public async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
2022
+ {
2023
+ return await _context.SaveChangesAsync(cancellationToken);
2024
+ }
2025
+
2026
+ public async Task BeginTransactionAsync(CancellationToken cancellationToken = default)
2027
+ {
2028
+ _transaction = await _context.Database.BeginTransactionAsync(cancellationToken);
2029
+ }
2030
+
2031
+ public async Task CommitAsync(CancellationToken cancellationToken = default)
2032
+ {
2033
+ try
2034
+ {
2035
+ await _context.SaveChangesAsync(cancellationToken);
2036
+ await _transaction?.CommitAsync(cancellationToken)!;
2037
+ }
2038
+ catch
2039
+ {
2040
+ await RollbackAsync(cancellationToken);
2041
+ throw;
2042
+ }
2043
+ }
2044
+
2045
+ public async Task RollbackAsync(CancellationToken cancellationToken = default)
2046
+ {
2047
+ if (_transaction != null)
2048
+ {
2049
+ await _transaction.RollbackAsync(cancellationToken);
2050
+ }
2051
+ }
2052
+
2053
+ public void Dispose()
2054
+ {
2055
+ _transaction?.Dispose();
2056
+ _context.Dispose();
2057
+ }
2058
+ }
2059
+
2060
+ // Service with Unit of Work
2061
+ public class UserService
2062
+ {
2063
+ private readonly IUnitOfWork _unitOfWork;
2064
+ private readonly ILogger<UserService> _logger;
2065
+
2066
+ public UserService(IUnitOfWork unitOfWork, ILogger<UserService> logger)
2067
+ {
2068
+ _unitOfWork = unitOfWork;
2069
+ _logger = logger;
2070
+ }
2071
+
2072
+ public async Task<User> CreateUserAsync(CreateUserRequest request,
2073
+ CancellationToken cancellationToken = default)
2074
+ {
2075
+ await _unitOfWork.BeginTransactionAsync(cancellationToken);
2076
+
2077
+ try
2078
+ {
2079
+ var user = new User(request.Username, Email.Create(request.Email),
2080
+ new UserProfile(request.FirstName, request.LastName, request.Bio));
2081
+
2082
+ await _unitOfWork.Users.AddAsync(user, cancellationToken);
2083
+ await _unitOfWork.CommitAsync(cancellationToken);
2084
+
2085
+ _logger.LogInformation("User created successfully with ID: {UserId}", user.Id);
2086
+ return user;
2087
+ }
2088
+ catch
2089
+ {
2090
+ await _unitOfWork.RollbackAsync(cancellationToken);
2091
+ throw;
2092
+ }
2093
+ }
2094
+ }
2095
+ ```
2096
+
2097
+ ### Message Queue Integration
2098
+
2099
+ ```csharp
2100
+ // RabbitMQ integration
2101
+ public interface IMessagePublisher
2102
+ {
2103
+ Task PublishAsync<T>(T message, string routingKey, CancellationToken cancellationToken = default);
2104
+ }
2105
+
2106
+ public class RabbitMqPublisher : IMessagePublisher
2107
+ {
2108
+ private readonly IConnection _connection;
2109
+ private readonly ILogger<RabbitMqPublisher> _logger;
2110
+
2111
+ public RabbitMqPublisher(IConnection connection, ILogger<RabbitMqPublisher> logger)
2112
+ {
2113
+ _connection = connection;
2114
+ _logger = logger;
2115
+ }
2116
+
2117
+ public async Task PublishAsync<T>(T message, string routingKey,
2118
+ CancellationToken cancellationToken = default)
2119
+ {
2120
+ using var channel = await _connection.CreateChannelAsync(cancellationToken);
2121
+
2122
+ await channel.ExchangeDeclareAsync("app_exchange", ExchangeType.Direct, durable: true,
2123
+ cancellationToken: cancellationToken);
2124
+
2125
+ var messageBody = JsonSerializer.SerializeToUtf8Bytes(message);
2126
+
2127
+ await channel.BasicPublishAsync("app_exchange", routingKey, true, new BasicProperties
2128
+ {
2129
+ ContentType = "application/json",
2130
+ DeliveryMode = DeliveryModes.Persistent
2131
+ }, messageBody, cancellationToken);
2132
+
2133
+ _logger.LogInformation("Message published to routing key: {RoutingKey}", routingKey);
2134
+ }
2135
+ }
2136
+
2137
+ // Message consumer
2138
+ public interface IMessageConsumer<T>
2139
+ {
2140
+ Task ConsumeAsync(T message, CancellationToken cancellationToken = default);
2141
+ }
2142
+
2143
+ public class UserCreatedEventConsumer : BackgroundService
2144
+ {
2145
+ private readonly IConnection _connection;
2146
+ private readonly IServiceScopeFactory _scopeFactory;
2147
+ private readonly ILogger<UserCreatedEventConsumer> _logger;
2148
+
2149
+ public UserCreatedEventConsumer(IConnection connection, IServiceScopeFactory scopeFactory,
2150
+ ILogger<UserCreatedEventConsumer> logger)
2151
+ {
2152
+ _connection = connection;
2153
+ _scopeFactory = scopeFactory;
2154
+ _logger = logger;
2155
+ }
2156
+
2157
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
2158
+ {
2159
+ using var channel = await _connection.CreateChannelAsync(stoppingToken);
2160
+
2161
+ await channel.QueueDeclareAsync("user_created_queue", durable: true, exclusive: false,
2162
+ autoDelete: false, cancellationToken: stoppingToken);
2163
+
2164
+ await channel.QueueBindAsync("user_created_queue", "app_exchange", "user.created",
2165
+ cancellationToken: stoppingToken);
2166
+
2167
+ var consumer = new AsyncEventingBasicConsumer(channel);
2168
+ consumer.ReceivedAsync += async (sender, args) =>
2169
+ {
2170
+ try
2171
+ {
2172
+ var message = JsonSerializer.Deserialize<UserCreatedEvent>(args.Body.ToArray());
2173
+
2174
+ if (message != null)
2175
+ {
2176
+ await HandleUserCreatedEvent(message, stoppingToken);
2177
+ }
2178
+
2179
+ await channel.BasicAckAsync(args.DeliveryTag, false, stoppingToken);
2180
+ }
2181
+ catch (Exception ex)
2182
+ {
2183
+ _logger.LogError(ex, "Error processing user created event");
2184
+ await channel.BasicNackAsync(args.DeliveryTag, false, true, stoppingToken);
2185
+ }
2186
+ };
2187
+
2188
+ await channel.BasicConsumeAsync("user_created_queue", false, consumer, stoppingToken);
2189
+
2190
+ _logger.LogInformation("User created event consumer started");
2191
+
2192
+ // Keep the consumer running
2193
+ while (!stoppingToken.IsCancellationRequested)
2194
+ {
2195
+ await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken);
2196
+ }
2197
+ }
2198
+
2199
+ private async Task HandleUserCreatedEvent(UserCreatedEvent @event,
2200
+ CancellationToken cancellationToken = default)
2201
+ {
2202
+ using var scope = _scopeFactory.CreateScope();
2203
+ var emailService = scope.ServiceProvider.GetRequiredService<IEmailService>();
2204
+
2205
+ await emailService.SendWelcomeEmailAsync(@event.Email, @event.FirstName, @event.LastName, cancellationToken);
2206
+
2207
+ _logger.LogInformation("Processed user created event for user: {UserId}", @event.UserId);
2208
+ }
2209
+ }
2210
+
2211
+ public record UserCreatedEvent(
2212
+ Guid UserId,
2213
+ string Email,
2214
+ string FirstName,
2215
+ string LastName,
2216
+ DateTime CreatedAt);
2217
+ ```
2218
+
2219
+ ## Modern Development Workflow
2220
+
2221
+ ### Project Configuration
2222
+
2223
+ ```xml
2224
+ <!-- Directory.Build.props -->
2225
+ <Project>
2226
+ <PropertyGroup>
2227
+ <TargetFramework>net8.0</TargetFramework>
2228
+ <ImplicitUsings>enable</ImplicitUsings>
2229
+ <Nullable>enable</Nullable>
2230
+ <LangVersion>12.0</LangVersion>
2231
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
2232
+ <NoWarn>$(NoWarn);1591</NoWarn>
2233
+ </PropertyGroup>
2234
+
2235
+ <ItemGroup>
2236
+ <PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
2237
+ <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
2238
+ <PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
2239
+ <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
2240
+ </ItemGroup>
2241
+ </Project>
2242
+
2243
+ <!-- MyApi.csproj -->
2244
+ <Project Sdk="Microsoft.NET.Sdk.Web">
2245
+ <PropertyGroup>
2246
+ <AssemblyName>MyApi</AssemblyName>
2247
+ <RootNamespace>MyApi</RootNamespace>
2248
+ </PropertyGroup>
2249
+
2250
+ <ItemGroup>
2251
+ <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0" />
2252
+ <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" />
2253
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0" />
2254
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0" />
2255
+ <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
2256
+ <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.0" />
2257
+ <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
2258
+ <PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
2259
+ <PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
2260
+ <PackageReference Include="Serilog.Sinks.Console" Version="5.0.0" />
2261
+ <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
2262
+ </ItemGroup>
2263
+ </Project>
2264
+ ```
2265
+
2266
+ ### Docker Configuration
2267
+
2268
+ ```dockerfile
2269
+ # Multi-stage Dockerfile for .NET 8.0
2270
+ FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
2271
+ WORKDIR /app
2272
+
2273
+ # Copy csproj and restore dependencies
2274
+ COPY *.csproj ./
2275
+ RUN dotnet restore
2276
+
2277
+ # Copy everything else and build
2278
+ COPY . ./
2279
+ RUN dotnet publish -c Release -o out
2280
+
2281
+ # Build runtime image
2282
+ FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
2283
+ WORKDIR /app
2284
+ COPY --from=build-env /app/out .
2285
+
2286
+ # Create non-root user
2287
+ RUN adduser --disabled-password --gecos '' appuser && chown -R appuser /app
2288
+ USER appuser
2289
+
2290
+ EXPOSE 8080
2291
+ ENV ASPNETCORE_URLS=http://+:8080
2292
+ ENV ASPNETCORE_ENVIRONMENT=Production
2293
+
2294
+ ENTRYPOINT ["dotnet", "MyApi.dll"]
2295
+ ```
2296
+
2297
+ ### CI/CD Configuration
2298
+
2299
+ ```yaml
2300
+ # .github/workflows/dotnet.yml
2301
+ name: .NET CI/CD
2302
+
2303
+ on:
2304
+ push:
2305
+ branches: [ main, develop ]
2306
+ pull_request:
2307
+ branches: [ main ]
2308
+
2309
+ env:
2310
+ DOTNET_VERSION: '8.0.x'
2311
+ SOLUTION_FILE: 'MySolution.sln'
2312
+
2313
+ jobs:
2314
+ test:
2315
+ runs-on: ubuntu-latest
2316
+
2317
+ steps:
2318
+ - uses: actions/checkout@v4
2319
+
2320
+ - name: Setup .NET
2321
+ uses: actions/setup-dotnet@v4
2322
+ with:
2323
+ dotnet-version: ${{ env.DOTNET_VERSION }}
2324
+
2325
+ - name: Cache dependencies
2326
+ uses: actions/cache@v3
2327
+ with:
2328
+ path: ~/.nuget/packages
2329
+ key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
2330
+ restore-keys: |
2331
+ ${{ runner.os }}-nuget-
2332
+
2333
+ - name: Restore dependencies
2334
+ run: dotnet restore ${{ env.SOLUTION_FILE }}
2335
+
2336
+ - name: Build
2337
+ run: dotnet build ${{ env.SOLUTION_FILE }} --no-restore --configuration Release
2338
+
2339
+ - name: Run tests
2340
+ run: dotnet test ${{ env.SOLUTION_FILE }} --no-build --configuration Release --verbosity normal --collect:"XPlat Code Coverage"
2341
+
2342
+ - name: Upload coverage to Codecov
2343
+ uses: codecov/codecov-action@v3
2344
+ with:
2345
+ file: '**/coverage.cobertura.xml'
2346
+
2347
+ - name: Run security scan
2348
+ run: |
2349
+ dotnet tool install --global dotnet-ssh --version 0.2.0
2350
+ dotnet-ssh scan MyApi/bin/Release/net8.0/MyApi.dll
2351
+
2352
+ build-and-deploy:
2353
+ needs: test
2354
+ runs-on: ubuntu-latest
2355
+ if: github.ref == 'refs/heads/main'
2356
+
2357
+ steps:
2358
+ - uses: actions/checkout@v4
2359
+
2360
+ - name: Setup .NET
2361
+ uses: actions/setup-dotnet@v4
2362
+ with:
2363
+ dotnet-version: ${{ env.DOTNET_VERSION }}
2364
+
2365
+ - name: Build and publish
2366
+ run: dotnet publish ${{ env.SOLUTION_FILE }} --configuration Release --output ./publish
2367
+
2368
+ - name: Build Docker image
2369
+ run: |
2370
+ docker build -t myregistry.com/myapi:${{ github.sha }} .
2371
+ docker tag myregistry.com/myapi:${{ github.sha }} myregistry.com/myapi:latest
2372
+
2373
+ - name: Push Docker image
2374
+ if: github.ref == 'refs/heads/main'
2375
+ run: |
2376
+ echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
2377
+ docker push myregistry.com/myapi:${{ github.sha }}
2378
+ docker push myregistry.com/myapi:latest
2379
+ ```
2380
+
2381
+ ---
93
2382
 
94
- - alfred-trust-validation (coverage verification)
95
- - alfred-code-reviewer (C#-specific review)
96
- - web-api-expert (ASP.NET Core API development)
2383
+ **Created by**: MoAI Language Skill Factory
2384
+ **Last Updated**: 2025-11-06
2385
+ **Version**: 2.0.0
2386
+ **C# Target**: .NET 8.0 LTS with C# 12.0 and modern ASP.NET Core patterns
97
2387
 
98
- ## Best Practices
99
- - Enable automatic validation by matching your linter with the language's official style guide.
100
- - Fix test/build pipelines with reproducible commands in CI.
2388
+ This skill provides comprehensive C# development guidance with 2025 best practices, covering everything from modern ASP.NET Core APIs to Entity Framework Core optimization and cross-platform development with .NET MAUI.