The Console App Framework Nobody Asked For (But Every .NET Developer Needs)
Let's talk about console applications. And let's bring mvc into it.
Say Whaaaaat ??!
Not the glamorous kind. Not the slick CLI tools with beautifully formatted help text and a Homebrew formula. I mean the real ones — the internal tools, the data migration scripts, the admin utilities — the ones that started as a two-hundred line Program.cs and now, three years later, nobody dares touch because the while(true) loop at the centre is load-bearing.
You know the one.
The Architecture Nobody Bothered With
Here's the thing that bothers me about console app development in .NET: we have everything for web. ASP.NET Core MVC gives you controllers, routing, model binding, middleware, dependency injection, a view engine, form posting — a complete architectural pattern that scales from a weekend project to a production system serving millions of requests.
Console developers get Console.WriteLine().
There's an unspoken assumption baked into the ecosystem: console apps are throwaway scripts. They don't need architecture. They're not real applications. You're not supposed to care.
I care. ConsoleMVC is what happened when I cared too much.
What It Actually Is
ConsoleMVC brings the Controller-ViewModel pattern to .NET console applications. If you've built ASP.NET Core MVC apps before, you already know the mental model — because it's the same one.
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new HomeViewModel
{
Title = "Welcome",
Message = "Select an option below."
};
return View(model);
}
}
Controllers inherit from Controller. Actions return ActionResult. Views are discovered by convention. Models are plain DTOs. RedirectToAction() works exactly as you'd expect.
The framework scans the assembly at startup, auto-discovers every controller and view via reflection, and begins the main loop at the default route. No manual registration. No XML configuration files. No "Settigns" typos routing silently to nowhere.
The .cvw Files
Here's where it gets interesting. Views use a .cvw (Console View) file format — plain C# with an @model directive at the top, compiled into proper ConsoleView<TModel> classes at build time by a Roslyn source generator.
@model MyApp.Models.DashboardViewModel
Console.WriteLine($"=== {Model.Title} ===");
Console.WriteLine(Model.Message);
Console.Write("Select: ");
var input = Console.ReadLine()?.Trim();
return input switch
{
"1" => NavigationResult.To("Home", "About"),
"q" => NavigationResult.Quit(),
_ => NavigationResult.To("Home", "Index")
};
No class boilerplate. No interface implementation. You write logic; the generator writes the plumbing. Every view must return a NavigationResult — telling the framework where to go next, or when to quit. The type system enforces it. There's no way to forget.
Form Posting (Yes, In a Console App)
The feature that raised the most eyebrows — and turned out to be the most architecturally correct decision in the entire project.
Views can collect user input, package it into a Dictionary<string, string>, and post it to a controller action, exactly like an HTML form POST. The framework binds the values to the target action's parameters automatically, handling type conversion along the way.
// In the view
var formData = new Dictionary<string, string>
{
["Name"] = name,
["Color"] = color
};
return NavigationResult.ToAction("Result", formData);
// In the controller
public ActionResult Result(GreetFormModel model)
{
if (string.IsNullOrWhiteSpace(model.Name))
return RedirectToAction("Index");
return View(new GreetResultModel { Greeting = $"Hello, {model.Name}!" });
}
Keeping input collection in the view and business logic in the controller is the entire point of MVC. Mixing them is how you end up back in the six-hundred-line Program.cs. The form posting pattern enforces the separation even when it's inconvenient — especially then.
The model binder supports complex types, simple parameters, nullable variants, enums, GUIDs, and case-insensitive key matching. Missing keys and failed conversions default gracefully.
First-Class IDE Support
Writing in an unfamiliar file format without IDE support is a particular kind of misery. So there's a JetBrains Rider plugin that treats .cvw files as first-class citizens.
What you get:
- Syntax highlighting —
@model,@usingdirectives, and full C# body highlighting - Code completion — directives,
NavigationResultmethods,Console.*,Model,ViewData - Error highlighting — missing
@model, empty arguments, missing return statement, model type mismatches - Navigation — gutter icons linking views to their controllers,
NavigationResult.To()target navigation, Go to Related - Refactoring — rename or move a model class and it propagates into
.cvwfiles via the ReSharper backend - Live templates —
cvw,navto,navaction,navquitsnippets - Quick-fixes — add missing
@model, add missing return statement
The plugin plugs into the ReSharper backend for full semantic C# analysis. It's not a syntax highlighter pretending to understand your code — it actually understands your code.
Getting Started
Install the project template:
dotnet new install ConsoleMVC.Template
Scaffold a new app:
dotnet new consolemvc -n MyApp
cd MyApp
dotnet run
You get a ready-to-run application with controllers, models, views, and a working form posting example out of the box. Add the Rider plugin, associate *.cvw with C# in your editor of choice, and you have a complete development experience.
The framework package is ConsoleMVC.Framework on NuGet. The template is ConsoleMVC.Template.
What's Next
The current framework is a working architectural foundation. What's coming is the part that excites me more: C-View Markup — a markup language purpose-built for terminal UIs.
Imagine writing a console screen like this:
<box title="Dashboard" width="60" border="double">
<text align="center" color="cyan">Welcome, @Model.UserName!</text>
<table>
<row><cell>Notifications</cell><cell>@Model.Count</cell></row>
</table>
</box>
<menu prompt="Select an option:">
<option key="1" nav="Home/Index">Home</option>
<option key="q" quit="true">Exit</option>
</menu>
Not HTML. Not a wrapper around an existing library. A domain-specific markup for terminals, compiled at build time by the source generator into optimised rendering code. Layout primitives, colour, alignment, declarative input collection — everything you currently hand-roll with Console.WriteLine(), done for you.
Console Apps Deserve Better
ConsoleMVC is an open-source project built on a simple conviction: console applications are real applications. They deserve the same architectural patterns, tooling, and development experience as web applications.
The framework is young. The rough edges are real. But the foundation is sound, and the direction is clear.
If you've built frameworks, rendering engines, or markup parsers before — or if you just think the problem is worth solving — the repository is open, issues are read, and PRs are welcome.
Read more about the project and see it in context at podeszwa.dev/portfolio/consolemvc.