Category: Greenfield Apps

  • Clean Architecture in .NET: Where It Earns Its Complexity

    Clean Architecture in .NET: Where It Earns Its Complexity

    Clean architecture has become close to a default recommendation for new .NET projects, and for good reason: separating domain logic from infrastructure concerns makes a codebase easier to test and easier to change later. It also adds real ceremony: more projects, more interfaces, more indirection between a request coming in and a database row being written. That ceremony is worth it in some situations and genuine overhead in others.

    What Clean Architecture Actually Buys You

    The core promise is that your business rules do not know or care whether they are backed by SQL Server, Cosmos DB, or an in memory list during tests. That independence pays off clearly in a few situations.

    Complex domain logic that changes often. A quality assurance system tracking parts through a multi stage automotive supply chain, with rules that shift as regulations and supplier agreements change, benefits enormously from having that logic isolated in a domain layer that can be tested without spinning up a database.

    Multiple entry points into the same logic. If the same business rules need to run from a web API, a background job, and an internal admin tool, clean architecture stops you from duplicating that logic three times or coupling it awkwardly to one specific host.

    Long project lifespans with changing infrastructure. If you genuinely expect to swap a database technology, or add a second one, in the next few years, the abstraction pays for itself when that day arrives.

    Where It Becomes Overhead

    Small CRUD focused applications. An internal tool that reads and writes fairly simple records does not need four layers of indirection to do it. The extra projects and interfaces add friction without adding testability that matters, because there is not much business logic to isolate in the first place.

    Teams new to the pattern under time pressure. Clean architecture has a real learning curve. A team unfamiliar with it, working against a tight deadline, often ends up with the structure but not the discipline: dependencies leak across layers anyway, and you are left paying the complexity cost without the benefit.

    Prototypes and proof of concepts. If there is a real chance the whole approach gets thrown away after validation, invest the architecture effort later, once you know the product is worth building properly.

    A Practical Middle Ground

    src/

    Domain/ // entities, business rules, no dependencies

    Application/ // use cases, orchestration

    Infrastructure/ // EF Core, external APIs

    Api/ // minimal API endpoints

    For most greenfield .NET projects that are not trivial CRUD, this four folder structure gives you real separation without the heavier ceremony some clean architecture templates default to, such as separate projects per layer with their own dependency graphs. Start with folders inside one project. Split into separate projects only once the codebase is large enough that build times or team boundaries genuinely require it.

    The Decision in One Question

    Before adopting clean architecture on a new build, ask honestly: is the domain logic in this application complex enough, and likely to change often enough, that isolating it from infrastructure will save real time over the life of the project? If yes, the structure earns its keep. If the honest answer is that this is mostly a data entry and reporting tool, a simpler, more direct architecture will get you to production faster and will not cost you anything meaningful in maintainability later.

    Architecture decisions made out of habit, rather than out of an honest read on the specific project, are one of the most common sources of wasted effort on greenfield builds.

  • WebForms to Blazor: A Realistic Migration Path, Not a Rewrite

    WebForms to Blazor: A Realistic Migration Path, Not a Rewrite

    WebForms applications have a reputation problem. They are old, the tooling around them has quietly stopped improving, and every conference talk assumes you have already left. None of that changes the fact that a lot of WebForms applications are still running real logistics and operations businesses today, reliably, and a full rewrite is rarely the responsible next step.

    Here is a more realistic path from WebForms to Blazor, one that respects the fact that the current system works and the team maintaining it has other priorities too.

    Start With What Actually Needs to Change

    Not every screen in a WebForms application needs modernising at the same time. Before writing any Blazor code, map the application by how often each screen is touched and how much business risk it carries. A rarely used admin screen with stable requirements can wait. A dispatch screen that every warehouse operator opens fifty times a day, built on postback patterns that make small changes painful, is where the return on modernisation is highest.

    This mapping exercise alone often reshapes the whole plan. Teams that assume they need to migrate everything usually find that twenty percent of the screens carry eighty percent of the daily pain.

    Run Both Frameworks Side by Side

    You do not need a big bang cutover. IIS can host a WebForms application and a Blazor Server or Blazor WebAssembly application side by side, with routing or a reverse proxy directing traffic to the right one per URL path. This means you can migrate one screen, ship it, get real feedback from the people using it daily, and migrate the next one, rather than committing to a multi month freeze on new features while the whole thing gets rebuilt.

    // Reverse proxy style routing:

    // /dispatch/* -> new Blazor app

    // everything else -> existing WebForms app

    The Business Logic Usually Survives Intact

    The part people worry about most, the actual business rules buried in code behind files, is usually the part that survives a migration best. Extracting that logic into plain C# classes with no dependency on the WebForms page lifecycle means it can be called from both the old pages and the new Blazor components during the transition, and it becomes properly testable for the first time, often years after it was written.

    This is also where the real value of the migration shows up. Not “it looks modern now,” but “we can finally write a unit test for the freight calculation logic that nobody has dared touch since 2014.”

    What Blazor Actually Buys You

    Beyond the obvious modern component model, the concrete wins for a logistics operations team tend to be:

    1. Real time updates without full page postbacks, which matters when dispatchers need to see order status change live.
    2. A component model that lets you reuse UI logic across screens instead of copying markup between pages.
    3. A path to a genuinely responsive interface for warehouse staff using tablets, which WebForms never handled gracefully.

    Set Realistic Expectations

    A WebForms to Blazor migration done screen by screen, prioritised by actual business risk, typically takes months, not weeks, for a system of meaningful size. That is not a failure of planning. It reflects the fact that the system has been absorbing business logic for years and deserves to be moved carefully rather than quickly.

    The goal is not to declare the old system dead on a fixed date. It is to reach a point where the screens that matter most are on a framework your team can actually build on, while the rest keeps running exactly as it has, until its turn comes.

  • .NET 8 vs .NET 10: What Actually Changed for Production Teams

    .NET 8 vs .NET 10: What Actually Changed for Production Teams

    Every .NET release comes with a changelog long enough to skim past. Most of it does not matter to a team shipping a real product. This is a shorter list: what changed between .NET 8 and .NET 10 that is actually worth planning around if you are running a production SaaS application.

    Native AOT Got Genuinely Usable

    Native AOT existed in .NET 8, but the supported surface area was narrow enough that most teams left it alone. In .NET 10, more of the standard library and common libraries work correctly under AOT, which means faster startup and a smaller memory footprint are now realistic for more than toy console apps. For a SaaS product running many small services, that translates directly into lower compute cost per instance and faster cold starts, which matters a great deal if you scale horizontally or run serverless workloads.

    The honest caveat: if your codebase leans heavily on reflection or dynamic code generation, AOT still needs real testing before you commit. It is worth a spike, not a blind upgrade.

    // Publish with AOT

    dotnet publish -c Release -r linux x64 -p:PublishAot=true

    The Garbage Collector Tuning Options Improved

    .NET 10 exposes more granular server GC tuning than .NET 8 did, particularly useful for high throughput API workloads with bursty allocation patterns. If your team has ever fought GC pauses during traffic spikes, this alone can justify the upgrade. It will not fix a memory leak, but it gives you more honest levers to pull once the leak is fixed.

    Minimal APIs Matured

    Minimal APIs were usable in .NET 8 but felt incomplete for anything beyond small services: validation, OpenAPI generation, and route grouping all needed workarounds. By .NET 10, most of that friction is gone. For a new SaaS build, this is the strongest argument for greenfield teams to skip controller based APIs entirely and start with minimal APIs from day one, unless the team already has strong conventions built around controllers.

    What Did Not Meaningfully Change

    Entity Framework Core’s core query behaviour is stable across both versions. If your pain points are around EF performance, the fix is almost always in how queries are written, not the runtime version. Do not expect an upgrade to quietly solve an N+1 query problem.

    Dependency injection, configuration, and hosting all remain conceptually the same. Teams sometimes assume a major version bump means a rewrite of startup code. It does not.

    Should You Upgrade an Existing SaaS Product

    For most teams already on .NET 8, the honest answer is: upgrade, but do not rush it. .NET 8 is a long term support release with real staying power, so there is no urgency driven by end of support. The case for moving to .NET 10 is strongest when:

    1. You are compute cost sensitive and Native AOT could meaningfully cut your hosting bill.
    2. You are actively fighting GC related latency spikes under load.
    3. You are starting a new service and would rather build on the newer API surface than migrate it later.

    If none of those apply, .NET 8 will serve you well for a while yet. Version upgrades are not free. They cost testing time, and that time is better spent when there is a concrete benefit waiting on the other side.

    For a new greenfield build starting today, .NET 10 with minimal APIs and Native AOT evaluated early is a reasonable default. For an existing production system, plan the move deliberately around one of the three reasons above rather than upgrading simply because a newer number exists.