enhance UX
ยท 1 year ago
558f3605f4e817099b863dbc9dd9020a3c9c6ea1
Parent:
d0d5ab434
1 file changed +308 โ185
- clean-architecture-dotnet-cheatsheet.html +308 โ185
Diff
--- a/clean-architecture-dotnet-cheatsheet.html +++ b/clean-architecture-dotnet-cheatsheet.html @@ -3,125 +3,155 @@ <head> <meta charset="utf-8" /> <meta content="width=device-width, initial-scale=1.0" name="viewport" /> - <title>Clean Architecture in .NET Web API: Comprehensive Cheatsheet</title> + <title>Clean Architecture in .NET Web API: Comprehensive Cheatsheet v2</title> <!-- SEO Meta Description --> - <meta content="A comprehensive cheatsheet for Clean Architecture in .NET Web API projects, covering core principles, layers (Domain, Application, Infrastructure, Presentation), project structure, C# examples, and best practices." name="description" /> + <meta content="A comprehensive cheatsheet for Clean Architecture in .NET Web API projects, covering core principles, layers (Domain, Application, Infrastructure, Presentation), project structure, C# examples, and best practices. Version 2 with improved UX." name="description" /> <!-- Keywords --> - <meta content="Clean Architecture, .NET, Web API, ASP.NET Core, Software Architecture, Domain-Driven Design, DDD, C#, Microservices, Software Design Patterns, .NET Cheatsheet, Developer Guide" name="keywords" /> + <meta content="Clean Architecture, .NET, Web API, ASP.NET Core, Software Architecture, Domain-Driven Design, DDD, C#, Microservices, Software Design Patterns, .NET Cheatsheet, Developer Guide, UX" name="keywords" /> <!-- Canonical URL --> - <link href="https://cheatsheets.davidveksler.com/clean-architecture-dotnet-cheatsheet.html" rel="canonical" /> - <!-- Favicon (using an appropriate emoji for architecture/layers) --> - <link href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>๐งฑ</text></svg>" rel="icon"/> + <link href="https://cheatsheets.davidveksler.com/clean-architecture-dotnet-cheatsheet-v2.html" rel="canonical" /> + <!-- Favicon --> + <link href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>๐๏ธ</text></svg>" rel="icon"/> <!-- Social Media Metadata (Open Graph) --> - <meta content="Clean Architecture in .NET Web API: Comprehensive Cheatsheet" property="og:title"/> - <meta content="Explore Clean Architecture for .NET Web API with layers, principles, C# examples, project structure, and best practices. Ideal for .NET developers and architects." property="og:description"/> + <meta content="Clean Architecture in .NET Web API: Comprehensive Cheatsheet v2" property="og:title"/> + <meta content="Explore Clean Architecture for .NET Web API with layers, principles, C# examples, project structure, and best practices. Enhanced UX in Version 2." property="og:description"/> <meta content="website" property="og:type"/> - <meta content="https://cheatsheets.davidveksler.com/clean-architecture-dotnet-cheatsheet.html" property="og:url"/> - <meta content="https://cheatsheets.davidveksler.com/images/clean-architecture-dotnet-cheatsheet.png" property="og:image"/> - <meta content="Visual diagram illustrating the layers of Clean Architecture in a .NET context." property="og:image:alt"/> + <meta content="https://cheatsheets.davidveksler.com/clean-architecture-dotnet-cheatsheet-v2.html" property="og:url"/> + <meta content="https://cheatsheets.davidveksler.com/images/clean-architecture-dotnet-cheatsheet-v2.png" property="og:image"/> + <meta content="Visual diagram illustrating the layers of Clean Architecture in a .NET context, version 2." property="og:image:alt"/> <!-- Twitter Card Metadata --> <meta content="summary_large_image" name="twitter:card"/> - <meta content="Clean Architecture in .NET Web API: Comprehensive Cheatsheet" name="twitter:title"/> - <meta content="Your go-to guide for implementing Clean Architecture in .NET Web API projects. Covers layers, code examples, best practices, and more." name="twitter:description"/> - <meta content="https://cheatsheets.davidveksler.com/images/clean-architecture-dotnet-cheatsheet.png" name="twitter:image"/> - <meta content="Illustration of Clean Architecture layers in .NET." name="twitter:image:alt"/> + <meta content="Clean Architecture in .NET Web API: Comprehensive Cheatsheet v2" name="twitter:title"/> + <meta content="Your go-to guide for implementing Clean Architecture in .NET Web API projects. Covers layers, code examples, best practices, and more. Enhanced UX in Version 2." name="twitter:description"/> + <meta content="https://cheatsheets.davidveksler.com/images/clean-architecture-dotnet-cheatsheet-v2.png" name="twitter:image"/> + <meta content="Illustration of Clean Architecture layers in .NET, version 2." name="twitter:image:alt"/> <!-- Structured Data (JSON-LD) --> <script type="application/ld+json"> { "@context": "https://schema.org", "@type": "TechArticle", - "headline": "Clean Architecture in .NET Web API: Comprehensive Cheatsheet", - "description": "A comprehensive guide to Clean Architecture in .NET Web API, detailing layers, core principles, project structure, C# examples, and best practices for developers and architects.", - "image": "https://cheatsheets.davidveksler.com/images/clean-architecture-dotnet-cheatsheet.png", + "headline": "Clean Architecture in .NET Web API: Comprehensive Cheatsheet Version 2", + "description": "A comprehensive guide to Clean Architecture in .NET Web API, detailing layers, core principles, project structure, C# examples, and best practices for developers and architects, with an improved user experience.", + "image": "https://cheatsheets.davidveksler.com/images/clean-architecture-dotnet-cheatsheet-v2.png", "author": { "@type": "Person", "name": "David Veksler (AI Generated)" }, "publisher": { "@type": "Organization", - "name": "David Veksler Cheatsheets", - "logo": { - "@type": "ImageObject", - "url": "https://cheatsheets.davidveksler.com/images/logo-placeholder.png" - } + "name": "David Veksler Cheatsheets" }, - "datePublished": "2025-06-06", - "dateModified": "2025-06-06", - "keywords": "Clean Architecture, .NET, Web API, ASP.NET Core, C#, Software Architecture, DDD, Software Design Patterns" + "datePublished": "2025-06-07", + "dateModified": "2025-06-07", + "keywords": "Clean Architecture, .NET, Web API, ASP.NET Core, C#, Software Architecture, DDD, UX" } </script> <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"/> <link href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css" rel="stylesheet"/> + <link rel="preconnect" href="https://fonts.googleapis.com"> + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> + <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Roboto+Slab:wght@400;700&display=swap" rel="stylesheet"> <style> :root { - --bs-body-bg: #f4f6f8; /* Light gray background */ - --bs-primary: #0D47A1; /* Deep Blue for primary accent */ - --bs-primary-dark: #002171; - --bs-primary-light: #5472d3; - --card-border-color: #d1d9e0; - --card-shadow-color: rgba(0, 0, 0, 0.07); - --text-color-main: #212529; - --text-color-secondary: #495057; - --text-color-highlight: var(--bs-primary-dark); - --blueprint-grid-color: rgba(13, 71, 161, 0.05); + --bs-body-bg: #f8f9fa; + --bs-primary: #005A9C; /* A more professional, slightly muted blue */ + --bs-primary-dark: #003E6B; + --bs-primary-light: #63A4FF; + --card-border-color: #dee2e6; + --card-shadow-color: rgba(0, 0, 0, 0.08); + --text-color-main: #2a363b; /* Darker grey for main text */ + --text-color-secondary: #5a6268; /* Lighter grey for secondary text */ + --text-color-headings: #004085; /* Dark blue for headings */ + --code-bg-color: #2b303b; /* Consistent dark bg for code */ + --code-text-color: #c0c5ce; /* Category Colors */ - --color-core-principles: #0D47A1; /* Primary Blue */ - --color-layers-overview: #004D40; /* Dark Teal */ - --color-domain-layer: #1E8449; /* Green */ - --color-application-layer: #6A1B9A;/* Purple */ - --color-infrastructure-layer: #A15C0D;/* Orange/Brown */ - --color-presentation-layer: #00838F; /* Teal/Cyan */ - --color-project-structure: #455A64; /* Blue Grey */ + --color-core-principles: #005A9C; + --color-layers-overview: #00695C; /* Darker Teal */ + --color-domain-layer: #2E7D32; /* Darker Green */ + --color-application-layer: #5E35B1;/* Deep Purple */ + --color-infrastructure-layer: #E65100;/* Dark Orange */ + --color-presentation-layer: #00838F; /* Strong Cyan */ + --color-project-structure: #4E342E; /* Dark Brown */ --color-best-practices: #37474F; /* Dark Slate Grey */ - --current-category-color: var(--bs-primary); /* Default */ + --current-category-color: var(--bs-primary); } body { background-color: var(--bs-body-bg); - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + font-family: 'Inter', sans-serif; color: var(--text-color-main); + line-height: 1.7; } .page-header { - background: linear-gradient(135deg, #e3f2fd, #bbdefb); /* Light blue gradient */ - padding: 2.5rem 1.5rem; + background: linear-gradient(135deg, var(--bs-primary) 0%, var(--bs-primary-dark) 100%), + url('data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22100%22%20height%3D%22100%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%3E%3Cdefs%3E%3Cpattern%20id%3D%22subtleDots%22%20width%3D%2210%22%20height%3D%2210%22%20patternUnits%3D%22userSpaceOnUse%22%3E%3Ccircle%20fill%3D%22rgba(255%2C255%2C255%2C0.05)%22%20cx%3D%225%22%20cy%3D%225%22%20r%3D%221%22/%3E%3C/pattern%3E%3C/defs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%23subtleDots)%22/%3E%3C/svg%3E'); + color: #fff; + padding: 3rem 1.5rem; text-align: center; - border-bottom: 2px solid var(--bs-primary-light); - margin-bottom: 2rem; + border-bottom: 5px solid var(--bs-primary-light); + margin-bottom: 2.5rem; + box-shadow: 0 4px 15px rgba(0,0,0,0.1); } .page-header h1 { - color: var(--bs-primary-dark); - font-weight: 600; - font-size: 2.5rem; + font-family: 'Roboto Slab', serif; + font-weight: 700; + font-size: 2.8rem; + letter-spacing: 0.5px; + text-shadow: 1px 1px 3px rgba(0,0,0,0.2); + } + .page-header h1 .bi { font-size: 1em; vertical-align: -0.1em; margin-right: 0.5em;} + .page-header .lead { font-size: 1.15rem; max-width: 900px; margin: 0.5rem auto 0; opacity: 0.95; } + .page-header .text-muted { color: rgba(255,255,255,0.75) !important; font-size: 0.9rem; } + + .global-controls { + background-color: #fff; + padding: 1rem; + border-radius: 8px; + margin-bottom: 2rem; + box-shadow: 0 2px 8px rgba(0,0,0,0.06); + display: flex; + gap: 0.5rem; } - .page-header h1 .bi { font-size: 0.9em; vertical-align: -0.05em; margin-right: 0.4em;} - .page-header .lead { color: #343a40; font-size: 1.1rem; max-width: 900px; margin: auto; } .schema-container { - background-color: rgba(255, 255, 255, 0.85); + background-color: #ffffff; border: 1px solid var(--card-border-color); - border-left: 5px solid var(--current-category-color); - border-radius: 8px; - padding: 1.5rem; - margin-bottom: 2.5rem; - box-shadow: 0 4px 12px var(--card-shadow-color); + border-radius: 12px; /* Softer radius */ + padding: 2rem; /* More padding */ + margin-bottom: 3rem; /* More spacing */ + box-shadow: 0 6px 18px var(--card-shadow-color); + position: relative; + overflow: hidden; /* For pseudo-elements */ } + .schema-container::before { /* Decorative top border */ + content: ''; + position: absolute; + top: 0; left: 0; right: 0; + height: 6px; + background-color: var(--current-category-color); + border-radius: 11px 11px 0 0; + } + .section-title { - color: var(--current-category-color); - margin-bottom: 1.5rem; - font-weight: 600; - font-size: 1.75rem; - border-bottom: 2px solid var(--current-category-color); - padding-bottom: 0.5rem; + font-family: 'Roboto Slab', serif; + color: var(--text-color-headings); + margin-top: -0.5rem; /* Pull up slightly */ + margin-bottom: 2rem; /* More space after title */ + font-weight: 700; + font-size: 2rem; /* Larger */ + padding-bottom: 0.75rem; display: flex; align-items: center; + position: relative; + z-index: 1; /* Above schema-container's ::before */ } - .section-title .bi { margin-right: 0.75rem; font-size: 1.5em; } + .section-title .bi { margin-right: 1rem; font-size: 1.8em; color: var(--current-category-color); opacity: 0.8;} - /* Applying category colors */ + /* Applying category colors to section titles (used by .schema-container ::before) */ .section-core-principles { --current-category-color: var(--color-core-principles); } .section-layers-overview { --current-category-color: var(--color-layers-overview); } .section-project-structure { --current-category-color: var(--color-project-structure); } @@ -129,35 +159,42 @@ .info-card { background: #fff; - border: 1px solid var(--card-border-color); - border-left: 4px solid var(--current-category-color); /* Uses card's specific color */ - border-radius: 6px; - box-shadow: 0 3px 8px var(--card-shadow-color); + border: 1px solid #e0e6ed; /* Softer border */ + border-top: 5px solid var(--current-category-color); /* Category color top border */ + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0,0,0,0.06); /* Softer shadow */ height: 100%; display: flex; flex-direction: column; - margin-bottom: 1.5rem; + margin-bottom: 2rem; /* More space between cards */ + transition: transform 0.2s ease-out, box-shadow 0.2s ease-out; + } + .info-card:hover { + transform: translateY(-4px); + box-shadow: 0 8px 20px rgba(0,0,0,0.09); } .info-card .card-header { - color: #fff; background-color: var(--current-category-color); /* Uses card's specific color */ - font-size: 1.2rem; text-align: center; margin: 0; padding: 0.8rem 0.6rem; - font-weight: 600; display: flex; justify-content: center; align-items: center; - gap: .6rem; - border-bottom: 1px solid var(--card-border-color); - border-radius: 5px 5px 0 0; + color: var(--current-category-color); + background-color: #f8f9fc; /* Very light bg for header */ + font-size: 1.3rem; /* Larger header text */ + text-align: left; /* Align left */ + padding: 1rem 1.25rem; /* More padding */ + font-weight: 700; /* Bolder */ + font-family: 'Roboto Slab', serif; + display: flex; align-items: center; gap: .75rem; + border-bottom: 1px solid #e0e6ed; + border-radius: 7px 7px 0 0; } - .info-card .card-header .bi { font-size: 1.3em; opacity: 0.9; } - .info-card .card-body { padding: 1.25rem; flex-grow: 1; } - .info-card h5 { font-weight: 600; color: var(--text-color-highlight); margin-bottom: 0.75rem; } - .info-card p, .info-card ul, .info-card dl { font-size: 0.95rem; color: var(--text-color-secondary); line-height: 1.6; } - .info-card ul { padding-left: 1.25rem; } - .info-card li { margin-bottom: 0.5rem; } - .info-card dl dt { font-weight: 600; color: var(--text-color-main); margin-top: 0.5rem; } - .info-card dl dd { margin-left: 1rem; margin-bottom: 0.5rem; } + .info-card .card-header .bi { font-size: 1.5em; opacity: 0.85; } + .info-card .card-body { padding: 1.5rem; flex-grow: 1; } + .info-card h5 { font-weight: 600; color: var(--text-color-headings); margin-bottom: 0.75rem; font-family: 'Inter', sans-serif; font-size:1.1rem; } + .info-card p, .info-card ul, .info-card dl { font-size: 0.98rem; color: var(--text-color-secondary); line-height: 1.75; } + .info-card ul { padding-left: 1.5rem; list-style-type: 'โ '; /* Unicode checkmark */ } + .info-card li { margin-bottom: 0.6rem; padding-left: 0.5rem; } - /* Card-specific colors */ + /* Card-specific category colors */ .card-core-philosophy { --current-category-color: var(--color-core-principles); } .card-when-to-use { --current-category-color: var(--color-core-principles); } .card-domain-layer { --current-category-color: var(--color-domain-layer); } @@ -169,66 +206,85 @@ .details-toggle { - font-size: 0.9rem; - padding: 0.4rem 0.8rem; color: var(--bs-primary); - border: 1px solid var(--bs-primary); background-color: transparent; + font-size: 0.9rem; font-weight: 500; + padding: 0.5rem 1rem; color: var(--current-category-color); + border: 2px solid var(--current-category-color); background-color: transparent; transition: background-color 0.2s ease, color 0.2s ease; - display: inline-flex; align-items: center; gap: 0.3em; - border-radius: 4px; margin-top: 1rem; + display: inline-flex; align-items: center; gap: 0.4em; + border-radius: 6px; margin-top: 1.25rem; } - .details-toggle:hover { background-color: var(--bs-primary); color: white; } + .details-toggle:hover { background-color: var(--current-category-color); color: white; } .details-toggle .bi { transition: transform 0.2s ease-in-out; } .details-toggle[aria-expanded="true"] .bi { transform: rotate(180deg); } .collapse-content { - font-size: 0.9rem; - border-top: 1px solid #e0e0e0; - padding-top: 1rem; margin-top: 1rem; - background-color: #fdfdff; /* Slightly off-white for contrast */ - padding: 1rem; border-radius: 4px; + font-size: 0.95rem; + border-top: 1px solid #e9ecef; /* Lighter separator */ + padding-top: 1.25rem; margin-top: 1.25rem; + background-color: #fbfcfe; /* Even lighter for content area */ + padding: 1.25rem; border-radius: 6px; } - .collapse-content h6 { font-weight: 600; color: var(--text-color-highlight); margin-top: 0.8rem; margin-bottom: 0.4rem; font-size: 1rem; } - .collapse-content ul { padding-left: 1.2rem; margin-bottom: 0.8rem; } - .collapse-content li { margin-bottom: 0.4rem; font-size: 0.9rem; } + .collapse-content h6 { font-weight: 700; color: var(--text-color-headings); margin-top: 1rem; margin-bottom: 0.5rem; font-size: 1.05rem; font-family: 'Inter', sans-serif;} + .collapse-content ul { padding-left: 1.5rem; margin-bottom: 1rem; list-style-type: 'โ '; } + .collapse-content li { margin-bottom: 0.5rem; font-size: 0.92rem; padding-left: 0.3rem;} .collapse-content code { - font-size: 0.85em; - color: #c7254e; /* Bootstrap's code color */ - background-color: #f9f2f4; /* Bootstrap's code background */ - padding: 0.2em 0.4em; - border-radius: 3px; - font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-size: 0.88em; + color: #b72e52; /* Adjusted for better contrast */ + background-color: #fceef2; /* Light pinkish bg */ + padding: 0.25em 0.5em; + border-radius: 4px; + font-family: 'SFMono-Regular', Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } .collapse-content pre { - background-color: #282c34; /* Dark background for code blocks */ - color: #abb2bf; /* Light text color for dark background */ - padding: 1em; - border-radius: 6px; + background-color: var(--code-bg-color); + color: var(--code-text-color); + padding: 1.25em; /* More padding */ + border-radius: 8px; /* Softer radius */ overflow-x: auto; - font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - font-size: 0.875em; - line-height: 1.45; + font-family: 'SFMono-Regular', Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-size: 0.9em; + line-height: 1.6; + border: 1px solid #444; /* Subtle border for pre */ } .collapse-content pre code { background-color: transparent; color: inherit; padding: 0; border-radius: 0; + font-size: 1em; /* Inherit pre's font size */ + } + .collapse-content pre::before { /* Language hint */ + content: "C#"; + display: block; + text-align: right; + font-size: 0.8em; + color: #777; + margin-bottom: -1em; /* Pull it up a bit */ + padding-right: 0.5em; + opacity: 0.7; } + .term { - font-weight: 600; color: var(--bs-primary-dark); - background-color: var(--bs-primary-light); - padding: 0.15em 0.4em; border-radius: 4px; cursor: help; - border: 1px solid var(--bs-primary); + font-weight: 600; color: var(--bs-primary); + background-color: #e3f2fd; /* Lighter blue bg for terms */ + padding: 0.2em 0.45em; border-radius: 4px; cursor: help; + border: 1px solid var(--bs-primary-light); + transition: background-color 0.2s ease, color 0.2s ease; + } + .term:hover { + background-color: var(--bs-primary); + color: #fff; } - a { color: var(--bs-primary); text-decoration: none; } + + a { color: var(--bs-primary); text-decoration: none; font-weight: 500; } a:hover { color: var(--bs-primary-dark); text-decoration: underline; } footer { text-align: center; - padding: 2rem 0; - margin-top: 2rem; - background-color: #e9ecef; + padding: 2.5rem 0; + margin-top: 3rem; + background-color: #e2e6ea; /* Slightly darker footer */ color: var(--text-color-secondary); font-size: 0.9rem; border-top: 1px solid var(--card-border-color); @@ -237,16 +293,19 @@ footer a:hover { color: var(--bs-primary-dark); } @media print { - body { font-size: 10pt; background-color: #fff; } - .page-header, footer, .details-toggle { display: none; } - .schema-container { border: 1px solid #ccc; box-shadow: none; margin-bottom: 1.5rem; padding: 1rem; border-left-width: 3px; } + body { font-size: 10pt; background-color: #fff; color: #000; } + .page-header, footer, .details-toggle, .global-controls { display: none; } + .schema-container { border: 1px solid #bbb; box-shadow: none; margin-bottom: 1.5rem; padding: 1rem; border-left-width: 0; } + .schema-container::before { display: none; } /* Hide decorative border in print */ .section-title { font-size: 1.4rem; margin-bottom: 1rem; color: #000 !important; border-bottom-color: #000 !important;} - .info-card { box-shadow: none; border: 1px solid #ddd; border-left-width: 3px; margin-bottom: 1rem; page-break-inside: avoid; } + .section-title .bi { color: #000 !important; } + .info-card { box-shadow: none; border: 1px solid #ccc; border-top-width: 3px; margin-bottom: 1rem; page-break-inside: avoid; } .info-card .card-header { background-color: #eee !important; color: #000 !important; font-size: 1.1rem; } .collapse.show { display: block !important; } /* Ensure collapsed content is visible */ .collapse-content { border-top: 1px solid #eee; background-color: #fff; } - pre, pre code { background-color: #f8f9fa !important; color: #212529 !important; border: 1px solid #eee; white-space: pre-wrap; word-break: break-all; } - .term { background-color: #e9ecef; border: 1px solid #ced4da; color: #000; } + .collapse-content pre, .collapse-content pre code { background-color: #f8f9fa !important; color: #212529 !important; border: 1px solid #eee; white-space: pre-wrap; word-break: break-all; } + .collapse-content pre::before { display: none; } /* Hide lang hint in print */ + .term { background-color: #e9ecef; border: 1px solid #ced4da; color: #000; padding: 0.1em 0.2em; } a { text-decoration: none; color: #000; } a[href^="http"]:after { content: " (" attr(href) ")"; font-size: 0.9em; } } @@ -254,15 +313,19 @@ </head> <body> <header class="page-header"> - <h1><i class="bi bi-diagram-3-fill"></i> Clean Architecture in .NET Web API</h1> - <p class="lead">A comprehensive cheatsheet covering core principles, layer responsibilities, project structure, C# examples, and best practices for building robust and maintainable .NET Web APIs using Clean Architecture.</p> - <p class="text-muted small">Last Updated: <span id="lastUpdatedDate">June 6, 2025</span></p> + <h1><i class="bi bi-layers-half"></i> Clean Architecture in .NET Web API</h1> + <p class="lead">An enhanced cheatsheet covering core principles, layer responsibilities, project structure, C# examples, and best practices for building robust and maintainable .NET Web APIs using Clean Architecture.</p> + <p class="text-muted small">Last Updated: <span id="lastUpdatedDate">June 7, 2025</span></p> </header> <main class="container"> + <div class="global-controls"> + <button class="btn btn-outline-primary btn-sm" id="expandAllBtn"><i class="bi bi-arrows-expand"></i> Expand All</button> + <button class="btn btn-outline-secondary btn-sm" id="collapseAllBtn"><i class="bi bi-arrows-collapse"></i> Collapse All</button> + </div> <!-- Core Philosophy & When to Use --> <div class="schema-container section-core-principles"> - <h2 class="section-title"><i class="bi bi-journal-richtext"></i> Core Concepts</h2> + <h2 class="section-title"><i class="bi bi-lightbulb"></i> Core Concepts</h2> <div class="row"> <div class="col-lg-6"> <div class="info-card card-core-philosophy"> @@ -279,7 +342,7 @@ </div> <div class="col-lg-6"> <div class="info-card card-when-to-use"> - <div class="card-header"><i class="bi bi-question-circle-fill"></i> When to Use Clean Architecture</div> + <div class="card-header"><i class="bi bi-question-diamond-fill"></i> When to Use Clean Architecture</div> <div class="card-body"> <ul> <li><strong>Complex Business Logic:</strong> When core rules are intricate and form the primary value.</li> @@ -296,12 +359,12 @@ <!-- Layers Overview & Responsibilities --> <div class="schema-container section-layers-overview"> - <h2 class="section-title"><i class="bi bi-stack"></i> Layers Overview & Responsibilities</h2> + <h2 class="section-title"><i class="bi bi-stack-overflow"></i> Layers Overview & Responsibilities</h2> <div class="row"> <!-- Domain Layer --> <div class="col-md-6 col-lg-6"> <div class="info-card card-domain-layer"> - <div class="card-header"><i class="bi bi-shield-shaded"></i> Domain Layer (Core)</div> + <div class="card-header"><i class="bi bi-shield-lock-fill"></i> Domain Layer (Core)</div> <div class="card-body"> <p><strong>Purpose:</strong> Contains enterprise-wide business logic and types. Innermost layer with no dependencies on other solution layers.</p> <button class="btn btn-sm details-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#detailsDomainLayer" aria-expanded="false" aria-controls="detailsDomainLayer"> @@ -330,9 +393,11 @@ public class Product <pre><code class="language-csharp">// Domain/Interfaces/IProductRepository.cs public interface IProductRepository { - Task<Product?> GetByIdAsync(Guid id); - Task AddAsync(Product product); - // ... other methods + Task<Product?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default); + Task<IEnumerable<Product>> GetAllAsync(CancellationToken cancellationToken = default); + Task AddAsync(Product product, CancellationToken cancellationToken = default); + Task UpdateAsync(Product product, CancellationToken cancellationToken = default); + Task DeleteAsync(Guid id, CancellationToken cancellationToken = default); }</code></pre> </li> <li><strong>Domain Events:</strong> Represent significant occurrences.</li> @@ -346,7 +411,7 @@ public interface IProductRepository <!-- Application Layer --> <div class="col-md-6 col-lg-6"> <div class="info-card card-application-layer"> - <div class="card-header"><i class="bi bi-arrows-move"></i> Application Layer</div> + <div class="card-header"><i class="bi bi-arrows-fullscreen"></i> Application Layer</div> <div class="card-body"> <p><strong>Purpose:</strong> Contains application-specific business logic. Orchestrates use cases, interacting with Domain and Infrastructure (via interfaces).</p> <button class="btn btn-sm details-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#detailsApplicationLayer" aria-expanded="false" aria-controls="detailsApplicationLayer"> @@ -363,21 +428,28 @@ public record CreateProductCommand(string Name, decimal Price) : IRequest<Gui public class CreateProductCommandHandler : IRequestHandler<CreateProductCommand, Guid> { private readonly IProductRepository _productRepository; - // ... - public async Task<Guid> Handle(CreateProductCommand request, CancellationToken ct) + private readonly IUnitOfWork _unitOfWork; + + public CreateProductCommandHandler(IProductRepository productRepository, IUnitOfWork unitOfWork) + { + _productRepository = productRepository; + _unitOfWork = unitOfWork; + } + + public async Task<Guid> Handle(CreateProductCommand request, CancellationToken cancellationToken) { var product = new Product(Guid.NewGuid(), request.Name, request.Price); - await _productRepository.AddAsync(product); - // await _unitOfWork.SaveChangesAsync(ct); + await _productRepository.AddAsync(product, cancellationToken); + await _unitOfWork.SaveChangesAsync(cancellationToken); return product.Id; } }</code></pre> </li> <li><strong><span class="term" data-bs-toggle="tooltip" title="Simple objects used to transfer data between layers, especially Application and Presentation. Avoid exposing domain entities directly.">Data Transfer Objects (DTOs)</span>:</strong> Data carriers between layers.</li> - <li><strong>Interfaces for Infrastructure Services:</strong> Abstractions for external concerns (e.g., `IEmailSender`).</li> + <li><strong>Interfaces for Infrastructure Services:</strong> Abstractions for external concerns (e.g., `IEmailSender`, `IDateTimeProvider`).</li> <li><strong>Validation Logic:</strong> Input data validation (e.g., using <span class="term" data-bs-toggle="tooltip" title="A popular .NET library for creating strongly-typed validation rules.">FluentValidation</span>).</li> <li><strong><span class="term" data-bs-toggle="tooltip" title="Command Query Responsibility Segregation: A pattern that separates read (Query) and update (Command) operations for a data store.">CQRS</span>:</strong> Separates read and write operations. Often uses <span class="term" data-bs-toggle="tooltip" title="A popular in-process messaging library in .NET that helps implement Mediator and CQRS patterns.">MediatR</span>.</li> - <li><strong>Application Exceptions:</strong> For application-specific errors.</li> + <li><strong>Application Exceptions:</strong> For application-specific errors (e.g., `NotFoundException`).</li> </ul> </div> </div> @@ -387,7 +459,7 @@ public class CreateProductCommandHandler : IRequestHandler<CreateProductComma <!-- Infrastructure Layer --> <div class="col-md-6 col-lg-6"> <div class="info-card card-infrastructure-layer"> - <div class="card-header"><i class="bi bi-hdd-network-fill"></i> Infrastructure Layer</div> + <div class="card-header"><i class="bi bi-database-fill-gear"></i> Infrastructure Layer</div> <div class="card-body"> <p><strong>Purpose:</strong> Handles all external concerns and technical details (databases, file systems, external APIs). Implements interfaces from Application/Domain.</p> <button class="btn btn-sm details-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#detailsInfrastructureLayer" aria-expanded="false" aria-controls="detailsInfrastructureLayer"> @@ -396,19 +468,33 @@ public class CreateProductCommandHandler : IRequestHandler<CreateProductComma <div class="collapse collapse-content" id="detailsInfrastructureLayer"> <h6>Key Components:</h6> <ul> - <li><strong>Data Access/Persistence:</strong> Repository implementations (e.g., using EF Core). Includes `DbContext`. + <li><strong>Data Access/Persistence:</strong> Repository implementations (e.g., using Entity Framework Core). Includes `DbContext`. <pre><code class="language-csharp">// Infrastructure/Persistence/Repositories/ProductRepository.cs public class ProductRepository : IProductRepository { private readonly ApplicationDbContext _dbContext; public ProductRepository(ApplicationDbContext dbContext) {/*...*/} - public async Task AddAsync(Product product) {/*...*/} + + public async Task<Product?> GetByIdAsync(Guid id, CancellationToken ct) + { /* ... */ } + public async Task AddAsync(Product product, CancellationToken ct) + { /* ... */ } // ... other implementations +} + +// Infrastructure/Persistence/ApplicationDbContext.cs +public class ApplicationDbContext : DbContext, IUnitOfWork +{ + public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } + public DbSet<Product> Products { get; set; } + // ... other DbSets & OnModelCreating + public async Task<int> SaveChangesAsync(CancellationToken ct) { /* ... */ } }</code></pre> </li> <li><strong>External Service Integrations:</strong> Clients for APIs, payment gateways, email.</li> - <li><strong>Caching Implementations:</strong> (e.g., Redis).</li> + <li><strong>Caching Implementations:</strong> (e.g., Redis, In-Memory).</li> <li><strong>Identity Services:</strong> Authentication/Authorization implementations.</li> + <li><strong>File System Access, Clock Services, etc.</strong></li> </ul> </div> </div> @@ -418,7 +504,7 @@ public class ProductRepository : IProductRepository <!-- Presentation Layer --> <div class="col-md-6 col-lg-6"> <div class="info-card card-presentation-layer"> - <div class="card-header"><i class="bi bi-display-fill"></i> Presentation Layer (Web API)</div> + <div class="card-header"><i class="bi bi-pc-display-horizontal"></i> Presentation Layer (Web API)</div> <div class="card-body"> <p><strong>Purpose:</strong> Handles user interaction (HTTP requests/responses for an API). Translates input to Application layer and presents results.</p> <button class="btn btn-sm details-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#detailsPresentationLayer" aria-expanded="false" aria-controls="detailsPresentationLayer"> @@ -442,12 +528,15 @@ public class ProductsController : ControllerBase var productId = await _mediator.Send(command); return CreatedAtAction(nameof(GetProductById), new { id = productId }, new { id = productId }); } - // ... other endpoints + + [HttpGet("{id}")] + public async Task<IActionResult> GetProductById(Guid id) { /* ... */ } }</code></pre> </li> <li><strong>Middleware:</strong> Global error handling, authentication, logging.</li> <li><strong><span class="term" data-bs-toggle="tooltip" title="The central place in an application where dependencies are registered and configured, typically in Program.cs or Startup.cs.">Dependency Injection (DI) Setup</span>:</strong> Configuration of services (<span class="term" data-bs-toggle="tooltip" title="The single place in an application where all components are wired together.">Composition Root</span>).</li> <li><strong>API Models/ViewModels:</strong> Models for API requests/responses.</li> + <li><strong>API Versioning, OpenAPI/Swagger Configuration.</strong></li> </ul> </div> </div> @@ -458,19 +547,23 @@ public class ProductsController : ControllerBase <!-- Project Structure Example --> <div class="schema-container section-project-structure"> - <h2 class="section-title"><i class="bi bi-folder2-open"></i> Project Structure Example</h2> + <h2 class="section-title"><i class="bi bi-diagram-2-fill"></i> Project Structure Example</h2> <div class="info-card card-project-structure"> - <div class="card-header"><i class="bi bi-diagram-2"></i> Solution Structure</div> + <div class="card-header"><i class="bi bi-collection-fill"></i> Solution Structure</div> <div class="card-body"> <pre><code class="language-text"> Solution.sln โโโ src -โ โโโ Domain/ (Core.csproj - .NET Class Library) +โ โโโ Domain/ (Core.csproj) โ โ โโโ Entities/ -โ โ โโโ Interfaces/ (e.g., IRepository.cs) -โ โ โโโ ... (ValueObjects, Enums, Exceptions, Events) +โ โ โโโ Aggregates/ +โ โ โโโ Enums/ +โ โ โโโ Events/ +โ โ โโโ Exceptions/ +โ โ โโโ Interfaces/ (e.g., IProductRepository.cs) +โ โ โโโ ValueObjects/ โ โ -โ โโโ Application/ (Application.csproj - .NET Class Library) +โ โโโ Application/ (Application.csproj) โ โ โโโ Features/ (e.g., Products, Orders) โ โ โ โโโ Products/ โ โ โ โ โโโ Commands/ @@ -478,23 +571,26 @@ Solution.sln โ โ โ โ โโโ DTOs/ โ โ โโโ Common/ โ โ โ โโโ Interfaces/ (e.g., IEmailSender.cs) -โ โ โ โโโ ... (Mappings, Behaviors) +โ โ โ โโโ Behaviors/ (MediatR pipeline behaviors) +โ โ โ โโโ Mappings/ (AutoMapper profiles) โ โ -โ โโโ Infrastructure/ (Infrastructure.csproj - .NET Class Library) +โ โโโ Infrastructure/ (Infrastructure.csproj) โ โ โโโ Persistence/ โ โ โ โโโ DataContext/ (e.g., ApplicationDbContext.cs) -โ โ โ โโโ Repositories/ (e.g., ProductRepository.cs) +โ โ โ โโโ Repositories/ (e.g., ProductRepository.cs) +โ โ โ โโโ Configurations/ (EF Core entity configurations) โ โ โโโ Services/ (e.g., EmailSender.cs) โ โ -โ โโโ Presentation/ (WebApi.csproj - ASP.NET Core Web API Project) +โ โโโ Presentation/ (WebApi.csproj - ASP.NET Core Project) โ โโโ Controllers/ โ โโโ Middleware/ -โ โโโ Program.cs (DI Setup) +โ โโโ Program.cs (DI Setup / Composition Root) โ โโโ tests โโโ Domain.UnitTests/ โโโ Application.UnitTests/ - โโโ ... (Infrastructure.IntegrationTests, Presentation.IntegrationTests) + โโโ Infrastructure.IntegrationTests/ + โโโ Presentation.IntegrationTests/ </code></pre> </div> </div> @@ -502,27 +598,27 @@ Solution.sln <!-- Best Practices & Considerations --> <div class="schema-container section-best-practices"> - <h2 class="section-title"><i class="bi bi-check2-circle"></i> Best Practices & Considerations</h2> + <h2 class="section-title"><i class="bi bi-award-fill"></i> Best Practices & Considerations</h2> <div class="info-card card-best-practices"> - <div class="card-header"><i class="bi bi-lightbulb-fill"></i> Key Practices</div> + <div class="card-header"><i class="bi bi-check-circle-fill"></i> Key Practices</div> <div class="card-body"> <ul> - <li><strong><span class="term" data-bs-toggle="tooltip" title="A design pattern where objects receive their dependencies from an external source rather than creating them internally. Crucial for Clean Architecture.">Dependency Injection (DI)</span>:</strong> Absolutely crucial. Register dependencies in Presentation layer's `Program.cs`.</li> - <li><strong><span class="term" data-bs-toggle="tooltip" title="A popular library for in-process messaging that helps implement CQRS and Mediator patterns, decoupling senders from handlers.">MediatR</span>:</strong> Widely used for CQRS in Application layer.</li> - <li><strong><span class="term" data-bs-toggle="tooltip" title="A .NET library for creating strongly-typed validation rules, often used in Application layer for command/query validation.">FluentValidation</span>:</strong> Robust validation in Application layer.</li> - <li><strong><span class="term" data-bs-toggle="tooltip" title="A library for object-to-object mapping, useful for converting between Entities, DTOs, and API Models.">AutoMapper</span> (or similar):</strong> For mapping between Entities, DTOs, API Models.</li> - <li><strong><span class="term" data-bs-toggle="tooltip" title="A pattern that groups multiple repository operations into a single transaction, often implemented within the DbContext in the Infrastructure layer.">Unit of Work (UoW) Pattern</span>:</strong> Often in Infrastructure (e.g., `DbContext`). `IUnitOfWork` interface in Application.</li> - <li><strong>Error Handling:</strong> Global error handling middleware in Presentation. Custom exceptions in Domain/Application.</li> - <li><strong><span class="term" data-bs-toggle="tooltip" title="A pattern in .NET for managing strongly-typed configuration settings, typically loaded from appsettings.json or environment variables.">Configuration (Options Pattern)</span>:</strong> Manage configuration via `IOptions<T>`.</li> - <li><strong>Async/Await:</strong> Use thoroughly for I/O-bound operations.</li> - <li><strong><span class="term" data-bs-toggle="tooltip" title="A design principle stating that a class should have only one reason to change, meaning it should have only one job or responsibility.">Single Responsibility Principle (SRP)</span>:</strong> Apply to classes and methods.</li> - <li><strong>Lean Controllers:</strong> Controllers should be thin, delegating to Application layer.</li> - <li><strong>Avoid Leaking Abstractions:</strong> Don't expose `IQueryable` from repositories to outer layers.</li> + <li><strong><span class="term" data-bs-toggle="tooltip" title="A design pattern where objects receive their dependencies from an external source rather than creating them internally. Crucial for Clean Architecture.">Dependency Injection (DI)</span>:</strong> Absolutely crucial. Register dependencies in Presentation layer's `Program.cs`. Use constructor injection.</li> + <li><strong><span class="term" data-bs-toggle="tooltip" title="A popular library for in-process messaging that helps implement CQRS and Mediator patterns, decoupling senders from handlers.">MediatR</span>:</strong> Widely used for implementing CQRS in Application layer. Decouples command/query senders from handlers. Allows cross-cutting concerns via pipeline behaviors (validation, logging).</li> + <li><strong><span class="term" data-bs-toggle="tooltip" title="A .NET library for creating strongly-typed validation rules, often used in Application layer for command/query validation.">FluentValidation</span>:</strong> Robust validation in Application layer, often integrated with MediatR pipelines.</li> + <li><strong><span class="term" data-bs-toggle="tooltip" title="A library for object-to-object mapping, useful for converting between Entities, DTOs, and API Models.">AutoMapper</span> (or similar):</strong> Useful for mapping between Entities, DTOs, and API Models. Define profiles where relevant.</li> + <li><strong><span class="term" data-bs-toggle="tooltip" title="A pattern that groups multiple repository operations into a single transaction, often implemented within the DbContext in the Infrastructure layer.">Unit of Work (UoW) Pattern</span>:</strong> Often implemented in Infrastructure (e.g., `DbContext`). An `IUnitOfWork` interface can be defined in Application layer.</li> + <li><strong>Error Handling:</strong> Implement global error handling middleware in Presentation layer. Define custom exceptions in Domain and Application layers.</li> + <li><strong><span class="term" data-bs-toggle="tooltip" title="A pattern in .NET for managing strongly-typed configuration settings, typically loaded from appsettings.json or environment variables.">Configuration (Options Pattern)</span>:</strong> Manage configuration via `IOptions<T>`, configured in Presentation layer.</li> + <li><strong>Async/Await:</strong> Use thoroughly for I/O-bound operations (database access, network calls) to maintain responsiveness.</li> + <li><strong><span class="term" data-bs-toggle="tooltip" title="A design principle stating that a class should have only one reason to change, meaning it should have only one job or responsibility.">Single Responsibility Principle (SRP)</span>:</strong> Apply SRP to classes and methods within each layer.</li> + <li><strong>Lean Controllers:</strong> Controllers should be thin, primarily delegating work to the Application layer. Avoid business logic in controllers.</li> + <li><strong>Avoid Leaking Abstractions:</strong> Do not expose `IQueryable` from repositories to outer layers, as this can lead to infrastructure concerns leaking. Queries should be fully defined within the data access layer or use specification patterns.</li> + <li><strong>Testing Strategy:</strong> Unit tests for Domain & Application layers (mocking dependencies). Integration tests for Infrastructure & Presentation layers (testing against real DBs/services or in-memory test servers).</li> </ul> </div> </div> </div> - </main> <footer> @@ -548,25 +644,26 @@ Solution.sln // Set last updated date const lastUpdatedDateSpan = document.getElementById('lastUpdatedDate'); if (lastUpdatedDateSpan) { - // Format today's date as Month Day, Year const today = new Date(); const options = { year: 'numeric', month: 'long', day: 'numeric' }; lastUpdatedDateSpan.textContent = today.toLocaleDateString('en-US', options); } - // Initialize tooltips var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) { return new bootstrap.Tooltip(tooltipTriggerEl, { - boundary: document.body, // Attempt to make tooltips less likely to be cut off - html: true // Allow HTML in tooltips if needed + boundary: document.body, + html: true }); }); - // Handle collapse icon changes - const collapseElements = document.querySelectorAll('.collapse'); - collapseElements.forEach(collapseEl => { + const allCollapseElements = document.querySelectorAll('.collapse'); + const expandAllBtn = document.getElementById('expandAllBtn'); + const collapseAllBtn = document.getElementById('collapseAllBtn'); + + // Handle individual collapse icon changes + allCollapseElements.forEach(collapseEl => { const button = document.querySelector(`.details-toggle[data-bs-target="#${collapseEl.id}"]`); if (button) { const iconEl = button.querySelector('.bi'); @@ -584,6 +681,32 @@ Solution.sln collapseEl.addEventListener('hide.bs.collapse', updateIconState); } }); + + // Expand All functionality + if (expandAllBtn) { + expandAllBtn.addEventListener('click', () => { + allCollapseElements.forEach(el => { + var bsCollapse = bootstrap.Collapse.getInstance(el); + if (!bsCollapse) { + bsCollapse = new bootstrap.Collapse(el, { toggle: false }); + } + bsCollapse.show(); + }); + }); + } + + // Collapse All functionality + if (collapseAllBtn) { + collapseAllBtn.addEventListener('click', () => { + allCollapseElements.forEach(el => { + var bsCollapse = bootstrap.Collapse.getInstance(el); + if (!bsCollapse) { + bsCollapse = new bootstrap.Collapse(el, { toggle: false }); + } + bsCollapse.hide(); + }); + }); + } }); </script> </body>