A work order management application built with .NET 10.0 implementing Onion Architecture. The system uses Blazor WebAssembly for the UI, Entity Framework Core for data access, MediatR for CQRS, and deploys to Azure Container Apps.
This codebase serves as both a working application and a teaching reference for software architecture. The 51 architectural patterns cataloged below are all demonstrated in the source code.
src/
Core/ Domain layer — models, interfaces, queries (no dependencies)
DataAccess/ EF Core, MediatR handlers (references Core only)
Database/ DbUp schema migrations
UI/Server/ Blazor Server host, Lamar DI
UI/Client/ Blazor WebAssembly frontend
UI/Api/ Web API endpoints
UI.Shared/ Shared UI types
LlmGateway/ Azure OpenAI integration
Worker/ Background hosted service
ChurchBulletin.AppHost/ .NET Aspire orchestration
ChurchBulletin.ServiceDefaults/ Aspire service defaults
UnitTests/ NUnit + Shouldly
IntegrationTests/ NUnit, LocalDB / SQL Server / SQLite
AcceptanceTests/ NUnit + Playwright
- .NET 10.0 SDK
- One of the following database options:
- Windows: SQL Server LocalDB (included with Visual Studio)
- Linux/macOS with Docker: SQL Server 2022 runs automatically in a container
- Linux/macOS without Docker: SQLite (automatic fallback)
- PowerShell 7+ (cross-platform, required for build scripts)
- Playwright browsers (for acceptance tests only)
# Quick build (Windows)
.\build.bat
# Quick build (Linux/macOS)
./build.sh
# Full build — clean, compile, unit tests, DB migration, integration tests
. .\build.ps1 ; Build
# dotnet CLI directly
dotnet build src/ChurchBulletin.sln --configuration Release# Unit tests
dotnet test src/UnitTests --configuration Release
# Integration tests
dotnet test src/IntegrationTests --configuration Release
# Acceptance tests (install Playwright browsers first)
pwsh src/AcceptanceTests/bin/Debug/net10.0/playwright.ps1 install
dotnet test src/AcceptanceTests --configuration DebugThe repository supports three common local development workflows. Pick the one that matches your environment.
-
Windows (Visual Studio / LocalDB)
- Open the solution in Visual Studio (ChurchBulletin.sln).
- Ensure LocalDB is available and your launch profile is configured.
- Run UI.Server from Visual Studio or:
cd src/UI/Server dotnet run
Note: Visual Studio launch profiles may override ConnectionStrings in launchSettings.json. Use the Environment configuration below if you need to override.
-
Linux / macOS (recommended with Docker)
- Start a SQL Server container:
docker run --name churchbulletin-mssql \ -e 'MSSQL_SA_PASSWORD=churchbulletin-mssql#1A' \ -e 'ACCEPT_EULA=Y' \ -p 1433:1433 \ -d mcr.microsoft.com/mssql/server:2022-latest
- Set environment variables and run the server:
export ConnectionStrings__SqlConnectionString="server=localhost,1433;database=ChurchBulletin;User ID=sa;Password=churchbulletin-mssql#1A;TrustServerCertificate=true;" export ASPNETCORE_ENVIRONMENT=Development # Must set APPLICATIONINSIGHTS_CONNECTION_STRING or set to an empty string to avoid the Azure Monitor exporter crashing: export APPLICATIONINSIGHTS_CONNECTION_STRING="" # Prevent the app from attempting to contact Azure OpenAI (leave empty if not used) export AI_OpenAI_ApiKey="" export AI_OpenAI_Url="" export AI_OpenAI_Model="" cd src/UI/Server dotnet run --no-launch-profile --urls "https://localhost:7174;http://localhost:5174"
Important: use
--no-launch-profileon Linux/macOS to avoid the Windows LocalDB connection string from launchSettings.json overriding your env vars. - Start a SQL Server container:
-
SQLite fallback (no Docker)
- Set the database engine and run the build scripts:
export DATABASE_ENGINE=SQLite cd src pwsh -NoProfile -ExecutionPolicy Bypass -File ./PrivateBuild.ps1
The build scripts will use SQLite when Docker is not available.
- Set the database engine and run the build scripts:
Health and URLs
- The application starts at https://localhost:7174 by default.
- Health check endpoint: https://localhost:7174/_healthcheck
Architecture diagrams
- See the arch/ folder for PlantUML sources and rendered images. The architecture docs (arch/README.md) explain rendering and icon configuration.
- To regenerate PlantUML images locally: use the helper scripts:
- PowerShell: pwsh arch/render-diagrams.ps1
- Bash: ./arch/render-diagrams.sh
- A CI job (.github/workflows/render-diagrams.yml) validates that diagram images are kept in sync on pull requests.
- To install the optional pre-commit hook that re-renders diagrams when .puml files are staged, run the repository setup script.
- Bash (Linux/macOS/Git Bash): ./scripts/setup-dev-env.sh
- PowerShell (Windows): pwsh ./scripts/setup-dev-env.ps1
Playwright (acceptance tests)
- Install browsers (PowerShell):
pwsh src/AcceptanceTests/bin/Debug/net10.0/playwright.ps1 install
- Run acceptance tests:
dotnet test src/AcceptanceTests --configuration Debug
gRPC (work orders)
- Protobuf contract: src/UI/Server/Protos/workorders.proto
- Generated C# (checked in): src/UI/Server/Generated/Protos/
- To regenerate the C# files after editing .proto, run the Grpc.Tools generation on an x64 machine and replace the checked-in generated files (Grpc.Tools can be unstable on ARM).
A catalog of 51 architectural patterns and design concepts demonstrated in this codebase, annotated with authoritative reference URLs suitable for student learning.
Layered architecture where dependencies point inward. Inner layers define interfaces; outer layers implement them. Core has zero outward dependencies.
Separates read models (queries) from write models (commands), allowing each to be optimized independently.
- Reference: Martin Fowler — CQRS
A behavioral design pattern where a mediator object encapsulates how objects interact, promoting loose coupling. Implemented here via the MediatR library.
- Reference: Refactoring Guru — Mediator
An object model of the business domain that incorporates both behavior and data. Business logic lives inside the domain objects themselves.
- Reference: Martin Fowler — Domain Model (P of EAA)
An abstract base class that provides common behavior (identity, equality) for all domain entities in a layer.
- Reference: Martin Fowler — Layer Supertype (P of EAA)
An immutable object defined entirely by its attributes rather than a unique identity. Two value objects with the same attributes are considered equal.
- Reference: Martin Fowler — Value Object
Replaces primitive enum types with full classes that carry behavior, enabling richer domain modeling and avoiding primitive obsession.
Objects transition through a set of defined states via validated transitions, preventing invalid state changes.
- Reference: Refactoring Guru — State Pattern
Abstracts data access behind a collection-like interface, decoupling the domain from persistence technology. Implemented implicitly via EF Core's DbContext.
- Reference: Martin Fowler — Repository (P of EAA)
Tracks all changes made during a business transaction and commits them as a single atomic operation. Implemented implicitly via EF Core's SaveChanges.
- Reference: Martin Fowler — Unit of Work (P of EAA)
Components declare their dependencies through constructor parameters; an external container resolves and provides them at runtime.
A well-known object that other objects use to find common services. Centralized registration of services in a single registry class.
- Reference: Martin Fowler — Registry (P of EAA)
The framework automatically discovers and registers services by scanning assemblies, reducing explicit configuration in favor of naming and structural conventions.
- Reference: Wikipedia — Convention over Configuration
A single-page application framework that runs .NET code directly in the browser via WebAssembly, eliminating the need for JavaScript.
Initial rendering occurs on the server for fast first paint; the client-side framework then takes over for interactivity.
A dedicated API layer tailored to the needs of a specific frontend, rather than a generic API serving all consumers.
A shared set of types and interfaces used across bounded contexts or UI boundaries, maintained as a single shared project.
Incremental, versioned schema changes applied via numbered migration scripts, allowing the database to evolve alongside application code.
- Reference: Martin Fowler — Evolutionary Database Design
Packaging an application and its dependencies into a lightweight, portable container image that runs consistently across environments.
- Reference: Docker — What is a Container?
A managed runtime that handles container deployment, scaling, networking, and revision management.
Automatically building and testing every code change when pushed, providing rapid feedback on integration errors.
- Reference: Martin Fowler — Continuous Integration
An automated pipeline that ensures code is always in a deployable state, from commit through testing environments to production.
- Reference: Martin Fowler — Continuous Delivery
Keeping development, staging, and production environments as similar as possible to reduce deployment surprises.
- Reference: The Twelve-Factor App — X. Dev/Prod Parity
Deploying a new version alongside the old one, then switching traffic, enabling zero-downtime releases and easy rollback.
- Reference: Martin Fowler — Blue Green Deployment
Incrementally replacing a legacy system by routing functionality to a new implementation while the old one is gradually retired. Applied here to the migration from Azure DevOps to GitHub Actions.
- Reference: Martin Fowler — Strangler Fig Application
Defining infrastructure declaratively in version-controlled files (ARM templates, pipeline YAML, Octopus OCL) rather than through manual configuration.
- Reference: Wikipedia — Infrastructure as Code
Deployment processes, variables, and settings stored as code in the repository, enabling versioning, diffing, and review of deployment configuration.
- Reference: Octopus Deploy — Deployment Process as Code
A versioning scheme (MAJOR.MINOR.PATCH) with defined rules about when each number increments, communicating the nature of changes to consumers.
- Reference: Semantic Versioning 2.0.0
The same compiled artifact flows unchanged through all environments (TDD, UAT, Prod). What was tested is what gets deployed.
- Reference: Minimum CD — Immutable Artifacts
Requiring manual reviewer approval before deploying to higher environments, enforcing governance over the release pipeline.
- Reference: Microsoft Learn — Gates and Approvals
Separating the concerns of "build" from "deploy" with a dedicated release management tool that tracks what version is in which environment.
- Reference: Octopus Deploy — Releases and Deployments
Dedicated endpoints that report system health, enabling load balancers, orchestrators, and deployment pipelines to verify application readiness.
Blocking deployment progression (e.g., acceptance test execution) until health endpoints confirm the application is ready.
Health checks executed from the client perspective (Blazor WASM) back to the server, providing end-to-end health visibility beyond server-side checks alone.
- Reference: Microsoft Learn — ASP.NET Core Health Checks
A layered testing strategy with many fast unit tests at the base, fewer integration tests in the middle, and a small number of end-to-end acceptance tests at the top.
- Reference: Martin Fowler — Test Pyramid
Replacing real dependencies with simplified implementations ("stubs") during testing. This codebase uses the "Stub" prefix convention rather than "Mock."
- Reference: Martin Fowler — Test Double
Generating test data with fluent builder objects (via AutoBogus), producing valid domain objects without verbose manual construction.
Structuring each test method into three phases: set up preconditions, execute the action under test, and verify the result.
- Reference: C2 Wiki — Arrange Act Assert
End-to-end tests that drive a real browser to verify the application behaves correctly from the user's perspective.
- Reference: Playwright Documentation
Unit testing individual UI components in isolation by rendering them in a test host without a browser.
Running the same test suite against multiple database engines (SQL Server, SQLite, LocalDB) to validate that data access logic is not vendor-specific.
Running CI builds in parallel across multiple CPU architectures (x86_64, ARM64) to verify cross-platform compatibility.
Creates objects without exposing the instantiation logic, letting subclasses or configuration determine which concrete class to create.
- Reference: Refactoring Guru — Factory Method
Long-running background tasks implemented as hosted services within the .NET generic host, running alongside the web application.
- Reference: Microsoft Learn — Worker Services in .NET
Development-time orchestration of distributed services, wiring connection strings, health checks, and service discovery for local development.
Static analysis annotations that help the compiler detect potential null reference errors at compile time rather than runtime.
- Reference: Microsoft Learn — Nullable Reference Types
Using the NuGet package format not just for library distribution but as the deployment unit that flows through the release pipeline.
- Reference: Microsoft Learn — What is NuGet?
An abstraction layer between the application and AI services, encapsulating connection management, health checks, and model configuration.
Documenting software architecture at four hierarchical levels of abstraction: System Context, Container, Component, and Code (Class).
Describing architecture from five concurrent viewpoints: Logical, Development, Process, Physical, and Scenarios — each addressing different stakeholder concerns.
- Reference: Wikipedia — 4+1 Architectural View Model
Maintaining architecture diagrams (PlantUML, Mermaid) as source files checked into version control alongside application code, enabling versioning, diffing, and review.
- Reference: The C4 Model — Tooling
🕐 Last updated: 2026-03-25T21:50:00Z
Deployments are verified in the UAT environment before release.
Deployment run 20260727-081828 is verified in the UAT environment before release.
Deployment run 20260727-090150 is verified in the UAT environment before release.
Deployment run 20260727-094548 is verified in the UAT environment before release.
Deployment run 20260727-132512 is verified in the UAT environment before release.
Deployment run 20260727-201015 is verified in the UAT environment before release.
Deployment run 20260727-210050 is verified in the UAT environment before release.
Deployment run 20260727-222027 is verified in the UAT environment before release.
Deployment run 20260727-231313 is verified in the UAT environment before release.
Deployment run 20260728-000302 is verified in the UAT environment before release.
Deployment run 20260728-041350 is verified in the UAT environment before release.
Deployment run 20260728-200157 is verified in the UAT environment before release.
Deployment run 20260728-203209 is verified in the UAT environment before release.
Deployment run 20260728-210121 is verified in the UAT environment before release.
Deployment run 20260728-220129 is verified in the UAT environment before release.
Deployment run 20260729-012310 is verified in the UAT environment before release.
Deployment run 20260729-060151 is verified in the UAT environment before release.
Deployment run 20260729-172838 is verified in the UAT environment before release.
Deployment run 20260729-203926 is verified in the UAT environment before release.