Skip to content

Instantly share code, notes, and snippets.

@weehong
Created October 20, 2025 09:38
Show Gist options
  • Save weehong/dd5c3935e81d6d6784aea8456bf1acf7 to your computer and use it in GitHub Desktop.
Save weehong/dd5c3935e81d6d6784aea8456bf1acf7 to your computer and use it in GitHub Desktop.
This script create a .NET project utilizing project structure
#!/bin/bash
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
PROJECT_NAME=""
INCLUDE_TESTS=true
DATABASE_PROVIDER="sqlserver"
DOTNET_VERSION="net9.0"
SCRIPT_DIR="$(pwd)"
# Function to print colored messages
print_info() {
echo -e "${GREEN}✓${NC} $1"
}
print_error() {
echo -e "${RED}✗${NC} $1"
}
print_warning() {
echo -e "${YELLOW}⚠${NC} $1"
}
print_step() {
echo -e "${BLUE}▶${NC} $1"
}
# Function to handle errors
handle_error() {
print_error "An error occurred on line $1"
print_error "Command: $2"
exit 1
}
# Set up error handling
trap 'handle_error $LINENO "$BASH_COMMAND"' ERR
# Function to check .NET version
check_dotnet_version() {
if ! command -v dotnet &> /dev/null; then
print_error ".NET SDK is not installed. Please install .NET 9.0 SDK."
exit 1
fi
local version=$(dotnet --version)
local major_version=$(echo $version | cut -d'.' -f1)
if [ "$major_version" -lt 8 ]; then
print_error "This script requires .NET 8.0 or later. Current version: $version"
print_warning "Please download the latest .NET SDK from: https://dotnet.microsoft.com/download"
exit 1
fi
print_info ".NET SDK version: $version"
}
# Function to validate project name
validate_project_name() {
if [[ ! $1 =~ ^[a-zA-Z][a-zA-Z0-9._-]*$ ]]; then
print_error "Invalid project name. Must start with a letter and contain only letters, numbers, dots, hyphens, or underscores."
return 1
fi
return 0
}
# Function to get user input
get_project_configuration() {
echo "========================================"
echo " Clean Architecture CQRS Setup"
echo " .NET 9.0 Production-Ready"
echo "========================================"
echo ""
# Get project name
while true; do
read -p "Enter project name: " PROJECT_NAME
if [ -z "$PROJECT_NAME" ]; then
print_error "Project name cannot be empty."
continue
fi
if ! validate_project_name "$PROJECT_NAME"; then
continue
fi
if [ -d "$PROJECT_NAME" ]; then
print_error "Directory '$PROJECT_NAME' already exists."
exit 1
fi
break
done
# Get test preference
read -p "Include test projects? (Y/n): " include_tests
INCLUDE_TESTS=${include_tests:-Y}
# Get database provider
echo ""
echo "Select database provider:"
echo " 1) SQL Server (default)"
echo " 2) PostgreSQL"
echo " 3) SQLite"
read -p "Enter choice [1-3]: " db_choice
case ${db_choice:-1} in
1) DATABASE_PROVIDER="sqlserver" ;;
2) DATABASE_PROVIDER="postgresql" ;;
3) DATABASE_PROVIDER="sqlite" ;;
*) DATABASE_PROVIDER="sqlserver" ;;
esac
echo ""
print_info "Configuration:"
echo " .NET Version: 9.0"
echo " Project Name: $PROJECT_NAME"
echo " Include Tests: $INCLUDE_TESTS"
echo " Database: $DATABASE_PROVIDER"
echo ""
read -p "Continue? (Y/n): " confirm
if [[ $confirm =~ ^[Nn]$ ]]; then
print_warning "Setup cancelled."
exit 0
fi
}
# Function to create directory structure
create_directory_structure() {
print_step "Creating directory structure..."
mkdir -p "$PROJECT_NAME"
cd "$PROJECT_NAME" || exit 1
mkdir -p src
mkdir -p docs
if [[ $INCLUDE_TESTS =~ ^[Yy]$ ]]; then
mkdir -p tests
fi
print_info "Directory structure created"
}
# Function to create solution
create_solution() {
print_step "Creating solution..."
dotnet new sln -n "$PROJECT_NAME" --force || {
print_error "Failed to create solution"
exit 1
}
print_info "Solution created: $PROJECT_NAME.sln"
}
# Function to create a project
create_project() {
local project_type=$1
local project_name=$2
local project_path=$3
if dotnet new "$project_type" -n "$project_name" -f "$DOTNET_VERSION" -o "$project_path" --force 2>/dev/null; then
if dotnet sln "$PROJECT_NAME.sln" add "$project_path/$project_name.csproj" 2>/dev/null; then
echo " ✓ $project_name"
return 0
else
print_warning "Failed to add $project_name to solution"
return 1
fi
else
print_warning "Failed to create $project_name"
return 1
fi
}
# Function to create all source projects
create_source_projects() {
print_step "Creating source projects..."
create_project "classlib" "$PROJECT_NAME.Domain" "src/$PROJECT_NAME.Domain"
create_project "classlib" "$PROJECT_NAME.Application" "src/$PROJECT_NAME.Application"
create_project "classlib" "$PROJECT_NAME.Application.Commands" "src/$PROJECT_NAME.Application.Commands"
create_project "classlib" "$PROJECT_NAME.Application.Queries" "src/$PROJECT_NAME.Application.Queries"
create_project "classlib" "$PROJECT_NAME.Infrastructure" "src/$PROJECT_NAME.Infrastructure"
create_project "classlib" "$PROJECT_NAME.Contracts" "src/$PROJECT_NAME.Contracts"
create_project "webapi" "$PROJECT_NAME.API" "src/$PROJECT_NAME.API"
print_info "Source projects created"
}
# Function to create test projects
create_test_projects() {
if [[ ! $INCLUDE_TESTS =~ ^[Yy]$ ]]; then
return
fi
print_step "Creating test projects..."
create_project "xunit" "$PROJECT_NAME.Domain.Tests" "tests/$PROJECT_NAME.Domain.Tests"
create_project "xunit" "$PROJECT_NAME.Application.Tests" "tests/$PROJECT_NAME.Application.Tests"
create_project "xunit" "$PROJECT_NAME.Infrastructure.Tests" "tests/$PROJECT_NAME.Infrastructure.Tests"
create_project "xunit" "$PROJECT_NAME.API.Tests" "tests/$PROJECT_NAME.API.Tests"
print_info "Test projects created"
}
# Function to add project reference safely
add_reference() {
local project=$1
shift
local references=("$@")
for ref in "${references[@]}"; do
if [ -f "$ref" ]; then
dotnet add "$project" reference "$ref" 2>/dev/null || print_warning "Failed to add reference from $project to $ref"
fi
done
}
# Function to add project references
add_project_references() {
print_step "Adding project references..."
# Application references Domain
add_reference "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" \
"src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj"
# Commands references
add_reference "src/$PROJECT_NAME.Application.Commands/$PROJECT_NAME.Application.Commands.csproj" \
"src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" \
"src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj"
# Queries references
add_reference "src/$PROJECT_NAME.Application.Queries/$PROJECT_NAME.Application.Queries.csproj" \
"src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" \
"src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj"
# Infrastructure references
add_reference "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" \
"src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" \
"src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj" \
"src/$PROJECT_NAME.Contracts/$PROJECT_NAME.Contracts.csproj"
# API references
add_reference "src/$PROJECT_NAME.API/$PROJECT_NAME.API.csproj" \
"src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" \
"src/$PROJECT_NAME.Application.Commands/$PROJECT_NAME.Application.Commands.csproj" \
"src/$PROJECT_NAME.Application.Queries/$PROJECT_NAME.Application.Queries.csproj" \
"src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" \
"src/$PROJECT_NAME.Contracts/$PROJECT_NAME.Contracts.csproj"
# Application and Infrastructure reference Contracts
add_reference "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" \
"src/$PROJECT_NAME.Contracts/$PROJECT_NAME.Contracts.csproj"
print_info "Project references configured"
}
# Function to add test references
add_test_references() {
if [[ ! $INCLUDE_TESTS =~ ^[Yy]$ ]]; then
return
fi
print_step "Adding test project references..."
# Domain Tests
add_reference "tests/$PROJECT_NAME.Domain.Tests/$PROJECT_NAME.Domain.Tests.csproj" \
"src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj"
# Application Tests
add_reference "tests/$PROJECT_NAME.Application.Tests/$PROJECT_NAME.Application.Tests.csproj" \
"src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" \
"src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj" \
"src/$PROJECT_NAME.Contracts/$PROJECT_NAME.Contracts.csproj"
# Infrastructure Tests
add_reference "tests/$PROJECT_NAME.Infrastructure.Tests/$PROJECT_NAME.Infrastructure.Tests.csproj" \
"src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" \
"src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" \
"src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj"
# API Tests
add_reference "tests/$PROJECT_NAME.API.Tests/$PROJECT_NAME.API.Tests.csproj" \
"src/$PROJECT_NAME.API/$PROJECT_NAME.API.csproj" \
"src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" \
"src/$PROJECT_NAME.Contracts/$PROJECT_NAME.Contracts.csproj"
print_info "Test references configured"
}
# Function to add packages to a project
add_package() {
local project=$1
local package=$2
if [ -f "$project" ]; then
dotnet add "$project" package "$package" 2>/dev/null || print_warning "Failed to add package $package to $project"
fi
}
# Function to install MediatR packages
install_mediatr_packages() {
print_step "Installing MediatR (CQRS)..."
add_package "src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj" "MediatR"
add_package "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" "MediatR"
add_package "src/$PROJECT_NAME.Application.Commands/$PROJECT_NAME.Application.Commands.csproj" "MediatR"
add_package "src/$PROJECT_NAME.Application.Queries/$PROJECT_NAME.Application.Queries.csproj" "MediatR"
print_info "MediatR installed"
}
# Function to install Serilog packages
install_serilog_packages() {
print_step "Installing Serilog (Logging)..."
# Serilog for Infrastructure
add_package "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" "Serilog"
add_package "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" "Serilog.Extensions.Hosting"
add_package "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" "Serilog.Sinks.Console"
add_package "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" "Serilog.Sinks.File"
add_package "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" "Serilog.Sinks.Seq"
add_package "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" "Serilog.Enrichers.Environment"
add_package "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" "Serilog.Enrichers.Thread"
# Serilog for API
add_package "src/$PROJECT_NAME.API/$PROJECT_NAME.API.csproj" "Serilog.AspNetCore"
print_info "Serilog installed"
}
# Function to install Entity Framework packages
install_ef_packages() {
print_step "Installing Entity Framework Core 9.0..."
add_package "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" "Microsoft.EntityFrameworkCore"
add_package "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" "Microsoft.EntityFrameworkCore.Design"
add_package "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" "Microsoft.EntityFrameworkCore.Tools"
case $DATABASE_PROVIDER in
"sqlserver")
add_package "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" "Microsoft.EntityFrameworkCore.SqlServer"
;;
"postgresql")
add_package "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" "Npgsql.EntityFrameworkCore.PostgreSQL"
;;
"sqlite")
add_package "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" "Microsoft.EntityFrameworkCore.Sqlite"
;;
esac
print_info "Entity Framework Core 9.0 with $DATABASE_PROVIDER installed"
}
# Function to install production packages
install_production_packages() {
print_step "Installing production packages..."
# FluentValidation
add_package "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" "FluentValidation"
add_package "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" "FluentValidation.DependencyInjectionExtensions"
# AutoMapper
add_package "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" "AutoMapper"
add_package "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" "AutoMapper.Extensions.Microsoft.DependencyInjection"
# API packages
add_package "src/$PROJECT_NAME.API/$PROJECT_NAME.API.csproj" "Microsoft.AspNetCore.Authentication.JwtBearer"
add_package "src/$PROJECT_NAME.API/$PROJECT_NAME.API.csproj" "Swashbuckle.AspNetCore"
add_package "src/$PROJECT_NAME.API/$PROJECT_NAME.API.csproj" "Asp.Versioning.Http"
add_package "src/$PROJECT_NAME.API/$PROJECT_NAME.API.csproj" "Asp.Versioning.Mvc.ApiExplorer"
# Health Checks
add_package "src/$PROJECT_NAME.API/$PROJECT_NAME.API.csproj" "Microsoft.Extensions.Diagnostics.HealthChecks"
add_package "src/$PROJECT_NAME.API/$PROJECT_NAME.API.csproj" "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore"
# Configuration
add_package "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" "Microsoft.Extensions.Configuration"
add_package "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" "Microsoft.Extensions.Configuration.Binder"
add_package "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" "Microsoft.Extensions.Options.ConfigurationExtensions"
# OpenTelemetry
add_package "src/$PROJECT_NAME.API/$PROJECT_NAME.API.csproj" "OpenTelemetry.Extensions.Hosting"
add_package "src/$PROJECT_NAME.API/$PROJECT_NAME.API.csproj" "OpenTelemetry.Instrumentation.AspNetCore"
print_info "Production packages installed"
}
# Function to install test packages
install_test_packages() {
if [[ ! $INCLUDE_TESTS =~ ^[Yy]$ ]]; then
return
fi
print_step "Installing test packages..."
local test_projects=(
"tests/$PROJECT_NAME.Domain.Tests/$PROJECT_NAME.Domain.Tests.csproj"
"tests/$PROJECT_NAME.Application.Tests/$PROJECT_NAME.Application.Tests.csproj"
"tests/$PROJECT_NAME.Infrastructure.Tests/$PROJECT_NAME.Infrastructure.Tests.csproj"
"tests/$PROJECT_NAME.API.Tests/$PROJECT_NAME.API.Tests.csproj"
)
for project in "${test_projects[@]}"; do
add_package "$project" "FluentAssertions"
add_package "$project" "Moq"
add_package "$project" "AutoFixture"
add_package "$project" "AutoFixture.Xunit2"
add_package "$project" "xunit.runner.visualstudio"
add_package "$project" "Microsoft.NET.Test.Sdk"
done
# Specific packages
add_package "tests/$PROJECT_NAME.Infrastructure.Tests/$PROJECT_NAME.Infrastructure.Tests.csproj" "Microsoft.EntityFrameworkCore.InMemory"
add_package "tests/$PROJECT_NAME.API.Tests/$PROJECT_NAME.API.Tests.csproj" "Microsoft.AspNetCore.Mvc.Testing"
print_info "Test packages installed"
}
# Function to clean up default files
cleanup_default_files() {
print_step "Cleaning up default files..."
find . -name "Class1.cs" -type f -delete 2>/dev/null
find . -name "UnitTest1.cs" -type f -delete 2>/dev/null
find . -name "WeatherForecast.cs" -type f -delete 2>/dev/null
# Remove default controller if exists
rm -f "src/$PROJECT_NAME.API/Controllers/WeatherForecastController.cs" 2>/dev/null
print_info "Default files removed"
}
# Function to create configuration files
create_configuration_files() {
print_step "Creating configuration files..."
# Create .gitignore
cat > .gitignore << 'EOF'
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio cache/options
.vs/
# NuGet
*.nupkg
*.snupkg
**/packages/*
# Rider
.idea/
# VS Code
.vscode/
# Environment files
*.env
appsettings.Development.json
appsettings.*.json
!appsettings.json
# Logs
logs/
*.log
# Database files
*.db
*.db-shm
*.db-wal
EOF
# Create global.json
cat > global.json << EOF
{
"sdk": {
"version": "9.0.0",
"rollForward": "latestMinor"
}
}
EOF
# Create README.md
cat > README.md << EOF
# $PROJECT_NAME
Clean Architecture CQRS application built with .NET 9.0, MediatR, and Serilog.
## Architecture
This project follows Clean Architecture principles with CQRS pattern:
- **Domain**: Business entities and domain logic
- **Application**: Use cases, commands, and queries
- **Infrastructure**: Data access, external services, and cross-cutting concerns
- **API**: REST endpoints and HTTP concerns
- **Contracts**: DTOs and API contracts
## Technology Stack
- **.NET 9.0** - Latest .NET framework
- **MediatR 12.x** - CQRS implementation
- **Entity Framework Core 9.0** - Data access with $DATABASE_PROVIDER
- **Serilog** - Structured logging
- **FluentValidation** - Input validation
- **AutoMapper** - Object mapping
- **OpenTelemetry** - Observability
- **xUnit** - Testing framework
## Getting Started
### Prerequisites
- .NET 9.0 SDK or later
- $DATABASE_PROVIDER database
### Build
\`\`\`bash
dotnet build
\`\`\`
### Run
\`\`\`bash
cd src/$PROJECT_NAME.API
dotnet run
\`\`\`
### Test
\`\`\`bash
dotnet test
\`\`\`
## Project Structure
\`\`\`
$PROJECT_NAME/
├── src/
│ ├── $PROJECT_NAME.Domain/ # Entities, Value Objects
│ ├── $PROJECT_NAME.Application/ # Interfaces, Services
│ ├── $PROJECT_NAME.Application.Commands/ # Command Handlers
│ ├── $PROJECT_NAME.Application.Queries/ # Query Handlers
│ ├── $PROJECT_NAME.Infrastructure/ # EF Context, Repositories
│ ├── $PROJECT_NAME.Contracts/ # DTOs
│ └── $PROJECT_NAME.API/ # Controllers
├── tests/
│ ├── $PROJECT_NAME.Domain.Tests/
│ ├── $PROJECT_NAME.Application.Tests/
│ ├── $PROJECT_NAME.Infrastructure.Tests/
│ └── $PROJECT_NAME.API.Tests/
├── docs/
├── global.json
└── $PROJECT_NAME.sln
\`\`\`
## License
[Your License Here]
EOF
# Create appsettings.json
cat > "src/$PROJECT_NAME.API/appsettings.json" << EOF
{
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"System": "Warning"
}
},
"WriteTo": [
{
"Name": "Console"
},
{
"Name": "File",
"Args": {
"path": "logs/log-.txt",
"rollingInterval": "Day",
"retainedFileCountLimit": 7
}
}
]
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=$PROJECT_NAME;Trusted_Connection=True;TrustServerCertificate=True;"
}
}
EOF
# Create appsettings.Development.json
cat > "src/$PROJECT_NAME.API/appsettings.Development.json" << EOF
{
"Serilog": {
"MinimumLevel": {
"Default": "Debug"
}
},
"DetailedErrors": true
}
EOF
print_info "Configuration files created"
}
# Function to build solution
build_solution() {
print_step "Building solution..."
if dotnet build --verbosity quiet 2>/dev/null; then
print_info "Build succeeded!"
else
print_warning "Build had some issues. Run 'dotnet build' to see details."
fi
}
# Function to display summary
display_summary() {
echo ""
echo "========================================"
echo " ✓ Setup Complete!"
echo "========================================"
echo ""
echo "Project: $PROJECT_NAME"
echo "Framework: .NET 9.0"
echo "Location: $(pwd)"
echo ""
echo "Next Steps:"
echo " 1. cd $PROJECT_NAME"
echo " 2. Review appsettings: src/$PROJECT_NAME.API/appsettings.json"
echo " 3. Build: dotnet build"
echo " 4. Run: cd src/$PROJECT_NAME.API && dotnet run"
echo " 5. Test: dotnet test"
echo ""
echo "Documentation: README.md"
echo "API Docs (after running): https://localhost:5001/swagger"
echo ""
}
# Main execution
main() {
clear
check_dotnet_version
echo ""
get_project_configuration
echo ""
create_directory_structure
create_solution
create_source_projects
create_test_projects
add_project_references
add_test_references
install_mediatr_packages
install_serilog_packages
install_ef_packages
install_production_packages
install_test_packages
cleanup_default_files
create_configuration_files
build_solution
display_summary
}
# Run main function
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment