A clean, modular template for building C# applications that can run as either console (CLI) or WPF GUI applications based on a simple configuration file. Perfect for applications that need to support power users, headless automation, and/or interactive user interfaces.
- Dual Interface Support: Switch between console and WPF modes with a single config file
- Clean Architecture: Separate projects for core logic, CLI, GUI, and launcher
- No Console Window in GUI Mode: Proper window management for desktop apps
- Shared Business Logic: Write your core functionality once, use it everywhere
- Modern .NET: Built with .NET 9
- Easy Configuration: Simple boolean toggle in
app.cfgfile
DualMode.sln
├── DualMode.Core/ # Business logic, models, and services
├── DualMode.Cli/ # Console interface implementation
├── DualMode.Gui/ # WPF interface implementation
└── DualMode.Launcher/ # Entry point and mode selection
- .NET 9.0 SDK or later (will probably work with older versions)
- Visual Studio 2022 or VS Code with C# extension
- Windows OS (for WPF support)
git clone https://github.com/Corbanistan/DualMode-Architecture-Template.git
cd DualMode-Architecture-Template
dotnet buildCreate or edit app.cfg in the output directory:
# Console Mode
UseGui=false
# GUI Mode
UseGui=true# The application will automatically detect the mode from app.cfg
dotnet run --project DualMode.LauncherWhen UseGui=false, the application runs as a console application with:
- Command-line argument support
- Console output and logging
When UseGui=true, the application runs as a WPF desktop application with:
- No console window interference
- Full desktop application experience
The app.cfg file supports the following options:
# Set to true for GUI mode, false for console mode
UseGui=true
## 🏗️ Architecture Overview
### DualMode.Core
Contains all business logic, models, and services that are shared between CLI and GUI modes:
```csharp
public interface IApplication
{
Task RunAsync(string[] args);
}
// Your business logic goes here
public class MyService
{
public void DoSomething()
{
// Shared functionality
}
}Implements the console interface:
public class ConsoleApplication : IApplication
{
public async Task RunAsync(string[] args)
{
Console.WriteLine("Running in console mode...");
// CLI-specific implementation
}
}Implements the WPF interface:
public class WpfApplication : IApplication
{
public void Run(string[] args)
{
var app = new Application();
var mainWindow = new MainWindow();
app.Run(mainWindow);
}
}Handles mode detection and application startup:
- Reads configuration from
app.cfg - Creates appropriate application instance
- Manages console allocation for CLI mode
- Ensures proper threading for WPF mode
- Update
AppConfigclass inProgram.cs:
public class AppConfig
{
public bool UseGui { get; set; } = false;
public string DatabaseConnection { get; set; } = "";
public LogLevel LogLevel { get; set; } = LogLevel.Info;
}-
Update the
LoadConfiguration()method to parse new options -
Pass configuration to your application instances
Add your business logic to DualMode.Core:
// Models
public class DataModel
{
public string Name { get; set; }
public DateTime Created { get; set; }
}
// Services
public interface IDataService
{
Task<List<DataModel>> GetDataAsync();
}
public class DataService : IDataService
{
public async Task<List<DataModel>> GetDataAsync()
{
// Your implementation
}
}Modify DualMode.Cli/ConsoleApplication.cs:
public class ConsoleApplication : IApplication
{
private readonly IDataService _dataService;
public ConsoleApplication(IDataService dataService)
{
_dataService = dataService;
}
public async Task RunAsync(string[] args)
{
// Parse command line arguments
// Execute CLI commands
// Output results to console
}
}Modify DualMode.Gui/WpfApplication.cs and create your WPF windows:
public partial class MainWindow : Window
{
private readonly IDataService _dataService;
public MainWindow(IDataService dataService)
{
InitializeComponent();
_dataService = dataService;
}
private async void LoadData_Click(object sender, RoutedEventArgs e)
{
var data = await _dataService.GetDataAsync();
// Update UI with data
}
}The template uses minimal dependencies:
- .NET 9.0: Core framework
- Windows Presentation Foundation (WPF): For GUI mode
- System.Runtime.InteropServices: For console management
The modular architecture makes testing straightforward:
[Test]
public async Task ConsoleApp_ShouldProcessArgs()
{
// Arrange
var mockService = new Mock<IDataService>();
var consoleApp = new ConsoleApplication(mockService.Object);
// Act
await consoleApp.RunAsync(new[] { "--test" });
// Assert
mockService.Verify(x => x.GetDataAsync(), Times.Once);
}- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- WPF mode requires Windows OS
- Console allocation may briefly flash a window on some systems
- Configuration file must be in the executable directory
This project is licensed under the MIT License - see the LICENSE file for details.
- Inspired by the need for flexible deployment options
- Built with modern .NET best practices
- Community feedback and contributions welcome