Overview

Updated

EasyLaunchpad is a comprehensive .NET Core boilerplate designed to accelerate your SaaS application development. It provides a solid foundation with pre-built features, modern architecture, and clean code practices.

Key Features

  • Complete user authentication and authorization system
  • Modular architecture, easy to extend and customize
  • Subscription management with Stripe and Paddle integration
  • Responsive admin dashboard with Tailwind CSS and DaisyUI components
  • Email templating and notification system
  • Background job processing with Hangfire
  • System logs by Serilog
  • Centralized application settings to manage all app areas

Architecture Overview

EasyLaunchpad follows a clean, modular architecture that separates concerns and promotes maintainability:

Backend Architecture

  • Clean Architecture pattern
  • Domain-driven design principles
  • Repository pattern for data access
  • Entity Framework Core ORM
  • Scheduled jobs with Hangfire & System logs

Frontend Architecture

  • Razor Pages for server-rendered UI
  • Tailwind CSS for styling
  • DaisyUI component library
  • Responsive design principles
  • Clean separation of view components and partials

Technology Stack

.NET 8.0
SQL/MySQL/PostgreSQL
JavaScript/jQuery
Tailwind CSS
DaisyUI
Stripe
Paddle Payments
Hangfire
Serilog Logging
Identity

Core Features

  • Automatic database creation as per the server provider
  • Custom branding options
  • Social media integration
  • Email configuration
  • Payment gateway support

Security Features

  • Google OAuth integration
  • reCAPTCHA protection
  • Email confirmation
  • Secure payment processing
  • Admin-only configuration

EasyLaunchpad Quick Setup

5 mins

EasyLaunchpad is a comprehensive, production-ready solution designed to get your business up and running in minimal time. This complete platform comes with all essential features pre-integrated, requiring only simple configuration to launch your branded service.

With your valid license, you can deploy a fully functional SAAS platform by following our streamlined setup process. The intuitive configuration workflow allows you to:

  • Establish core branding (app name, logo, color scheme)
  • Configure essential business operations (payments, email, authentication)
  • Enable advanced features as needed (social integration, security enhancements)

Setup Organization

The setup is organized into mandatory and optional sections:

Essential configurations

(required for basic operation)

  • Server (SQL, MySQL or PostgreSQL)
  • Branding (app name, logo etc)
  • Email account for messages and updates
  • Stripe/Paddle configuration to accept app purchases

Premium features

(activate as your business needs evolve)

  • Google OAuth support
  • Captcha
  • Email confirmation

Most features work immediately after configuration - no additional development required. Our sensible defaults minimize setup time while maintaining flexibility for customization.

Implementation Tip

For optimal results, we recommend completing all basic configurations before launching, then revisiting optional features as your user base grows. The entire setup can typically be completed in under 10 minutes for basic deployments.

Further Reading

For comprehensive technical specifications and advanced configuration options, please refer to the dedicated module documentation. Each feature section contains detailed implementation guides, best practices, and troubleshooting resources to support your deployment.

Follow these steps to quickly configure and customize your application within few minutes.

1. Database Configuration

1

Connection String Setup

Locate appsettings.json

Find the configuration file in your project root

Update Connection String for choosen provider

Modify the Provider and ConnectionStrings with your database details

Run Application

The database will be created automatically on first run

"DatabaseSettings": {
	"Provider": "SqlServer", // Options: "SqlServer", "MySQL", "PostgreSQL"
	"ConnectionStrings": {
	  "SqlServer": "Server=;Database=easylaunchpad_db;Trusted_Connection=True;MultipleActiveResultSets=true;TrustServerCertificate=True;",
	  "MySQL": "Server=;Port=;Database=easylaunchpad_db;Uid=;Pwd=;charset=utf8mb4;",
	  "PostgreSQL": "Host=;Port=;Database=easylaunchpad_db;Username=;Password=;Pooling=true;"
	}
  }
If you want to see more details or you face issues during database configuration, check the Database Providers section .

2. Branding Configuration

2

Customize Application Appearance

Setting Description Location
Application Name The name displayed throughout the app Admin → System Settings → Branding
Favicon Browser tab icon (32x32px, .ico) Admin → System Settings → Branding
Application Logo Main logo displayed in header (150x50px, .png) Admin → System Settings → Branding

3. Footer Configuration

3

Social Links & Copyright

Navigate to Footer Settings

Go to Admin → System Settings → Social & Footer

Add Social Media Links

Enter URLs for Facebook, Twitter, LinkedIn

Set Copyright Notice

Add your copyright text (e.g., "© 2023 Your Company")

Save Changes

Click Save to apply

4. Email Configuration

4

Email Account Setup

Navigate to Email System

Go to Admin → Email System → Email Accounts Add Account

Add new Email Account and Emails will be functional and working after the successful configuration - nothing else is required.

Next, Test the newly added email account, Open Emial Account list, and click on 3 dots menu in the grid, click on Test Email

5. Payment Gateway Setup

5

Stripe/Paddle Integration

Creating Subscription Packages

Navigate to Packages

Go to Admin → Payment System → Packages

Add New Package

Click "Add New Package" button

Configure Package

Set name, price, features, and duration

Save Package

Package will be available for purchase immediately

6. Google OAuth Setup

6

Google Authentication

Before You Begin

You'll need to create OAuth credentials in the Google Cloud Console.

Navigate to Authentication Settings

Go to Admin → System Settings → Authentication

7. reCAPTCHA Setup

7

Google reCAPTCHA

Navigate to Authentication Settings

Go to Admin → System Settings → Security

Note

Get your keys from the reCAPTCHA admin console.

8. Email Confirmation

8

Navigate to Authentication Settings

Go to Admin → System Settings → Authentication

Require Email Confirmation

When enabled, new users will receive a confirmation email with a verification link before they can log in.

Important

Ensure your email configuration is working properly before enabling this feature.

9. Theme Customization

9

Customize UI Elements for your Business/SaaS

Navigate to Landing Page

Open Views/Home/Index.cshtml

Locate Partial Views

Find partial views in Views/Shared/ or Views/Home/Partials/

Modify Components

Edit these files to customize different sections:

  • _Header.cshtml - Navigation and header
  • _Hero.cshtml - Main banner section
  • _Features.cshtml - Features listing
  • _Footer.cshtml - Footer content

CSS Customization

Modify CSHTML files directly with TailwindCSS utility classes according to your need, clean and rebuild soltution and site.csss and admin.css files will be updated automaticaly.

💡 Pro Tip

Want to match the design with your brand? Need more flexibility?

Explore our pre-built variants for the Hero section, Features, Navigation, CTA, and Footer inside the UI documentation.

🎨 Mix & match components and apply your own branding — no need to start from scratch.

Installation Guide

Updated

Get started with EasyLaunchpad in just a few minutes. Follow these steps to set up your development environment and run the application locally.

Prerequisites

Make sure you have the following installed before proceeding:

  • .NET 8.0 SDK
  • Node.js (v22+ recommended) – for Tailwind CSS, frontend assets, and package management
  • Visual Studio 2022+ or VS Code with C# extension
  • At least one supported database engine:
    • SQL Server 2019+ (Express edition supported)
    • MySQL 8.0+
    • PostgreSQL 15+
  • Git for version control
  • IIS (Internet Information Services)

Step 1: Download the source code

Make sure you have proper license to use the code

Step 2: Configure Database Connection

Update the database provider and connection strings in appsettings.json. You can switch between SQL Server, MySQL, or PostgreSQL by changing the Provider value.


{
"DatabaseSettings": {
"Provider": "SqlServer", // Options: "SqlServer", "MySQL", "PostgreSQL"
"ConnectionStrings": {
	"SqlServer": "Server=localhost;Database=easylaunchpad_db;Trusted_Connection=True;MultipleActiveResultSets=true;TrustServerCertificate=True;",
	"MySQL": "Server=localhost;Port=3306;Database=easylaunchpad_db;Uid=root;Pwd=yourpassword;charset=utf8mb4;",
	"PostgreSQL": "Host=localhost;Port=5432;Database=easylaunchpad_db;Username=postgres;Password=yourpassword;Pooling=true;"
}
}
}
					

Example: To use MySQL, set "Provider": "MySQL" and update the MySQL connection string.

If you want to see more details or you face issues during database configuration, check the Database Providers section .

Security Warning

Never commit sensitive connection strings to source control. Use environment variables or user secrets for production environments.

Step 3: Restore NuGet Packages

dotnet restore

This will download all required NuGet packages for the project

Step 4: Restoring Node.js Packages

npm install

It is recommended to have the Node v22+ installed for Tailwind CSS, frontend assets, and package management.

Open terminal in your Easylaunchpad.Website root

Step 5: Build the Application

dotnet build

Compiles the application and checks for build errors

Step 6: Run the Application

dotnet run

The application will automatically:

  • Apply database migrations
  • Start the web server

Success!

You've successfully installed EasyLaunchpad. Default admin credentials are:

  • Email: admin@easylaunchpad.com
  • Password: Admin@123

Folder Structure

Understanding the project structure is essential for efficient development. EasyLaunchpad adopts a clean, modular architecture that promotes separation of concerns and scalability.

EasyLaunchpad/
├── DataAccess/							 # Handles database context and EF Core migrations
│   ├── Context/                         # Provider-specific DbContexts
│   │   ├── SqlServerApplicationDbContext.cs
│   │   ├── MySqlApplicationDbContext.cs
│   │   └── PostgreSqlApplicationDbContext.cs
│   │
│   ├── Factories/                       # DbContext factories for providers
│   │   ├── SqlServerDbContextFactory.cs
│   │   ├── MySqlDbContextFactory.cs
│   │   └── PostgreSqlDbContextFactory.cs
│   │
│   ├── Migrations/                      # Provider-specific migrations
│   │   ├── SqlServer/                   # Migrations for SQL Server
│   │   ├── MySQL/                       # Migrations for MySQL
│   │   └── PostgreSQL/                  # Migrations for PostgreSQL
│   │
│   └── ApplicationDbContext.cs          # Base application DbContext (shared config, conventions)
│
├── Infrastructure/						 # Infrastructure-level implementations
│   ├── Extensions/                      # Core utility and extension methods
│   ├── Filters/                         # Custom action and authorization filters
│   ├── Services/                        # Service interfaces and implementations
│   └── Utilities/                       # Middleware, constants, and helper classes
│
├── Models/								 # Domain models and shared DTOs
│   ├── Db/                              # Entity framework domain entities
│   ├── Dto/                             # Data Transfer Objects
│   ├── VM/                              # View Models for UI binding
│   └── Utilities/                       # Enums, constants, and shared types
│
└── Website/							 # Main web application (MVC + Razor)
├── Areas/                           # Modular feature areas (e.g., Admin)
├── Controllers/                     # API and MVC controllers
├── Models/                          # View-specific model classes
├── Core/                            # Core configurations (e.g., AutoMapper, jobs)
├── Views/                           # Razor views and layout templates
├── wwwroot/                         # Static files (CSS, JS, images)
└── Program.cs                       # Application entry point

Configuration

EasyLaunchpad uses a configuration system that allows you to customize various aspects of the application without modifying code.

Application Settings

The main configuration file is appsettings.json. Here are the key sections:


{
"Logging": {
"LogLevel": {
	"Default": "Information",
	"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"DatabaseSettings": {
"Provider": "SqlServer", // Options: "SqlServer", "MySQL", "PostgreSQL"
"ConnectionStrings": {
	"SqlServer": "Server=;Database=easylaunchpad_db;Trusted_Connection=True;MultipleActiveResultSets=true;TrustServerCertificate=True;",
	"MySQL": "Server=;Port=;Database=easylaunchpad_db;Uid=;Pwd=;charset=utf8mb4;",
	"PostgreSQL": "Host=;Port=;Database=easylaunchpad_db;Username=;Password=;Pooling=true;"
}
}
}

Environment-Specific Configuration

For different environments, use environment-specific settings files:

  • appsettings.Development.json - Development environment
  • appsettings.Staging.json - Staging environment
  • appsettings.Production.json - Production environment

Security Best Practice

Store sensitive configuration values like API keys and connection strings in environment variables or user secrets. Never commit these values to source control.

Authentication & Authorization

EasyLaunchpad provides a complete authentication and authorization system built on ASP.NET Core Identity.

Authentication Options

The following authentication methods are supported out of the box:

Local Authentication

  • Username/password authentication
  • Email confirmation
  • Password reset functionality

External Providers

  • Google authentication

Configuring External Providers

To enable external authentication providers, enable the open authentication from admin > settings > authentication settings:

Role-Based Authorization

EasyLaunchpad includes a role-based authorization system with predefined roles:

Role Description Permissions
Admin System-wide administrator Full access to all features
Registered Standard user Basic application access

Using Authorization in Controllers

Apply authorization attributes to controllers or actions:

[Authorize(Roles = "Admin,Registered")]
public class UserManagementController : Controller
{
	[HttpGet]
	public IActionResult Index()
	{
		// Only TenantAdmin and Manager can access this
		return View();
	}

	[Authorize(Roles = "Admin")]
	[HttpPost]
	public async Task DeleteUser(string userId)
	{
		// Only Admin can delete users
		// Implementation...
		return RedirectToAction("Index");
	}
}

Multi-Database Provider Support

Advanced

Seamless support for SQL Server, MySQL, and PostgreSQL, with an extensible architecture for adding additional providers like Oracle, SQLite, and beyond. Switch between providers with simple configuration changes while maintaining a single codebase.

SQL Server MySQL PostgreSQL Extensible Isolated Migrations

System Overview

The multi-database provider system isolates database-specific logic while keeping business code provider-agnostic. Applications can switch providers or add new ones with minimal changes. Each provider has its own DbContext, migrations, and connection strings, ensuring complete isolation and type safety.

Supported Providers

  • SQL Server (Default)
  • MySQL (Pomelo)
  • PostgreSQL (Npgsql)

Architecture

  • Base ApplicationDbContext
  • Provider-specific DbContexts
  • Design-time factories
  • Isolated migration folders

Integrated Services

  • Hangfire background jobs
  • Serilog logging
  • Identity authentication
  • Automatic migrations
Key Benefit: The architecture ensures complete provider isolation with type-safe, compile-time checking while maintaining a single business logic codebase.

Project Structure

DataAccess/
├── Context/
│   ├── ApplicationDbContext.cs          # Base context with shared configurations
│   ├── MySqlApplicationDbContext.cs     # MySQL-specific configurations
│   ├── PostgreSQLApplicationDbContext.cs # PostgreSQL-specific configurations
│   └── SqlServerApplicationDbContext.cs # SQL Server (uses defaults)
├── Factories/
│   ├── MySqlDbContextFactory.cs         # Design-time factory for MySQL
│   ├── PostgresDbContextFactory.cs      # Design-time factory for PostgreSQL
│   └── SqlServerDbContextFactory.cs     # Design-time factory for SQL Server
└── Migrations/
	├── MySQL/                           # MySQL-specific migrations
	├── PostgreSQL/                      # PostgreSQL-specific migrations
	└── SqlServer/                       # SQL Server-specific migrations

Configuration Setup

1. Configure appsettings.json

Add database settings with your chosen provider and connection strings for all supported databases:

{
  "DatabaseSettings": {
	"Provider": "SqlServer",  // Options: SqlServer, MySQL, PostgreSQL
	"ConnectionStrings": {
	  "SqlServer": "Server=(localdb)\\mssqllocaldb;Database=YourDb;Trusted_Connection=True;MultipleActiveResultSets=true",
	  "MySQL": "Server=localhost;Port=3306;Database=YourDb;Uid=root;Pwd=yourpassword;",
	  "PostgreSQL": "Host=localhost;Port=5432;Database=YourDb;Username=postgres;Password=yourpassword;"
	}
  }
}

2. Install Required NuGet Packages

Install packages based on your chosen provider:

dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Hangfire.SqlServer
dotnet add package Serilog.Sinks.MSSqlServer

Provider-Specific Configurations

SQL Server

Uses default EF Core conventions with no special configuration needed.

Features:

  • Native datetime types
  • Full EF Core feature support
  • Optimal for Windows
  • No string length restrictions

MySQL

Requires explicit configuration for string keys and indexes.

Key Considerations:

  • 255 char limit on key fields
  • LONGTEXT for regular strings
  • DATETIME(6) for precision
  • TINYINT(1) for booleans
Disable sql_require_primary_key for Hangfire

PostgreSQL

Normalizes identifiers and enforces UTC timezone handling.

Features:

  • Lowercase table/column names
  • Automatic UTC conversion
  • TEXT type for strings
  • timestamp with time zone
Always use DateTime.UtcNow

Working with Migrations

Each provider maintains separate migrations in isolated folders. This prevents conflicts and allows provider-specific optimizations.

Creating Migrations

SQL Server

dotnet ef migrations add MigrationName \
  --context SqlServerApplicationDbContext \
  --output-dir Migrations/SqlServer

MySQL

dotnet ef migrations add MigrationName \
  --context MySqlApplicationDbContext \
  --output-dir Migrations/MySQL

PostgreSQL

dotnet ef migrations add MigrationName \
  --context PostgreSQLApplicationDbContext \
  --output-dir Migrations/PostgreSQL

Applying Migrations

The application automatically applies migrations on startup through the EnsureDatabaseAsync() method. You can also manually apply them:

SQL Server:

dotnet ef database update --context SqlServerApplicationDbContext

MySQL:

dotnet ef database update --context MySqlApplicationDbContext

PostgreSQL:

dotnet ef database update --context PostgreSQLApplicationDbContext
Automatic Migration: The application automatically detects the configured provider and applies the appropriate migrations on startup via EnsureDatabaseAsync()

Switching Database Providers

Switching between database providers is straightforward and requires minimal configuration changes.

  1. 1

    Update Configuration

    Change the Provider value in appsettings.json:

    {
      "DatabaseSettings": {
    	"Provider": "MySQL"  // Change from SqlServer to MySQL
      }
    }
  2. 2

    Verify Connection String

    Ensure the connection string for your chosen provider is configured correctly in appsettings.json

  3. 3

    Ensure Required Packages

    Verify that the NuGet packages for your chosen provider are installed

  4. 4

    Restart Application

    The Program.cs automatically configures everything based on your provider choice. Migrations apply automatically on startup.

No code changes required! The application automatically detects the provider and configures Hangfire, Serilog, and Identity accordingly.

Core Services

IDatabaseConfigurationService

Manages database provider configuration and connection strings.

public interface IDatabaseConfigurationService
{
	DatabaseProvider GetDatabaseProvider();
	string GetConnectionString();
	void ConfigureDbContext(
		DbContextOptionsBuilder options, 
		string connectionString, 
		DatabaseProvider provider);
}

Usage Example:

var dbService = serviceProvider
	.GetRequiredService<IDatabaseConfigurationService>();

var provider = dbService.GetDatabaseProvider();
var connectionString = dbService.GetConnectionString();

IMigrationService

Handles database creation and migration application.

public interface IMigrationService
{
	Task EnsureDatabaseAsync();
}

Usage Example:

var migrationService = serviceProvider
	.GetRequiredService<IMigrationService>();

await migrationService.EnsureDatabaseAsync();
Automatically called during startup

Extending to New Database Providers

Want to add support for Oracle, SQLite, or another database? Follow this comprehensive step-by-step guide:

1

Add Provider to Enum

Update the DatabaseProvider enum to include your new provider:

public enum DatabaseProvider
{
	SqlServer,
	MySQL,
	PostgreSQL,
	Oracle  // Your new provider
}
2

Create Provider-Specific Context

Create a new context class that inherits from ApplicationDbContext:

public class OracleApplicationDbContext : ApplicationDbContext
{
	public OracleApplicationDbContext(
		DbContextOptions<OracleApplicationDbContext> options, 
		IConfiguration configuration)
		: base(options, configuration)
	{
	}

	protected override void OnModelCreating(ModelBuilder builder)
	{
		base.OnModelCreating(builder);

		// Add Oracle-specific configurations here
		// Example: Configure sequences, NVARCHAR2 types, etc.
		foreach (var entityType in builder.Model.GetEntityTypes())
		{
			foreach (var property in entityType.GetProperties())
			{
				// Oracle-specific type mappings
				if (property.ClrType == typeof(string) 
					&& property.GetMaxLength() == null)
				{
					property.SetColumnType("NVARCHAR2(2000)");
				}
			}
		}
	}
}
3

Create Design-Time Factory

Implement IDesignTimeDbContextFactory for EF Core tooling:

public class OracleDbContextFactory 
	: IDesignTimeDbContextFactory<OracleApplicationDbContext>
{
	public OracleApplicationDbContext CreateDbContext(string[] args)
	{
		var configuration = new ConfigurationBuilder()
			.SetBasePath(Directory.GetCurrentDirectory())
			.AddJsonFile("appsettings.json")
			.AddJsonFile("appsettings.Development.json", optional: true)
			.Build();

		var connectionString = configuration
			.GetSection("DatabaseSettings:ConnectionStrings:Oracle").Value;

		if (string.IsNullOrEmpty(connectionString))
			throw new InvalidOperationException(
				"Oracle connection string not found in configuration.");

		var optionsBuilder = new DbContextOptionsBuilder<OracleApplicationDbContext>();
		optionsBuilder.UseOracle(connectionString, oracleOptions =>
		{
			oracleOptions.EnableRetryOnFailure(
				maxRetryCount: 3,
				maxRetryDelay: TimeSpan.FromSeconds(5),
				errorCodesToAdd: null);
		});

		return new OracleApplicationDbContext(optionsBuilder.Options, configuration);
	}
}
4

Update DatabaseConfigurationService

Add your provider case to the ConfigureDbContext method:

public void ConfigureDbContext(
	DbContextOptionsBuilder options, 
	string connectionString, 
	DatabaseProvider provider)
{
	switch (provider)
	{
		// ... existing cases ...

		case DatabaseProvider.Oracle:
			options.UseOracle(connectionString, oracleOptions =>
			{
				oracleOptions.EnableRetryOnFailure(
					maxRetryCount: 3,
					maxRetryDelay: TimeSpan.FromSeconds(5),
					errorCodesToAdd: null);
			});
			break;

		default:
			throw new NotSupportedException(
				$"Database provider {provider} is not supported");
	}
}
5

Update DatabaseExtensions

Add registration in AddEasyLaunchpadDatabase method:

case DatabaseProvider.Oracle:
	services.AddDbContext<ApplicationDbContext, OracleApplicationDbContext>(
		(serviceProvider, options) =>
		{
			var dbConfigService = serviceProvider
				.GetRequiredService<IDatabaseConfigurationService>();
			var connectionString = dbConfigService.GetConnectionString();
			dbConfigService.ConfigureDbContext(
				options, connectionString, DatabaseProvider.Oracle);

			if (environment.IsDevelopment())
			{
				options.EnableSensitiveDataLogging();
				options.LogTo(Console.WriteLine, LogLevel.Information);
			}
		});
	break;
6

Update Program.cs for Hangfire

Add Hangfire storage configuration for your provider:

case DatabaseProvider.Oracle:
	config.UseOracleStorage(connectionString, new OracleStorageOptions
	{
		QueuePollInterval = TimeSpan.FromSeconds(15),
		SchemaName = "HANGFIRE"
	});
	break;
7

Update Program.cs for Serilog

Add Serilog sink configuration for your provider:

case DatabaseProvider.Oracle:
	loggerConfig.WriteTo.Logger(lc => lc
		.Filter.ByIncludingOnly(e =>
			e.Level == LogEventLevel.Warning ||
			e.Level == LogEventLevel.Error ||
			(e.Level == LogEventLevel.Information &&
			 e.Properties.ContainsKey("SourceContext") &&
			e.Properties["SourceContext"].ToString().StartsWith("\"Easylaunchpad"))
		)
		.WriteTo.Oracle(
			connectionString: connString,
			tableName: appIdentityService.LogTableName,
			autoCreateSqlTable: true));
	break;
8

Create Migration Directory

Create a new folder for provider-specific migrations:

DataAccess/
└── Migrations/
	└── Oracle/  <-- Create this folder
9

Update Configuration

Add connection string to appsettings.json:

{
  "DatabaseSettings": {
	"Provider": "Oracle",
	"ConnectionStrings": {
	  "Oracle": "Data Source=localhost:1521/ORCL;User Id=system;Password=oracle;"
	}
  }
}
Success! Your new provider is now fully integrated. Create migrations and start using it immediately.

Best Practices

Follow these guidelines to ensure smooth operation across all database providers and maintain code quality.

Connection Strings

  • Development: Store in appsettings.Development.json
  • Production: Use environment variables or Azure Key Vault
  • Security: Never commit credentials to source control
  • Validation: Test connections before deployment

Provider-Specific Migrations

Never mix migrations from different providers

  • Use separate folders for each provider
  • Never manually modify generated migrations
  • Test on dev database first
  • Keep migration history in source control

DateTime Handling

Consistent datetime handling across providers:

  • SQL: Works with local and UTC times
  • MY: Stores as-is; manage UTC in code
  • PG: Auto converts to/from UTC
entity.CreatedAt = DateTime.UtcNow;

String Length Limits

Always specify max length for indexed string properties:

builder.Entity<YourEntity>()
	.Property(e => e.Email)
	.HasMaxLength(255);

Prevents "key too long" errors in MySQL

Testing Migrations

Before deploying, test on all providers:

# Test each provider
dotnet ef database update \
  --context SqlServerApplicationDbContext

dotnet ef database update \
  --context MySqlApplicationDbContext

dotnet ef database update \
  --context PostgreSQLApplicationDbContext

Performance

Connection pooling enabled by default:

  • SQL: Min Pool Size=5; Max=100
  • MY: MinimumPoolSize=5; Max=100
  • PG: Minimum Pool Size=5; Max=100
Retry logic: 3 attempts, 5s delays

Pro Tips

💡 Entity Configuration: Configure shared logic in the base ApplicationDbContext, override in provider-specific contexts for optimizations

💡 LINQ Queries: Some LINQ queries may not translate identically across providers - always test queries when switching databases

💡 Centralize Enums: Maintain DatabaseProvider enum and update DI in one place for clean, extensible code

💡 Integration Tests: Run comprehensive tests on all target providers before production deployment

Troubleshooting

Migration not found

Solution:

  • Ensure you're in the correct project directory
  • Verify the migration folder exists
  • Check that you're using the correct context name
cd /path/to/DataAccess
dotnet ef migrations list --context MySqlApplicationDbContext
Connection timeout

Solution:

  • Verify database server is running
  • Check firewall settings allow connections
  • Validate connection string format
  • Test connection with database management tool (SSMS, MySQL Workbench, pgAdmin)
"Table already exists" error

Solution:

Drop the database or roll back migrations, then reapply:

# Roll back all migrations
dotnet ef database update 0 --context YourDbContext

# Then reapply
dotnet ef database update --context YourDbContext
Provider package not found

Solution:

  • Ensure correct NuGet packages are installed for your provider
  • Check package versions are compatible with your EF Core version
  • Restore NuGet packages: dotnet restore
MySQL: "Specified key was too long"

Solution:

The MySqlApplicationDbContext automatically applies 255 character limits to key fields. If you still encounter this error:

  • Check for custom string properties that are indexed
  • Manually set HasMaxLength(255) on these properties
  • Ensure the ConfigureKeyStringProperties method is being called
MySQL Hangfire: "sql_require_primary_key"

Solution:

Disable this MySQL setting or ensure Hangfire tables have primary keys:

SET GLOBAL sql_require_primary_key = 0;

This allows Hangfire to auto-create its schema tables properly.

PostgreSQL: Case sensitivity issues

Solution:

PostgreSQL context automatically converts all identifiers to lowercase. If you encounter issues:

  • Use lowercase in all raw SQL queries
  • Double-check table and column names in your queries
  • Avoid mixing case in entity property names
Different SQL syntax across providers

Solution:

  • Some LINQ queries may not translate identically across providers
  • Test queries individually when switching providers
  • Avoid provider-specific SQL functions in LINQ queries
  • Use EF Core's built-in functions that work across all providers

Summary

What You Get with Multi-Database Support

Key Features
  • Flexibility: Switch providers via simple configuration
  • Extensibility: Clear pattern for adding new providers
  • Separation: Provider logic isolated from business code
  • Type Safety: Compile-time checking for each provider
  • Automation: Migrations apply automatically on startup
Integrated Services
  • Hangfire: Background jobs configured per provider
  • Serilog: Database logging for each provider
  • Identity: ASP.NET Identity fully integrated
  • Migrations: Automatic detection and application
  • Retry Logic: Built-in transient failure handling

Single Codebase, Multiple Databases

Your application adapts to different deployment environments while maintaining a unified business logic layer. Whether you're deploying to Azure SQL, AWS RDS MySQL, or Google Cloud PostgreSQL, the same code works seamlessly.

Admin Dashboard

Updated

The Admin Dashboard provides real-time monitoring of your application's key metrics and operations.

Dashboard Features

  • Quick status overview of all system components
  • Visual indicators for critical issues
  • Historical data tracking and trends

System Requirements

  • Modern web browser (Chrome, Firefox, Edge, Safari)
  • Admin privileges to access the dashboard
  • ASP.NET Core 8.0 runtime

Dashboard Widgets

Sync

All widgets showing quick data overview. Click any widget for more detailed information.

1

Users & Roles Widget

Description

Displays statistics about user accounts and their roles in the system. Shows total users, active users.

Technical Details

  • Endpoint: /dashboard/user-role-stats
  • Data Source: User service
2

Email Notification Widget

Description

Tracks email notification status including queued emails, emails sent today, failed deliveries, and scheduled emails with color-coded severity indicators.

Technical Details

  • Endpoint: /dashboard/email-notification
  • Data Source: QueuedEmail database table
  • Metrics:
    • Queued: Emails waiting to be sent
    • Sent Today: Successful deliveries today
    • Failed: Emails with >3 failed attempts
3

Cron Jobs Widget

Description

Monitors background jobs and scheduled tasks, showing currently running jobs, recurring jobs, and success/failure rates.

Technical Details

  • Endpoint: /dashboard/cron-job
  • Data Source: ScheduledJob database table
  • Metrics:
    • Recurring Jobs: Total scheduled jobs
    • Running Now: Currently executing jobs
    • Success/Fail: Last 24 hours stats
4

Logs Widget

Description

Provides insight into system logging activity, showing total logs today broken down by severity level (info, warning, error) with trend analysis.

Technical Details

  • Endpoint: /dashboard/logs
  • Data Source: Logs database table
  • Metrics:
    • Total Logs: Today's log entries
    • Info: Informational messages
    • Warning: Potential issues
    • Error: Critical errors
    • Logs/Hour: Average logging rate
5

Sitemap Widget

Description

Tracks the status of your website's sitemap, showing total pages, indexed pages, and hidden pages with last update timestamp.

Technical Details

  • Endpoint: /dashboard/sitemap
  • Data Source: SitemapEntry database table
  • Metrics:
    • Total Entries: All sitemap entries
    • Indexed: Pages marked for search engines
    • Hidden: Pages with noindex
    • Last Update: Most recent change
6

Payment & Subscriptions Widget

Description

Provides financial metrics including today's payments, active subscriptions, failed payments, and upcoming renewals with revenue breakdowns.

Technical Details

  • Endpoint: /dashboard/payments-subscription
  • Data Source: Subscription database table
  • Metrics:
    • Payments Today: Total revenue today
    • Active Subs: Currently active subscriptions
    • Failed Payments: Today's failures
    • Upcoming Renewals: Next 7 days
    • MRR (Monthly Recurring Revenue): Current monthly revenue

Extending the Dashboard

Important

These extensions require developer access and knowledge of ASP.NET Core and Razor views.

Adding New Widgets

To add a new widget to the dashboard:

  1. Create a new partial view in Views/Shared folder
  2. Add the widget to the dashboard grid in Views/Dashboard/Index.cshtml
  3. Create a new endpoint in DashboardController
  4. Implement the service method in DashboardService
  5. Create DTO classes for the response data

Customizing Widgets

Existing widgets can be customized by:

  • Modifying the partial view HTML/CSS
  • Adding new metrics to service methods
  • Extending DTO classes with additional properties
  • Changing visualization types (charts, gauges, etc.)

Example Widget Implementation

// 1. Add Controller Endpoint
[HttpGet("new-widget")]
public async Task<IActionResult> GetNewWidgetData()
{
	var data = await _dashboardService.GetNewWidgetDataAsync();
	return Ok(data);
}

// 2. Add Service Method
public async Task<NewWidgetDto> GetNewWidgetDataAsync()
{
	// Fetch data from database or external API
	var metrics = await _repository.GetWidgetMetricsAsync();

	return new NewWidgetDto 
	{
		TotalItems = metrics.Count,
		SuccessRate = metrics.SuccessPercentage,
		LastUpdated = DateTime.UtcNow
	};
}

// 3. Create DTO
public class NewWidgetDto
{
	public int TotalItems { get; set; }
	public double SuccessRate { get; set; }
	public DateTime LastUpdated { get; set; }
}

// 4. Add partial view to dashboard
<partial name="_NewWidget" model="Model.NewWidgetData" />

Widget Customization Example

public class EmailNotificationDto
{
	public int Queued { get; set; }
	public int SentToday { get; set; }
	public int Failed { get; set; }

	// Added properties
	public double DeliveryRate { get; set; }
	public TimeSpan AvgProcessingTime { get; set; }
	public Dictionary<string, int> ByProvider { get; set; }

	[JsonIgnore]
	public bool IsHealthy => DeliveryRate > 0.95;
}

User & Roles Management

Updated

Manage users and roles, control access, and define responsibilities across your application.

User Management Features

  • Create, update, and delete user accounts
  • Assign multiple roles to users
  • Activate/deactivate accounts
  • Advanced search & filtering
  • Pagination support
  • Profile management

Role Management Features

  • Create, update, and delete roles
  • Search & filter capabilities
  • Pagination support
  • Default role protection

User Management

Important

All user management endpoints require admin privileges.

1

User Statuses

Status Value Description
Active 1 User can log in and access the system
Inactive 2 User account deactivated/inactive and cannot login and access the system
Pending 3 User account created but not yet activated
Deleted 4 Soft-deleted user (retained for auditing)
2

User's Controller Endpoints

Endpoint Method Description Parameters
/user GET Main user management page None
/user/list POST Get paginated user list start, draw, role, status, search, length
/user/add GET Get add user form None
/user/add POST Create new user UserVM model
/user/update/{id} GET Get edit user form User ID
/user/update/{id} PUT Update user User ID, UserVM model
/user/delete/{userId} DELETE Delete user User ID
3

User Data Models

UserVM (View Model)

public class UserVM {
  public string Email { get; set; }
  public string Password { get; set; }
  public string FullName { get; set; }
  public string PhoneNumber { get; set; }
  public string Address { get; set; }
  public string City { get; set; }
  public string State { get; set; }
  public string Country { get; set; }
  public string ZipCode { get; set; }
  public IFormFile ProfilePicture { get; set; }
  public string[] Role { get; set; }
  public bool IsActive { get; set; }
}

UserUpdateDto

public class UserUpdateDto {
  public string Id { get; set; }
  public string Email { get; set; }
  public string FullName { get; set; }
  public string PhoneNumber { get; set; }
  public string Address { get; set; }
  public string City { get; set; }
  public string State { get; set; }
  public string Country { get; set; }
  public string ZipCode { get; set; }
  public string ProfilePicture { get; set; }
  public string[] RoleIds { get; set; }
  public SelectListItem[] RolesList { get; set; }
  public bool IsActive { get; set; }
}

Role Management

Security Note

Role modifications can affect system security. Changes are logged and require admin privileges.

1

Role's Controller Endpoints

Endpoint Method Description Parameters
/role GET Main role management page None
/role/list POST Get paginated role list start, draw, search, length
/role/add GET Get add role form None
/role/add POST Create new role RoleVM model
/role/update/{id} GET Get edit role form Role ID
/role/update/{roleId} PUT Update role Role ID, RoleVM model
2

Role Data Models

RoleVM (View Model)

public class RoleVM {
  public string Name { get; set; }
}
3

Default System Roles

Role Description Permissions
Admin Application administrator Full access within the application
Registered Standard user Basic application access

Extending Functionality

Developer Note

These extensions require developer access and knowledge of ASP.NET Core and Razor views.

Adding New User Fields

To add new fields to the user model:

  1. Add the property to ApplicationUser entity
  2. Update the UserVM and UserUpdateDto models
  3. Modify the Add and Update methods in UserService
  4. Update the relevant views (add/edit forms)
  5. Add any necessary validation rules
1

Customizing User Search

To add new search criteria to the user list:

// In UserService.GetAllUsersAsync()
if (!string.IsNullOrEmpty(searchPage.NewCriteria))
{
	query = query.Where(u => u.NewProperty.Contains(searchPage.NewCriteria));
}
2

Implementing User Import

To add bulk user import functionality:

// In UserController
[HttpPost("import")]
public async Task ImportUsers(IFormFile file)
{
	if (file == null || file.Length == 0)
		return BadRequest("No file uploaded");

	using var stream = file.OpenReadStream();
	var result = await _userService.ImportUsersAsync(stream);

	if (result.Success)
		return Ok(result);

	return BadRequest(result);
}

Security Considerations

  • Always validate and sanitize user input when managing users and roles
  • Implement proper audit logging for all user and role modifications
  • Consider implementing rate limiting on user management endpoints
  • Protect sensitive operations with additional authentication factors
  • Regularly review role assignments

Payment & Subscription

Updated

Comprehensive documentation for managing payment gateways, packages, and subscriptions

These modules allow administrators to configure payment gateways, create subscription packages, and manage customer subscriptions.

Payment Gateways

  • Stripe and Paddle integration
  • Secure credential storage
  • Default gateway selection
  • No-code configuration

Packages

  • Multiple package types
  • Flexible durations
  • Gateway association
  • Easy management

Subscriptions

  • Active/inactive tracking
  • Searchable records
  • Pagination support
  • Detailed monitoring

Payment Gateway

Controller: PaymentGatewayController.cs

Handles all operations related to payment gateways including listing, adding, updating, and deleting gateways. It also allows for configuring gateway-specific settings such as API credentials.

Key Actions

Action Description
Index() Returns the payment gateway overview page
GetList() Retrieves a paginated list of gateways
Add()/Update() Shows the form to add or edit a gateway
SetDefault() Marks a payment gateway as default
Delete() Deletes a payment gateway

The configuration interface for payment methods is intuitive and secure. Simply paste your credentials and save - no code changes required!

Configuration Process

Stripe Configuration

  • Publishable Key
  • Secret Key
  • Webhook Secret
  • Customer Portal Url

Paddle Configuration

  • API Key
  • Client Token
  • Success Url
  • Error Url
  • Customer Portal Url

Packages

Controller: PackageController.cs

Manages subscription packages, each linked to a payment gateway with specific types and frequencies.

Key Actions

Action Description
Index() View for all packages
GetList() Retrieves paginated package list
Add()/Update() Opens views for package editing

Adding packages automatically update the public pricing page.

Package Configuration

Package Properties

  • Associated Gateway
  • Name and Description
  • Actual Amount (To show the original pricing)
  • Current Amount(Show discounted pricing)
  • Pricing Header (Pricing Heading inside the pricing card)
  • Currency symbol selection
  • Billing Frequency (One time or Subscription)
  • Product Id (from Paddle/Stripe)
  • Price Id (from Paddle/Stripe)
  • Display order (used to show the packages 1st, 2nd or the 3rd place)
  • Package Type (Basic, Best Valued, Most Popular) Shows the outer lines highlighted and packge is prominantly shown
  • Active (show the package on public site or not)
  • Description (Package features to be added here)

UI Elements

  • Gateway dropdown
  • Type selection
  • Duration options
  • Price input with validation

Important Notes

  • Package features must be manually configured in the Pricing section
  • All payment credentials are encrypted before storage
  • Test your new payment gateway in sandbox mode before going live

Email Templates

Updated

Dynamic email templating, queuing, and sending system for Easylaunchpad using DotLiquid templates and SMTP configurations.

Email Accounts

  • SMTP configuration
  • Multiple account support
  • AES encrypted credentials
  • Default account selection

Templates

  • DotLiquid templating
  • Dynamic placeholders
  • Subject/Body customization
  • Recipient fields

Queue System

  • Asynchronous sending
  • Bulk email support
  • Retry mechanism
  • Background processing

Email Account Configuration

Security Note

SMTP credentials are encrypted using AES-256 before storage in the database.

1

SMTP Account Management

The EmailAccount model represents SMTP credentials and settings for sending emails.

Key Features

  • Multiple SMTP account support
  • Default account selection
  • SSL/TLS encryption
  • Credential encryption using AES
  • Test email functionality

Message Templates with DotLiquid

1

Dynamic Email Templating

MessageTemplates are stored email blueprints that use DotLiquid placeholders for dynamic content rendering.

Template Fields

Field Description Example Placeholder
Subject Email subject line {{OrderNumber}}
Body HTML/Text email content {{CustomerName}}
To Primary recipient {{UserEmail}}
CC/BCC Carbon copy recipients {{ManagerEmail}}

Rendering Process

Load Template

Retrieve template from database by name

Prepare Data

Create dictionary with placeholder values

Render Content

DotLiquid replaces placeholders with actual values

Queue Email

Add rendered email to queue for sending

Queued Email System

Performance Note

Emails are processed asynchronously via background service for better performance.

1

Asynchronous Email Processing

Emails are inserted into the QueuedEmail table and sent by a background service.

Queue Benefits

Reliability

Emails persist in queue until successfully sent

Performance

Non-blocking sending process

Retry Mechanism

Automatic retries for failed sends

Bulk Sending

Efficient processing of multiple emails

Queue Status Tracking

Status Description
Pending Email waiting to be sent
Sent Successfully delivered
Retrying Failed but will retry
Failed Permanently failed after retries

EmailEngineService

1

Primary Email Interface

The EmailEngineService.Execute() method is the main entry point for sending emails.

Method Signature

Task Execute(string templateName, Dictionary<string, string> keyValuePairs);

Required Dictionary Keys

  • Subject - Email subject line
  • ToEmail - Primary recipient email
  • ReplyTo - (Optional) Reply-to address
  • CC/BCC - (Optional) Carbon copy recipients

Adding New Templates

Important

Ensure all required placeholders are provided when executing templates to avoid rendering errors.

1

Template Creation Process

  1. Add template to database

    Create new record in MessageTemplate table with placeholders

  2. Define required placeholders

    Document all variables needed for the template

    // Required placeholders:
    // - CustomerName
    // - OrderNumber
    // - CustomerEmail
    // - OrderTotal
  3. Test the template

    Use sample data to verify rendering works correctly

  4. Implement in code

    Call EmailEngineService.Execute() with proper data

Important Notes

  • SMTP credentials are encrypted using AES-256 before storage

  • Emails are sent asynchronously via background processing

  • Test templates thoroughly before production use

Scheduled Jobs

Updated

Comprehensive solution for managing background jobs in ASP.NET Core with Hangfire integration.

User Interface

  • Job listing with sorting/filtering
  • Advance Control Panel (Hangfire)
  • Quick Run action
  • Server-side pagination

Service Layer

  • Job initialization
  • Type synchronization
  • Automatic discovery

Job Interfaces

  • IScheduledJob
  • IAsyncScheduledJob
  • AsyncScheduledJob base
  • Flexible implementation

User Interface Components

Note

All UI components are responsive and work across desktop and mobile devices.

1

Index View (Index.cshtml)

Feature Description
Job Listing Datatable with all scheduled jobs
Columns Name, Type, Schedule, Status, Dates
Actions Quick Run
Pagination Server-side with sorting/searching

Service Layer (SchedulerJobService)

Important

Service layer handles all job management logic and synchronization with Hangfire.

1

Core Functionality

Job Initialization

public async Task InitializeSchedulerJobsAsync()
{
  // Setup all active jobs with Hangfire
  // RecurringJob.AddOrUpdate(...)
}

Key Features

  • Automatic discovery of job implementations
  • Support for sync/async job types
  • Database persistence of configurations
  • Automatic reinitialization on updates

Job Interfaces

1

IScheduledJob

Base interface for all scheduled jobs

public interface IScheduledJob
{
  void Execute();
}
2

IAsyncScheduledJob

Extends IScheduledJob for async operations

public interface IAsyncScheduledJob : IScheduledJob
{
  Task ExecuteAsync();
}
3

AsyncScheduledJob

Abstract base class for async jobs

public abstract class AsyncScheduledJob : IAsyncScheduledJob
{
  public abstract Task ExecuteAsync();

  public void Execute() => 
	throw new NotSupportedException();
}

Extending the Functionality

Developer Note

These extensions require developer access and knowledge of ASP.NET Core and Hangfire.

1

Adding New Job Types

  1. Implement IScheduledJob or IAsyncScheduledJob
  2. System auto-discovers & registers the types to DI
  3. Sync with database types (optional)
public class MyNewJob : IAsyncScheduledJob
{
  public async Task ExecuteAsync()
  {
	// Job logic here
	await Task.Delay(1000);
  }

  public void Execute() => 
	throw new NotSupportedException();
}

// In Startup.cs
services.AddTransient();
2

Customizing Job Behavior

  1. Change default schedule in InitializeSchedulerJobsAsync Or SyncJobTypesWithDatabaseAsync (if not depending on hanfgire storage)
  2. Extend ScheduledJob entity for parameters
  3. Update DTOs and views
// Example: Changing default schedule
var newJob = new ScheduledJob
{
	JobName = jobType.Name,
	JobType = typeName,
	CronExpression = Cron.Hourly(), // Default
	IsActive = false,
	CreatedAt = DateTime.UtcNow
};

Usage Instructions

Action Instructions
Viewing Jobs Navigate to Scheduled Jobs page to see all configured jobs
Advance Configuration Navigate to the Hangfire console panel
Quick Run Use action button to immediately execute the job
Adding New Jobs Implement interface - system auto-discovers new jobs

Best Practices

Job Implementation

  • Use IAsyncScheduledJob for IO-bound work
  • Keep CPU-bound jobs short
  • Implement robust error handling
  • Design jobs to be idempotent

Monitoring

  • Add detailed execution logging
  • Set up alerts for failures
  • Review Hangfire dashboard

Troubleshooting

Issue Solution
Job not appearing
  • Verify interface implementation
  • Check DI registration
  • Review discovery logs
Job not executing
  • Check active status
  • Validate cron expression
  • Review Hangfire errors
Changes not effective
  • Trigger reinitialization
  • Restart application
  • Clear Hangfire cache

System Summary

This system provides a flexible foundation for scheduled job management that can be extended to meet specific application needs while maintaining a consistent interface for administration.

Sitemaps

Updated

Automated website crawling and XML sitemap generation solution

SEO Crawler XML Sitemap

System Overview

Note

This system automatically crawls your website and generates standards-compliant XML sitemaps through scheduled background jobs.

Scheduled Job

  • Runs on configurable schedule (daily)
  • Controlled by SEO settings
  • Uses application's root as the base URL
  • Automatic execution

Crawler Service

  • Starts from root URL
  • Follows internal links
  • Excludes admin paths
  • Normalizes URLs

Data Management

  • CRUD operations
  • Paginated listing
  • Duplicate detection
  • Search/sort capabilities

Key Components

1

CrawlAndGenerateSitemapJob

Responsibilities

  • Executes sitemap generation process
  • Checks auto-generation setting
  • Initiates crawling process
  • Generates and update sitemap.xml

Configuration

  • SeoSettings.GenerateAutoSitemap flag
  • Root Address as Base URL and from SeoSettings.SitemapBaseUrl primarily from Application Constants
  • Cron schedule configuration
2

SitemapCrawlerService

Website Crawling

  • Starts from root URL (/)
  • Follows internal links
  • Excludes admin/account paths
  • Processes only HTML with 200 status

URL Normalization

  • Converts to absolute URLs
  • Removes fragments/queries
  • Filters external links
  • Detects duplicates

Key Features

Configurable max pages (500) Exclusion list Comprehensive logging Duplicate detection
3

SitemapService

Core Operations

  • Create/Read/Update/Delete
  • Paginated listing
  • Search & sort
  • Existence checking

Example: Add Entry

await _sitemapService.AddAsync(new SitemapEntry
{
	Url = "https://example.com/page",
	LastModified = DateTime.UtcNow,
	ChangeFrequency = "weekly",
	Priority = 0.8
});

Example: Get All

var entries = await _sitemapService
	.GetAllAsync(pageNumber, pageSize);

How It Works

Scheduled Execution

Job runs periodically based on cron schedule, checks if auto-generation is enabled

Crawling Process

Starts from root URL, discovers internal links, skips excluded paths

Data Storage

Valid URLs stored in database SitemapEntries table with metadata

Sitemap Generation

Generates XML sitemap with all URLs, saves to sitemap.xml

Sitemap XML Structure

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
	<loc>https://example.com/</loc>
	<lastmod>2023-06-30</lastmod>
	<changefreq>weekly</changefreq>
	<priority>0.8</priority>
  </url>
  <!-- Additional URLs -->
</urlset>

Extending the Functionality

1

Customizing Crawling Behavior

Exclusion List

// Add to excludedPaths in SitemapCrawlerService
private readonly List<string> excludedPaths = new() { 
	"/admin", 
	"/login",
	"/account",
	"/dashboard",
	"/new-path-to-exclude" // Add your paths
};
2

Enhancing Sitemap Content

Dynamic Priority

// Dynamic priority calculation
await _sitemapService.AddAsync(new SitemapEntry
{
	Url = fullUrl,
	LastModified = DateTime.UtcNow,
	ChangeFrequency = "weekly",
	Priority = CalculatePriority(fullUrl) // Custom logic
});
3

Adding Manual Triggers

[HttpPost]
public async Task<IActionResult> GenerateNow()
{
	var job = new CrawlAndGenerateSitemapJob(...);
	job.Execute();
	return RedirectToAction(nameof(Index));
}
4

Multi-Language Support

// In CrawlAsync method
if (urlContainsLanguageParameter(fullUrl))
{
	// Handle language-specific URL
	// Add hreflang tags
}
5

Monitoring & Reporting

public class CrawlStats {
	public int TotalPages { get; set; }
	public int NewUrlsAdded { get; set; }
	public DateTime LastRun { get; set; }
}

Usage Instructions

Action Instructions
Enable Auto-Generation Set SeoSettings.GenerateAutoSitemap to true and configure AppSettings:BaseUrl
Manual Generation Manual trigger or wait for scheduled execution
Viewing Results Access generated sitemap at /sitemap.xml
Admin Interface View crawled URLs and manage settings through admin UI

Best Practices

Schedule Frequency

  • Dynamic sites: Daily or weekly
  • Static sites: Monthly
  • E-commerce: Consider hourly for inventory

Performance

  • Schedule during low-traffic periods
  • Add delays between requests for large sites
  • Monitor memory usage
  • Consider distributed crawling

SEO Recommendations

  • Ensure important pages are reachable
  • Verify excluded paths
  • Check sitemap completeness
  • Submit to search consoles

Troubleshooting

Issue Solution
Missing Pages
  • Verify links in HTML
  • Check exclusion list
  • Confirm 200 status code
Sitemap Not Updating
  • Check auto-generation setting
  • Verify job is running
  • Review application logs
Performance Issues
  • Reduce MaxPages
  • Add request delays
  • Split large sites

System Summary

This system provides a flexible foundation for automated sitemap generation that can be customized to meet specific website requirements while following SEO best practices.

Settings

Updated

Centralized configuration system for managing application settings

Configuration Key-Value Store Admin UI

System Overview

Note

This system provides a centralized way to manage application settings through a web interface with role-based access control.

Configuration

  • Key-value storage
  • Dynamic loading
  • Type-safe access
  • Cache integration

Management

  • Admin web interface
  • Category organization

Key Components

1

AppSettingsService

Core Operations

  • Load settings by type
  • Save settings changes
  • Cache management

Key Features

Type-safe access Audit logging Cache integration
2

SettingsController

Core Actions

  • Index - List all settings
  • Details/Edit - View & Update settings

Example: Get Settings

[HttpGet("branding")]
public IActionResult Branding()
{
	var model = _settingsService
		.LoadSettings<BrandingSettings>();
	return View(model);
}

Example: Save Settings

[HttpPost("branding")]
public IActionResult Branding(
	BrandingSettings model)
{
	_settingsService.SaveSettings(model);
	_cache.Refresh();
	return RedirectToAction(nameof(Branding));
}
3

SettingsBase

Example Implementation

public class BrandingSettings : SettingsBase
{
	public string SiteName { get; set; }
	public string LogoUrl { get; set; }
	public string PrimaryColor { get; set; }
	// Additional properties...
}

How It Works

Settings Access

Application requests settings through AppSettingsService

Load Process

Service checks cache, then database, returns typed settings

Admin Changes

Changes made through UI are validated and saved

Cache Update

Cache is invalidated and settings are reloaded on next request

Database Structure

CREATE TABLE AppSettings (
	Id INT PRIMARY KEY,
	Name NVARCHAR(255) NOT NULL,
	Value NVARCHAR(MAX) NOT NULL
);

Extending the Functionality

1

Adding New Settings

Create Settings Class

public class NotificationSettings : SettingsBase
{
	public bool EnableEmail { get; set; }
	public string AdminEmail { get; set; }
	public int MaxNotifications { get; set; }
	// Additional properties
}
2

Settings UI

@model BrandingSettings

<form method="post">
	<div class="form-group">
		<label asp-for="SiteName"></label>
		<input asp-for="SiteName" class="form-control" />
	</div>
	<!-- Additional fields -->
	<button type="submit" class="btn btn-primary">Save</button>
</form>
3

Validation

public class BrandingSettings : SettingsBase
{
	[Required]
	[StringLength(100)]
	public string SiteName { get; set; }

	[Url]
	public string LogoUrl { get; set; }

	[RegularExpression("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$")]
	public string PrimaryColor { get; set; }
}
4

Audit Logging

public async Task SaveSettingsAsync<T>(T settings) 
	where T : SettingsBase
{
	var oldSettings = await LoadSettingsAsync<T>();
	settings.UpdatedAt = DateTime.UtcNow;
	settings.UpdatedBy = _userService.GetCurrentUserId();
	// Log changes
_auditLogger.LogChange(
	typeof(T).Name,
	oldSettings,
	settings
);

// Save to database
await _repository.SaveAsync(settings);
}

Usage Instructions

Action Instructions
Access Settings Use _settingsService.LoadSettings<T>() in your application code
Modify Settings Use admin UI or call _settingsService.SaveSettings<T>()
Add New Category Create new Settings class and implement UI

Best Practices

Organization

  • Group related settings: Keep similar settings together
  • Use clear names: Self-documenting property names
  • Default values: Provide sensible defaults

Security

  • Encrypt sensitive data
  • Implement proper access controls
  • Validate all inputs

Performance

  • Cache settings appropriately
  • Batch related updates
  • Minimize database calls
  • Consider distributed caching

System Summary

This settings module provides a robust, secure foundation for managing all application configuration and role-based access control.

Stripe Payment Gateway Integration

Updated

Seamlessly integrate Stripe as a payment gateway to accept credit card payments and manage subscriptions.

Stripe Features

  • Secure credit card processing
  • Recurring subscription billing
  • PCI compliant payment handling
  • Support for multiple currencies
  • Webhook integration for real-time updates

Security Features

  • Credentials encrypted at rest
  • Token-based payment processing
  • No sensitive data stored on your servers
  • Secure API communication
  • Automatic PCI compliance
  • Two-factor authentication support

Stripe Setup Guide

Before You Begin

You'll need an active Stripe account. Create one at stripe.com if you don't have one already.

1

Configuration Steps

Navigate to Payment Gateways

Go to Settings → Payment Gateways in the admin panel

Change default Payment Gateway

Click action and select "Default"

Enter Credentials

Copy your API keys etc. from Stripe Dashboard and paste them on configuration form.

Save Configuration

Click Save to activate your Stripe integration

2

Required Stripe Credentials

Field Description Where to Find
Publishable Key Used for client-side operations Stripe Dashboard → Developers → API Keys
Secret Key Used for server-side operations Stripe Dashboard → Developers → API Keys
Webhook Secret Verifies webhook requests from Stripe Stripe Dashboard → Developers → Webhooks
Customer Portal Url A secure link provided by Stripe that allows customers to view and manage their subscriptions, billing details, and payment methods directly.
3

Webhook Configuration

To receive real-time payment events from Stripe:

Endpoint URL: https://yourdomain.com/webhook/stripe
Events to listen for:
- checkout.session.completed
- checkout.session.expired
- customer.subscription.updated
- customer.subscription.deleted
- invoice.payment_failed

Important

Webhooks are essential for subscription management. Without proper webhook setup, subscription statuses may not update correctly.

Testing Your Integration

Test Mode

Use Stripe's test mode to verify your integration without processing real payments. Toggle between live and test mode in the payment gateway configuration.

Test Cards

Use these test card numbers:

  • 4242 4242 4242 4242 - Successful payment
  • 4000 0000 0000 0002 - Failed payment
  • 5555 5555 5555 4444 - Mastercard (success)

Troubleshooting

Payments not processing

Verify your API keys are correct and in the right mode (test/live). Check your server logs for any Stripe API errors.

Webhooks not working

Ensure your webhook endpoint is accessible from the internet and the secret key matches what's in your Stripe dashboard.

Subscription status not updating

Check that all required webhook events are enabled and your webhook handler is processing them correctly.

Best Practices

  • Always use HTTPS for your payment pages and webhook endpoints
  • Regularly rotate your API keys and webhook secrets
  • Monitor your Stripe dashboard for failed payments and disputes
  • Implement proper error handling for payment failures
  • Keep your Stripe SDK updated to the latest version

Paddle Payment Gateway Integration

Updated

Integrate Paddle as a payment gateway to accept payments and manage subscriptions with built-in tax compliance and global payment methods.

Paddle Features

  • Unified payments and subscriptions
  • Global payment methods support
  • Sandbox environment for testing
  • Webhook integration for real-time updates

Security Features

  • API key authentication
  • Sandbox and production environments
  • Webhook signature verification
  • PCI compliant payment handling
  • Role-based access control

Paddle Setup Guide

Before You Begin

You'll need an active Paddle account. Create one at paddle.com if you don't have one already.

1

Configuration Steps

Navigate to Payment Gateways

Go to Settings → Payment Gateways in the admin panel

Change default Payment Gateway

Click action and select "Default"

Enter Credentials

Copy your API keys from Paddle Dashboard and paste them on configuration form

Save Configuration

Click Save to activate your Paddle integration

2

Required Paddle Credentials

Field Description Where to Find
API Key Used for server-side API calls Paddle Dashboard → Authentication → API Keys
Client Token Used for client-side operations Paddle Dashboard → Authentication → Client Tokens
Success Url Used to redirect the user once payment is successful
Error Url Used to redirect the user once payment is failed
Customer Portal Url A secure link provided by Paddle that allows customers to view and manage their subscriptions, billing details, and payment methods directly.
Sandbox Mode Toggle between test and production environments Configuration setting in admin UI
3

Webhook Configuration

To receive real-time payment events from Paddle:

Endpoint URL: https://yourdomain.com/webhook/paddle
Events to listen for:
- subscription.created
- subscription.updated
- subscription.cancelled
- transaction.completed
- transaction.paid

Important

Webhooks are essential for subscription management. Without proper webhook setup, subscription statuses may not update correctly.

Testing Your Integration

Sandbox Mode

Use Paddle's sandbox environment to test your integration without processing real payments. Toggle sandbox mode in the payment gateway configuration.

Test Cards

Use these test card numbers in sandbox mode:

  • 4242 4242 4242 4242 - Successful payment
  • 4000 0000 0000 0002 - Failed payment
  • 5555 5555 5555 4444 - Mastercard (success)

Troubleshooting

API requests failing with authentication errors

Verify your API key is correct and has the proper permissions. Ensure you're using the correct base URL (sandbox vs production).

Webhooks not being received

Check that your webhook endpoint is publicly accessible and returns a 200 status code. Verify the webhook URL is correctly configured in Paddle Dashboard.

Subscription status not updating

Ensure all required webhook events are enabled and your webhook handler is processing them correctly. Check your server logs for any processing errors.

Best Practices

  • Always use HTTPS for your payment pages and webhook endpoints
  • Regularly rotate your API keys
  • Implement proper error handling for payment failures
  • Test your integration thoroughly in sandbox mode before going live
  • Monitor your Paddle dashboard for failed payments and disputes

Google OAuth Integration

Updated

Implement secure authentication with Google OAuth 2.0, allowing users to sign in with their Google accounts.

OAuth Features

  • Secure Google account authentication
  • Configurable through admin UI

Google OAuth Setup Guide

Before You Begin

You'll need to create a project in the Google Cloud Console and configure OAuth credentials.

2

Admin UI Configuration

Field Description Where to Find
Client ID Google OAuth client identifier Google Cloud Console → APIs & Services → Credentials
Client Secret Google OAuth client secret (stored encrypted) Google Cloud Console → APIs & Services → Credentials

Configuration Steps:

  1. Navigate to Admin → Settings → Authentication
  2. Click on "Google OAuth Configuration"
  3. Enter your Google Client ID and Client Secret
  4. Save the configuration

Testing Your Integration

Test Mode

Use Google's test mode to verify your integration without affecting real users. You can configure test users in the Google Cloud Console under OAuth consent screen.

Test Accounts

For testing purposes:

  • Use any Google account you control for basic testing
  • For restricted scopes, add test users in Google Cloud Console
  • Verify different scenarios (new users vs returning users)

Troubleshooting

Authentication fails with "Invalid Credentials"

Verify your Client ID and Client Secret are correctly entered in the admin UI and match what's in your Google Cloud Console. Ensure the Client Secret hasn't expired.

Redirect URI mismatch error

Ensure the authorized redirect URIs in Google Cloud Console exactly match your domain and the standard /signin-google endpoint.

Best Practices

  • Always use HTTPS for your authentication endpoints
  • Request only the scopes your application needs
  • Regularly review authorized applications in your Google Cloud Console
  • Implement proper session management and logout functionality
  • Monitor your Google Cloud Console for any security alerts

Google reCAPTCHA Integration

Updated

Integrate Google reCAPTCHA to protect your application from spam and automated submissions on login, registration, and other sensitive forms.

reCAPTCHA Features

  • Protects against spam and automated abuse
  • Simple "I'm not a robot" checkbox
  • Invisible reCAPTCHA options available
  • Detailed analytics in Google admin console

Security Features

  • Server-side token validation
  • Configurable for specific forms
  • Conditional rendering based on settings
  • Graceful degradation if reCAPTCHA fails to load

reCAPTCHA Setup Guide

Before You Begin

You'll need to register your site at Google reCAPTCHA Admin to obtain your site key and secret key.

1

Configuration Steps

Register Your Site

Go to reCAPTCHA Admin and register your site

Configure Security Settings

Navigate to Settings → Security in the admin panel

Enter reCAPTCHA Keys

Add your site key and secret key from Google reCAPTCHA

Enable for Specific Forms

Toggle which forms should use reCAPTCHA (login, register, etc.)

2

Required reCAPTCHA Credentials

Field Description Where to Find
Site Key Used for client-side widget integration Google reCAPTCHA Admin → Your Sites
Secret Key Used for server-side token validation Google reCAPTCHA Admin → Your Sites
Enable for Forms Toggle which forms require reCAPTCHA Security Settings in admin UI

Implementation Details

Server-Side Validation

public class ValidateRecaptchaAttribute : TypeFilterAttribute
{
	public ValidateRecaptchaAttribute() : base(typeof(ValidateRecaptchaFilter))
	{
	}

	private class ValidateRecaptchaFilter : IAsyncActionFilter
	{
		// Validates reCAPTCHA token with Google's API
		// Checks if reCAPTCHA is enabled for the specific action
		// Returns appropriate error responses if validation fails
	}
}

Apply this attribute to any action that requires reCAPTCHA validation:

[HttpPost]
[ValidateRecaptcha]
public async Task<IActionResult> Login(LoginModel model)
{
	// Your login logic
}

Client-Side Implementation

Load reCAPTCHA Script

@section Scripts {
	<script src="https://www.google.com/recaptcha/api.js" async defer></script>
}

Add reCAPTCHA Widget

if (ViewBag.CaptchEnabled)
{
	<div class="g-recaptcha" data-sitekey="@_securitySettings.CaptchaPublicKey"></div>
}

Validate Before Submission

if (isCaptchaEnabled) {
	if (typeof grecaptcha === 'undefined') {
		showToast('reCAPTCHA failed to load. Please refresh the page.', 'error');
		return;
	}

	var token = grecaptcha.getResponse();
	if (!token) {
		showToast('Please verify you are a human.', 'error');
		return;
	}
}

Troubleshooting

reCAPTCHA widget not appearing

Verify the site key is correct and the reCAPTCHA script is properly loaded. Check browser console for errors. Ensure the widget is only rendered when ViewBag.CaptchEnabled is true.

Validation always fails

Check your secret key is correct. Verify your server can reach Google's reCAPTCHA API (no firewall restrictions). Ensure you're passing the correct token from the client-side.

reCAPTCHA not required when it should be

Check your security settings to ensure reCAPTCHA is enabled for the specific form type (login, register, etc.). Verify the ValidateRecaptcha attribute is applied to the action.

Best Practices

  • Always validate reCAPTCHA tokens server-side - client-side validation alone is not secure
  • Monitor your reCAPTCHA analytics in the Google admin console
  • Consider using reCAPTCHA v3 for less intrusive protection
  • Provide clear error messages when reCAPTCHA validation fails
  • Regularly review your security settings and adjust reCAPTCHA sensitivity as needed

Quick Rebranding Cheat Sheet

15 mins

The 15-Minute Rebrand Guide

Transform your EasyLaunchpad application to match your brand identity in just 15 minutes. This streamlined process allows you to update colors, typography, and visual elements without touching any component code.

Prerequisites Check (2 minutes)

Before you start, verify you have:

  • Node.js installed on your machine
  • Terminal/Command Prompt access
  • Text editor (VS Code, Sublime, etc.)
  • Access to your new brand colors (hex codes ready)
  • The project files accessible

Phase 1: Preparation (5 minutes)

1.1

Gather Your New Brand Assets

Collect these specific items:

Color Type Purpose Example Usage
Primary Brand Color Your main brand color Logo color, main buttons
Secondary Color Supporting elements Less prominent UI elements
Accent Color Highlights & CTAs Call-to-action buttons, links
Success Color Positive actions Success messages, confirmations
Error Color Errors & warnings Error messages, validation
Warning Color Cautions Warning messages, alerts
Info Color Informational messages Tips, helpful information
1.2

Understand Color Variants Needed

For EACH brand color (primary, secondary, accent), you need:

DEFAULT State

The base color used everywhere

Hover State

Slightly different when user hovers (usually 10-15% darker/lighter)

Light Variant

Lighter version for backgrounds, badges, subtle elements

Dark Variant

Darker version for text on light backgrounds or emphasis

Pro Tip

Use online tools like ColorBox.io or Adobe Color to generate these variants automatically from your base color.

1.3

Create a Color Reference Document

Make a simple list like this:

Primary Color:
- Default: #1a73e8
- Hover: #1557b0
- Light: #e8f4fd
- Dark: #0d3c6e

Secondary Color:
- Default: #5f6368
- Hover: #3c4043
- Light: #f1f3f4
- Dark: #202124

Accent Color:
- Default: #34a853
- Hover: #2d8e47
- Light: #e6f4ea
- Dark: #1e7e34

(Continue for all colors...)

Phase 2: Theme File Configuration (5 minutes)

2.1

Open the Theme Configuration File

Navigate to EasyLaunchpad.Website's root and open:

File name: tailwind.theme.js

This is your ONLY editing point for colors - don't touch any other CSS files

Critical Note

Only edit tailwind.theme.js. Do NOT edit _theme-vars.css or site.css directly as these files are auto-generated.

2.2

Update Brand Colors Section

Locate the brandColors object and replace:

What to change:
  • Find the primary color group
  • Replace all four values (DEFAULT, hover, light, dark) with your new primary color variants
  • Repeat for secondary color group
  • Repeat for accent color group
const brandColors = {
  primary: {
	DEFAULT: '#1a73e8',  // Your new primary color
	hover: '#1557b0',     // Darker/lighter on hover
	light: '#e8f4fd',     // Light background variant
	dark: '#0d3c6e'       // Dark text variant
  },
  secondary: {
	DEFAULT: '#5f6368',
	hover: '#3c4043',
	light: '#f1f3f4',
	dark: '#202124'
  },
  accent: {
	DEFAULT: '#34a853',
	hover: '#2d8e47',
	light: '#e6f4ea',
	dark: '#1e7e34'
  }
};
Critical notes:
  • Use hex color format (starting with #)
  • Keep the structure exactly the same
  • Don't remove any properties
  • Ensure quotes are properly closed
2.3

Update Semantic Colors Section (Optional)

Locate the semanticColors object and optionally update:

Success Colors

Keep green unless your brand specifically requires different

Error Colors

Keep red unless your brand specifically requires different

Warning Colors

Keep orange/yellow unless your brand specifically requires different

Info Colors

Keep blue unless your brand specifically requires different

When to change semantic colors:
  • Your brand guideline specifically defines these
  • Industry standards require different colors (e.g., finance, healthcare)
  • Accessibility requirements demand specific contrast ratios
2.4

Customize Dark/Light Mode Appearance

Locate the modes object with dark and light sub-objects:

What to update in DARK mode:
textPrimary

Main text color in dark mode (usually off-white)

textSecondary

Secondary text color (slightly dimmer than primary)

cardBackground

Background for cards/panels (slightly lighter than page background)

border

Border color for elements (subtle, not too bright)

What to update in LIGHT mode:
pageBackground

The overall light background color (usually white or very light gray)

textPrimary

Main text color in light mode (usually very dark gray/black)

textSecondary

Secondary text color (lighter gray)

cardBackground

Background for cards/panels (usually white or slight off-white)

border

Border color for elements (light gray)

Important consideration:
  • Ensure sufficient contrast ratios (4.5:1 for normal text, 3:1 for large text)
  • Test readability in both modes
  • Keep dark mode truly dark (don't make it too bright)
  • Keep light mode comfortable (pure white can be harsh)

Phase 3: Typography & Fonts (3 minutes)

3.1

Update Font Families (If Changing)

Still in tailwind.theme.js, locate the typography section:

What to change:
fontFamily.sans

Your primary body font (used for most text)

fontFamily.heading

Font for headings (can be same as sans or different)

fontFamily.mono

Font for code snippets (usually monospace font)

Font format requirements:
  • List fonts in priority order
  • Include fallback system fonts
  • Use exact font names as they appear in Google Fonts or your CSS
fontFamily: {
  sans: ['Inter', 'system-ui', 'sans-serif'],
  heading: ['Poppins', 'Inter', 'sans-serif'],
  mono: ['Fira Code', 'monospace']
}
// Main brand font → Secondary option → System fallback
3.2

Adjust Font Sizes (If Needed)

Review the fontSize object:

When to change:
  • Your brand requires larger/smaller base text
  • Accessibility requirements
  • Target audience preferences (older users need larger text)
What to consider:
  • base is your default body text size (typically 1rem = 16px)
  • All other sizes scale relative to base
  • Maintain proportional relationships between sizes
Note

Most applications work well with default font sizes. Only adjust if you have specific brand or accessibility requirements.

3.3

Update Spacing/Padding (Optional)

If your brand feels more "spacious" or "compact":

What to adjust:
  • Component padding values
  • Section spacing
  • Container widths
Note

This is advanced customization - skip if unsure.

Phase 4: Compilation & Testing (5 minutes)

4.1

Generate CSS Variables

Open your terminal in the EasyLaunchpad.Website's root directory and run:

npm run generate:theme

What this does:

Converts your theme.js file into CSS custom properties

Output:

Creates/updates _theme-vars.css file

Wait for:

"Theme variables generated successfully" message

If you see errors:
  • Check for syntax errors in tailwind.theme.js
  • Ensure all color values have # symbols
  • Verify all quotes are properly closed
  • Look for missing commas between properties
4.2

Build Final CSS

In the same terminal, run:

npm run build:css

What this does:

Compiles everything into the final site.css file

Output:

Updates site.css in wwwroot/css/

Wait for:

Compilation completion message (usually shows file size)

Alternative For Compiling Tailwind

If you dont run any command, then Clean the Solution and Rebuild Solution, it is already added to .csproj as the prebuild command

4.3

Clear Browser Cache

Critical step before testing:

Windows/Linux

Press Ctrl + Shift + R

Mac

Press Cmd + Shift + R

Why this is important:

Browsers cache CSS files aggressively

Alternative: Open in Incognito/Private browsing mode

4.4

Visual Testing Checklist

Open your application and systematically check:

Color verification:
Typography testing:
Responsive testing:
Component-specific testing:
4.5

Cross-Browser Testing

If this is production-critical, test in:

Chrome/Edge

Chromium-based

Firefox

Mozilla

Safari

If targeting Mac/iOS

The Absolute Minimum Rebrand

If you only have 5 minutes:

Open tailwind.theme.js

Change ONLY these three colors in brandColors:

  • primary.DEFAULT
  • secondary.DEFAULT
  • accent.DEFAULT

Run: npm run generate:theme

Run: npm run build:css

Hard refresh browser (Ctrl+Shift+R)

Result

Your app now uses new colors everywhere components exist.

Common Pitfalls & How to Avoid Them

Mistake 1: Editing Wrong Files

Don't edit: _theme-vars.css or site.css directly

Only edit: tailwind.theme.js

Why: Those files are auto-generated and will be overwritten

Mistake 2: Forgetting to Regenerate

Editing theme file and refreshing browser immediately

Always run both npm commands after editing

Why: Changes don't apply until CSS is rebuilt

Mistake 3: Poor Contrast Ratios

Using light text on light backgrounds

Use contrast checker tools before committing

Why: Accessibility and readability suffer

Mistake 4: Not Testing Dark Mode

Only checking light mode appearance

Always toggle and test both modes thoroughly

Why: Dark mode might have unreadable combinations

Mistake 5: Skipping Browser Cache Clear

Refreshing normally and thinking "it didn't work"

Always hard refresh (Ctrl+Shift+R)

Why: Browser serves cached CSS file

Mistake 6: Inconsistent Color Variants

Making hover state too different from default

Keep hover states subtle (10-15% darker/lighter)

Why: Jarring transitions confuse users

Mistake 7: Breaking JSON/JavaScript Syntax

Missing commas, quotes, or brackets

Use code editor with syntax highlighting

Why: Build command will fail with cryptic errors

Quick Reference Commands

Daily Development:

npm run watch:css

Automatically rebuilds CSS when you save changes - use this during active development.

After Theme Changes:

npm run build:all

Regenerates variables AND rebuilds CSS in one command.

Just CSS Compilation:

npm run build:css

Only rebuilds CSS without regenerating variables.

Understanding What Happens Behind the Scenes

The Theme Pipeline:

1

You edit tailwind.theme.js with new colors

2

generate-theme-vars.js reads your theme file

3

Converts your colors to CSS custom properties (variables)

4

Writes those variables to _theme-vars.css

5

Tailwind compiler reads both _theme-vars.css and tailwind.css

6

Combines everything into final site.css

7

Your HTML uses site.css to display with new colors

Why This Architecture Exists:

Single source of truth

One file controls all colors

Automatic propagation

Change once, updates everywhere

Theme switching

Dark/light modes work automatically

Maintainability

Easy to update without touching components

Consistency

Impossible to have mismatched colors

Post-Rebrand Checklist

After completing your rebrand, verify:

Emergency Rollback

If something goes terribly wrong:

Option 1: Restore from Backup

Find your backup of tailwind.theme.js (you made one, right?)

Replace the current file with the backup

Run: npm run build:css

Hard refresh browser

If no backup exists:
  • Check version control (Git) history
  • Restore from last commit
  • Or manually revert color values to previous brand

Pro Tips for Smooth Rebranding

1️

Make a backup of tailwind.theme.js before starting

2️

Change one section at a time (brand colors first, then modes, then typography)

3️

Test after each major change rather than all at once

4️

Use color palette generators to create harmonious variants

5️

Keep a style guide document with all your color decisions

6️

Take screenshots of the old design for comparison

7️

Get stakeholder approval on colors BEFORE implementing

8️

Consider accessibility from the start, not as an afterthought

9️

Test on actual devices not just browser resize tools

10

Document your decisions for future reference

Time Investment

Basic Rebrand

15-30 min

Colors only

Comprehensive

1-2 hours

Colors + Typography + Testing

Emergency

5 min

Absolute minimum

File Reference

Files You Should Edit:

tailwind.theme.js - Theme colors
tailwind.css - Custom classes
tailwind.config.js - Advanced

Files You Should NOT Edit:

_theme-vars.css - Auto-generated
site.css - Compiled output

Congratulations!

You now have all the knowledge needed to rebrand your EasyLaunchpad application quickly and efficiently.

Quick Setup 15 Minutes Professional Results

Last Updated: 2025 | Version: 1.0

Theming System Comprehensive Documentation

Updated

Comprehensive CSS/Tailwind-based theming architecture for rapid UI development.

System Features

  • Dynamic dark/light mode switching
  • Pre-built, reusable UI components
  • Centralized color management
  • CSS variable-based theming
  • Automatic theme compilation

Development Benefits

  • Rapid UI development workflow
  • Consistent styling across application
  • Easy customization and maintenance
  • Production-ready components
  • Responsive design system

Goal

Enable rapid UI development with consistent, customizable styling across your entire application.

File Structure

wwwroot/
	css/
		tailwind.css - Component classes & utilities
		_theme-vars.css - Auto-generated CSS variables
		site.css - Compiled final stylesheet
	scripts/
		generate-theme-vars.js - Theme generator script
tailwind.public.config.js - Main Tailwind config
tailwind.theme.js - Theme definitions (EDIT THIS)

System Architecture

1

Theme Definition

Define all colors, modes (dark/light), typography, and spacing in tailwind.theme.js - your single source of truth.

// tailwind.theme.js
module.exports = {
  colors: {
	primary: '#3b82f6',
	secondary: '#8b5cf6',
	// ... more colors
  }
}
2

Variable Generation

Script reads tailwind.theme.js and automatically generates _theme-vars.css with CSS custom properties.

npm run generate:theme
3

Component Classes

Imports _theme-vars.css and defines reusable component classes like .btn-primary, .card-base, etc.

4

Tailwind Configuration

Configures Tailwind to use the theme colors and extends utility classes in tailwind.public.config.js.

5

Final Compilation

Tailwind compiles everything into the final site.css file used in your HTML.

npm run build:css

Flow Summary

tailwind.theme.js generate-theme-vars.js _theme-vars.css tailwind.css tailwind.public.config.js site.css

Using Pre-Built Components

All component classes are defined in tailwind.css and ready to use in your HTML.

Button Components

Available Classes:

  • .btn-primary - Primary action button
  • .btn-secondary - Secondary button
  • .btn-outline - Outlined button
  • .btn-ghost - Transparent button
  • .btn-sm - Small button
  • .btn-gradient-primary - Gradient button

HTML Examples:

<!-- Primary Button -->
<button class="btn-primary">Click Me</button>

<!-- Secondary Button -->
<button class="btn-secondary">Cancel</button>

<!-- Small Outline Button -->
<button class="btn-outline btn-sm">Learn More</button>

<!-- Gradient Button -->
<button class="btn-gradient-primary">Sign Up</button>

Card Components

Available Classes:

  • .card-base - Basic card with padding and border
  • .card-hover - Card with hover animation
  • .card-elevated - Card with shadow elevation
  • .auth-card - Authentication-specific card

HTML Example:

<div class="card-hover">
	<h3 class="text-primary text-xl font-bold mb-2">Card Title</h3>
	<p class="text-secondary">Card content goes here</p>
</div>

Form Components

Available Classes:

  • .input-base - Text input field
  • .form-label - Form label
  • .form-group - Form field container
  • .form-error - Error message
  • .checkbox-base - Checkbox input

HTML Example:

<div class="form-group">
	<label class="form-label">Email Address</label>
	<input type="email" class="input-base" placeholder="you@example.com">
	<span class="form-error">Please enter a valid email</span>
</div>

Badge Components

Available Classes:

  • .badge-primary - Primary badge
  • .badge-success - Success badge (green)
  • .badge-error - Error badge (red)
  • .badge-warning - Warning badge (orange)
  • .badge-info - Info badge (blue)

HTML Example:

<span class="badge-success">Active</span>
<span class="badge-error">Cancelled</span>
<span class="badge-warning">Pending</span>

Complete Component Reference

Component Type Class Name Description
Button .btn-primary Primary action button
Button .btn-secondary Secondary button
Card .card-base Basic card with border
Card .card-hover Card with hover effect
Input .input-base Styled text input
Badge .badge-success Green success badge
Alert .alert-error Red error alert box
Navigation .nav-link Navigation link style

Changing Theme Colors & Styles

Important

Always edit tailwind.theme.js - never manually edit _theme-vars.css or site.css as they are auto-generated.

Method 1: Changing Brand Colors

Step 1: Open tailwind.theme.js

// Located at project root
tailwind.theme.js

Step 2: Edit Brand Colors Section

brandColors: {
	primary: {
		DEFAULT: '#your-primary-color',    // Change to your brand color
		hover: '#your-hover-color',        // Hover state
		light: '#your-light-variant',      // Light variant
		dark: '#your-dark-variant',        // Dark variant
	},

	secondary: {
		DEFAULT: '#your-secondary-color',  // Secondary color
		hover: '#hover-state',
		light: '#light-variant',
		dark: '#dark-variant',
	},

	accent: {
		DEFAULT: '#your-accent-color',     // Accent color
		hover: '#hover-state',
		light: '#light-variant',
		dark: '#dark-variant',
	},
}

Step 3: Regenerate Theme Variables

npm run generate:theme

Step 4: Rebuild CSS

npm run build:css

Done!

Your new colors are now applied throughout the entire application.

Method 2: Customizing Dark/Light Modes

Edit Mode-Specific Colors

modes: {
	dark: {
		pageBackground: '#your-dark-bg',    // Dark mode page background
		textPrimary: '#your-dark-text',     // Dark mode text
		cardBackground: '#your-card-bg',    // Dark mode cards
		// ... more properties
	},

	light: {
		pageBackground: '#your-light-bg',   // Light mode page background
		textPrimary: '#your-light-text',    // Light mode text
		cardBackground: '#your-card-bg',    // Light mode cards
		// ... more properties
	},
}

Method 3: Changing Typography

typography: {
	fontFamily: {
		sans: ['Your-Font', 'system-ui', 'sans-serif'],    // Body font
		heading: ['Heading-Font', 'sans-serif'],           // Heading font
		mono: ['Mono-Font', 'monospace'],                  // Code font
	},
	fontSize: {
		xs: '0.75rem',
		sm: '0.875rem',
		base: '1rem',      // Default size
		lg: '1.125rem',
		xl: '1.25rem',
		// ...
	},
}

Example Color Palette Structure

Primary
#2563eb
Secondary
#64748b
Accent
#8b5cf6
Success
#10B981
Error
#EF4444
Warning
#F59E0B

Note

Replace these example colors with your own brand colors in tailwind.theme.js

Adding Custom Components

To add your own reusable component classes:

1

Open tailwind.css

// File: wwwroot/css/tailwind.css
2

Add Your Component in @layer components

@layer components {
	/* Your custom component */
	.my-custom-button {
		background-color: rgb(var(--color-accent));
		color: #000;
		padding: 12px 24px;
		border-radius: 8px;
		font-weight: 600;
		transition: all 0.3s;
	}

	.my-custom-button:hover {
		transform: scale(1.05);
		box-shadow: 0 4px 12px rgba(var(--color-accent), 0.3);
	}

	.my-custom-card {
		background-color: rgb(var(--theme-card-background));
		border: 2px solid rgb(var(--color-primary));
		border-radius: 12px;
		padding: 20px;
		box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
	}
}
3

Rebuild CSS

npm run build:css
4

Use in HTML

<button class="my-custom-button">Custom Button</button>

<div class="my-custom-card">
	<h3>Custom Card</h3>
	<p>With custom styling</p>
</div>

Pro Tip

Always use CSS variables like rgb(var(--color-primary)) in your custom components so they automatically adapt to theme changes.

Best Practices for Custom Components

  • Use theme CSS variables instead of hardcoded colors
  • Follow the naming convention: .component-name
  • Add hover states for interactive elements
  • Include transition effects for smooth animations
  • Test in both dark and light modes

Advanced Usage

Creating a Complete Page Layout

Example: Landing Page with Components

<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>My App</title>
	<link rel="stylesheet" href="/css/site.css">
</head>
<body class="bg-page">
	<!-- Hero Section -->
	<section class="section-page">
		<div class="container-narrow text-center">
			<h1 class="title-hero text-primary">Welcome</h1>
			<p class="subtitle-base text-secondary mb-8">
				Build beautiful applications
			</p>
			<button class="btn-primary-lg">Get Started</button>
		</div>
	</section>

	<!-- Features Section -->
	<section class="section-page bg-section-alt">
		<div class="container-base">
			<h2 class="title-section text-center mb-12">Features</h2>
			<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
				<div class="card-hover">
					<h3 class="text-xl font-bold text-primary mb-3">Fast</h3>
					<p class="text-secondary">Lightning-fast development</p>
				</div>
				<div class="card-hover">
					<h3 class="text-xl font-bold text-primary mb-3">Flexible</h3>
					<p class="text-secondary">Customize everything</p>
				</div>
				<div class="card-hover">
					<h3 class="text-xl font-bold text-primary mb-3">Modern</h3>
					<p class="text-secondary">Latest design trends</p>
				</div>
			</div>
		</div>
	</section>
</body>
</html>

Authentication Page Example

Login Page

<section class="section-auth">
	<div class="container-narrow">
		<div class="auth-card">
			<div class="auth-header">
				<h2 class="text-3xl font-bold text-primary mb-2">Welcome Back</h2>
				<p class="text-secondary">Sign in to your account</p>
			</div>

			<div class="auth-body">
				<form class="auth-form">
					<div class="form-group">
						<label class="form-label">Email</label>
						<input type="email" class="auth-input" 
							   placeholder="you@example.com">
					</div>

					<div class="form-group">
						<label class="form-label">Password</label>
						<input type="password" class="auth-input" 
							   placeholder="••••••••">
					</div>

					<div class="auth-checkbox-wrapper">
						<input type="checkbox" class="auth-checkbox" id="remember">
						<label for="remember" class="auth-checkbox-label">
							Remember me
						</label>
					</div>

					<button type="submit" class="auth-button">Sign In</button>
				</form>
			</div>

			<div class="auth-footer">
				<p class="auth-footer-text">
					Don't have an account? 
					<a href="/register" class="auth-footer-link">Sign up</a>
				</p>
			</div>
		</div>
	</div>
</section>

Theme Switching Implementation

JavaScript for Dark/Light Toggle

<!-- Theme Toggle Button -->
<button id="theme-toggle" class="btn-ghost">
	<span id="theme-icon">🌙</span>
</button>

<script>
	const themeToggle = document.getElementById('theme-toggle');
	const themeIcon = document.getElementById('theme-icon');
	const html = document.documentElement;

	// Load saved theme or default to dark
	const savedTheme = localStorage.getItem('theme') || 'dark';
	html.setAttribute('data-theme', savedTheme);
	updateIcon(savedTheme);

	// Toggle theme on click
	themeToggle.addEventListener('click', () => {
		const currentTheme = html.getAttribute('data-theme');
		const newTheme = currentTheme === 'dark' ? 'light' : 'dark';

		html.setAttribute('data-theme', newTheme);
		localStorage.setItem('theme', newTheme);
		updateIcon(newTheme);
	});

	function updateIcon(theme) {
		themeIcon.textContent = theme === 'dark' ? '☀️' : '🌙';
	}
</script>

Responsive Design

Built-in Breakpoints

Tailwind Breakpoints

  • sm: - 640px and up
  • md: - 768px and up
  • lg: - 1024px and up
  • xl: - 1280px and up

Responsive Examples

<!-- Responsive Grid -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
	<!-- Mobile: 1 column, Tablet: 2 columns, Desktop: 4 columns -->
	<div class="card-base">Card 1</div>
	<div class="card-base">Card 2</div>
	<div class="card-base">Card 3</div>
	<div class="card-base">Card 4</div>
</div>

<!-- Responsive Text -->
<h1 class="text-2xl sm:text-3xl lg:text-5xl font-bold">
	Responsive Heading
</h1>

<!-- Hide/Show on Different Screens -->
<div class="block lg:hidden">Mobile Menu</div>
<div class="hidden lg:block">Desktop Menu</div>

Troubleshooting & FAQ

Q: My theme changes aren't showing up

Solution:

  1. Run npm run generate:theme
  2. Run npm run build:css
  3. Clear browser cache (Ctrl+Shift+R)
  4. Check that site.css is loaded in your HTML
Q: Component class isn't working

Checklist:

  1. Verify the class exists in tailwind.css
  2. Check for typos in class name
  3. Ensure CSS is compiled: npm run build:css
  4. Inspect element in browser DevTools to see applied styles
Q: How do I add a new color?
// In tailwind.theme.js, add to brandColors or semanticColors:
brandColors: {
	// ...existing colors
	tertiary: {
		DEFAULT: '#your-color',
		hover: '#your-hover-color',
		light: '#your-light-color',
		dark: '#your-dark-color',
	}
}

// Then regenerate: npm run generate:theme
Q: Dark mode not switching?

Check these:

  1. HTML should have data-theme="dark" or data-theme="light" attribute
  2. JavaScript theme switcher is working
  3. Both dark and light modes are defined in tailwind.theme.js

Common Build Commands

Command Purpose
npm run generate:theme Generate CSS variables from theme config
npm run build:css Compile Tailwind to site.css
npm run watch:css Watch for changes and auto-compile
npm run build:all Generate theme + build CSS in one command

Files you SHOULD edit

  • tailwind.theme.js - Theme colors and configuration
  • tailwind.css - Add custom component classes
  • tailwind.public.config.js - Tailwind settings (advanced)

Files you should NOT edit manually

  • _theme-vars.css - Auto-generated from theme.js
  • site.css - Compiled output file

CSS Variable Reference

All these variables are available for use in your custom CSS:

Variable Usage Example
--color-primary Primary brand color (RGB) rgb(var(--color-primary))
--color-secondary Secondary color (RGB) rgb(var(--color-secondary))
--theme-page-background Page background (RGB) rgb(var(--theme-page-background))
--theme-text-primary Primary text color (RGB) rgb(var(--theme-text-primary))
--theme-card-background Card background (RGB) rgb(var(--theme-card-background))
--color-success Success state color (RGB) rgb(var(--color-success))
--color-error Error state color (RGB) rgb(var(--color-error))

Note

Variables are in RGB format without the rgb() wrapper, so always use rgb(var(--variable-name)) or rgba(var(--variable-name), opacity)

Getting Help

Common Issues & Solutions

Issue: "Cannot find module 'tailwindcss'"

Solution:

npm install tailwindcss --save-dev

Issue: "Scripts not found"

Solution: Ensure package.json has correct scripts:

{
  "scripts": {
	"generate:theme": "node scripts/generate-theme-vars.js",
	"build:css": "tailwindcss -c tailwind.public.config.js -i wwwroot/css/tailwind.css -o wwwroot/css/site.css"
  }
}

Issue: "Colors not showing"

Checklist:

  1. Run npm run generate:theme
  2. Run npm run build:css
  3. Check HTML has <link rel="stylesheet" href="/css/site.css">
  4. Clear browser cache (Ctrl+Shift+R)
  5. Check DevTools for CSS loading errors

Debugging Checklist

When something doesn't work:

  1. ✅ Check browser console for errors
  2. ✅ Inspect element in DevTools
  3. ✅ Verify class name spelling
  4. ✅ Check if site.css is loaded
  5. ✅ Verify CSS was compiled recently
  6. ✅ Test in incognito mode (clear cache)
  7. ✅ Check if theme attribute is set on HTML tag

Need More Help?

Contact channels:

  • Email: dev-support@yourcompany.com
  • Slack: #frontend-help channel
  • Internal Wiki: wiki.yourcompany.com/theme-system
  • Create a ticket in Jira

Quick Summary

For New Developers - Start Here!

1️⃣ Understanding the System

  • tailwind.theme.js = Source of truth for colors and theme
  • generate-theme-vars.js = Converts theme to CSS variables
  • tailwind.css = Pre-built component classes
  • site.css = Final compiled CSS (use this in HTML)

2️⃣ Using Components in HTML

<!-- Just add the class names -->
<button class="btn-primary">Click Me</button>
<div class="card-hover">...</div>
<input class="input-base" />
<span class="badge-success">Active</span>

3️⃣ Changing Theme Colors

  1. Open tailwind.theme.js
  2. Edit the brandColors section
  3. Run npm run generate:theme
  4. Run npm run build:css
  5. Done! Colors update everywhere automatically

4️⃣ Adding Custom Components

  1. Open wwwroot/css/tailwind.css
  2. Add your component in @layer components { }
  3. Use CSS variables: rgb(var(--color-primary))
  4. Run npm run build:css
  5. Use your new class in HTML

5️⃣ Daily Development

# Start watch mode
npm run watch:css

# Edit HTML with component classes
# CSS auto-compiles on save

# When changing theme colors:
npm run generate:theme
npm run build:css

Key Takeaways

  • NEVER edit _theme-vars.css or site.css manually
  • ALWAYS edit colors in tailwind.theme.js
  • USE pre-built component classes for faster development
  • ADD custom components to tailwind.css
  • REGENERATE theme vars and rebuild CSS after theme changes