← All work CODE · 2026

ConsoleMVC — a real mvc adaptation

Model View Controller in Console?

An MVC framework for console applications, inspired by ASP.NET Core MVC. Controller-ViewModel pattern with convention-based routing and Razor-like .cvw view templates.

ConsoleMVC — a real mvc adaptation

Console Applications Deserve Better

Every developer, at some point, inherits a console application. Maybe you wrote it yourself three years ago. Maybe it was handed to you by someone who has since fled the company and possibly the country. Either way, you open the file and what greets you is a single, heroic Program.cs — six hundred lines long, no structure, no pattern, just a sprawling while(true) loop held together by switch statements and quiet desperation.

I've been that developer. More than once.

The standard response is to shrug, add another case, and move on. After all, it's just a console app. It's not a real application. It doesn't need architecture.

That, right there, is the lie we need to talk about.


The Prejudice Nobody Admits To

Web developers get ASP.NET Core MVC. They get controllers, routing, model binding, middleware, dependency injection — the whole cathedral. They get separation of concerns practically handed to them on a silver platter.

Console developers get Console.WriteLine().

There's an unspoken hierarchy in software development. Web apps are serious. Mobile apps are modern. Desktop apps are legacy, but fine. Console apps are the embarrassing cousin nobody invites to architectural discussions. They're treated as throwaway scripts, glorified batch files — things that don't warrant the same engineering rigour as a proper application.

I fundamentally disagree with that. And ConsoleMVC is the argument I'm making in code.

The Realisation That Started Everything

Think about what a console screen actually is. It collects input. It renders output. It hands control to the next screen.

That's a web page. A 2D web page, stripped of hyperlinks and scroll behaviour, but structurally identical in responsibility. A console screen should do one thing well — Single Responsibility Principle, if you want to be formal about it — and then step aside.

The moment I framed it that way, the architecture became obvious. If a web page maps to a Controller action, a View, and a Model, then a console screen should too. The framework already existed, conceptually. Someone just had to build the console version of it.

So I built it.

Convention Over Configuration, Or: Stop Registering Things Manually

The first version of ConsoleMVC required you to register every controller by hand. It was four lines of code per controller, which sounds trivial until you have fifteen controllers and you've misspelt "Settings" as "Settigns" for the fourth time and your application routes silently to nowhere.

That was miserable. I deleted all of it.

The framework now discovers controllers and views automatically at startup via reflection. You name a class HomeController, drop it in the Controllers/ folder, and it exists. You name a file IndexView.cvw, place it under Views/Home/, and the router finds it. No registration. No configuration files. No XML.

It is an example GreetController from the ConsoleMvc v1.1.0 template package. It displays the page Index and Receives form data posted from the Index view and displays a personalised greeting. If the name is empty, redirects back to the form.

The result feels almost suspicious — surely I have to tell it something? You don't. Convention does the work. Naming things correctly is your only obligation, which, admittedly, is already asking a lot of some teams.

The Source Generator, Or: The Part That Nearly Broke Me

The .cvw view files are where ConsoleMVC gets genuinely interesting, and also where I spent the better part of three weeks questioning my life choices.

The idea was simple: write plain C# in a file with an @model directive at the top, and have it compiled into a proper ConsoleView<TModel> class at build time. No runtime reflection. No string evaluation. Real, compiled, type-safe C#.

The execution was less simple.

Right now, it is not really that impressive, it is just a custom file, with extra syntax and plain C#. I'd say it is even worse than that, as if you're not using rider you are basically handicapped by lack of InteliJ - why rider? Well, good you asked. I made a custom made ConsoleMVC CvwSupport plugin - that provides basic life-supporting functionalities for modern developer.

Roslyn source generators are powerful and, in the way of all powerful things, thoroughly unforgiving. The generator targets netstandard2.0, which means certain APIs you'd reach for instinctively — Environment.NewLine, for instance — are banned. The analyser will tell you this at compile time, in red, with the energy of a disappointed parent.

But when it works — when you write @Model.Title in a .cvw file and IntelliJ autocompletes it, and it compiles, and the correct string appears in the terminal — it feels genuinely magical. The kind of magic that took weeks to build and looks effortless to use. Which is, I suppose, what good infrastructure is supposed to feel like.

Form Posting in a Console App (Yes, Really)

The feature I was most sceptical about was form data posting. The idea is that a view collects user input, packages it into a Dictionary<string, string>, and posts it to a controller action — exactly like an HTML form POST in web MVC. The framework binds the values to the action's parameters automatically, handling type conversion along the way.

My first reaction was: this is overengineered. Just read the input in the controller.

My second reaction, approximately two weeks later, was: no, this is correct.

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 situation. The form posting pattern enforces the separation even when it would be more convenient to abandon it — especially then, in fact.


// Post individual values to the Result action.
// The framework binds each dictionary entry to the matching
// parameter name on CalcController.Result(int a, int b, string op).
var formData = new Dictionary<string, string>
{
    ["a"] = a,
    ["b"] = b,
    ["op"] = op
};

The model binder supports complex types, simple parameters, nullable variants, enums, GUIDs, and case-insensitive key matching. Missing keys default gracefully. Type conversion failures default gracefully. It's the kind of robustness that only emerges after you've watched it fail in all the interesting ways first.

What's Coming

The current state of ConsoleMVC is a working, architectural foundation. What's coming is the part that excites me more: C-View Markup.

The vision is a markup language purpose-built for terminal UIs. Tags like <box>, <table>, <menu>, and <input> — not HTML, not a browser concern, but semantically meaningful primitives for console layout. Razor-style @ expressions for embedding C# logic. Style attributes for colour, alignment, and borders. The whole thing compiled at build time by the source generator into optimised rendering code.

Writing a console screen should feel like writing a view, not like hand-rolling ASCII art.

Console Apps Deserve Better

ConsoleMVC is still young. There are features missing. There are rough edges. I'm a student building this because I think the problem is real and the existing tooling doesn't solve it.

But the architectural foundation is sound. Convention-based routing, reflection-based discovery, a compiled view engine, automatic model binding — the same patterns that made ASP.NET Core MVC productive, applied to the terminal.

Console applications aren't lesser applications. They're just applications that have been consistently under-served by tooling that assumed they weren't worth the effort.

ConsoleMVC disagrees.


The project is open source and available on NuGet as ConsoleMVC.Framework. If you've built frameworks, rendering engines, or markup parsers before and have thoughts on the direction — particularly the C-View Markup roadmap — I'd genuinely love to hear from you. Open an issue, send an email, or just read the code and cringe productively.