The Starting Point
A 12-year-old VB.NET desktop application that had outlived its era. The application managed a real business workflow with an established SQL Server database — the data model was solid and worth keeping. Everything around it wasn't.
The specific pain points:
- ›SSRS dependency for all reporting — heavy, locked to on-prem infrastructure, impossible to run anywhere else
- ›Active Directory group authentication — groups managed manually, no MFA enforcement, no conditional access
- ›No CI/CD — deployments were manual file copies
- ›VB.NET codebase — no modern tooling, no GitHub, no one under 45 was comfortable in it
- ›On-prem SQL Server — no redundancy, no managed backups, no scalability path
The goal was a complete modernization without losing a decade of business data or changing how reports looked.
💡 Tip
The database schema was the one thing worth preserving. 12 years of production data, well-normalized, with real business meaning baked in. The migration strategy was built around protecting that.
What Changed
Database: SQL Server → Azure SQL
The schema migrated to Azure SQL Database with minimal changes — a few column type adjustments and the removal of some SSRS-specific stored procedures. Azure SQL's compatibility level handles the rest.
This gave the application managed backups, point-in-time restore, geo-redundancy, and elastic scaling without touching the application's data layer.
Frontend: VB.NET Windows Forms → Blazor Server
The UI was rebuilt as a Blazor Server application. Blazor was chosen over React or Next.js deliberately — the team maintaining this application is .NET-native. Blazor keeps them in C# and Razor syntax, with no JavaScript framework to learn or maintain.
The component structure mirrors the original application's workflow layout, making the UX transition feel evolutionary rather than disorienting for end users.
Reporting: SSRS → QuestPDF
This was the most technically interesting constraint. SSRS reports had been in use for 12 years. The rendered output — column widths, fonts, page breaks, headers — was what users considered "correct." Any deviation would be rejected.
QuestPDF replaced SSRS entirely, with reports defined in fluent C# that produce pixel-accurate PDF output matching the original SSRS layouts.
Document.Create(container => {
container.Page(page => {
page.Size(PageSizes.Letter);
page.Margin(1, Unit.Centimetre);
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().Text(x => {
x.Span("Page ");
x.CurrentPageNumber();
x.Span(" of ");
x.TotalPages();
});
});
});The reports now generate server-side in Blazor and stream as PDF downloads — no SSRS server, no report manager, no deployment complexity.
ℹ Note
QuestPDF is MIT licensed, actively maintained, and generates production-quality PDFs in pure C#. For anyone still running SSRS dependencies, this is the migration path.
Authentication: AD Groups → Entra SSO with Application Roles
Authentication moved from LDAP-queried Active Directory groups to Entra ID SSO with application roles defined in the app registration.
This gave the application MFA enforcement via conditional access policies, full audit logging through Entra sign-in logs, and role assignment managed in the Entra portal rather than AD group membership.
// Blazor auth policy using Entra application roles
builder.Services.AddAuthorization(options => {
options.AddPolicy("AdminOnly", policy =>
policy.RequireRole("App.Admin"));
options.AddPolicy("ReadOnly", policy =>
policy.RequireRole("App.Read", "App.Admin"));
});CI/CD: Manual Deploys → GitHub Actions
The codebase moved to GitHub. Two environments: dev (auto-deploys on every push to main) and prod (gated — requires a manual approval in GitHub Actions before deploying).
deploy-prod:
needs: deploy-dev
environment:
name: production
url: https://app.greyhelixit.com
steps:
- name: Deploy to Azure App Service (prod)
uses: azure/webapps-deploy@v3
with:
app-name: ${{ secrets.AZURE_PROD_APP_NAME }}
package: ./publishThis gave the team a proper testing surface and removed the risk of untested changes going directly to production.
Build Time
The entire project — database migration, Blazor rebuild, QuestPDF report porting, Entra SSO integration, and GitHub Actions CI/CD setup — took approximately 20 hours across two weekends, with Claude Code handling the heavy lifting on boilerplate, report layout code, and the Entra configuration.
Outcome
| Before | After |
|---|---|
| VB.NET Windows Forms | Blazor Server (C#) |
| On-prem SQL Server | Azure SQL Database |
| SSRS reports | QuestPDF (in-process, streaming) |
| AD group authentication | Entra ID SSO + application roles |
| Manual file deployments | GitHub Actions (dev + prod gated) |
| No redundancy | Managed backups, geo-redundancy |
The application is running in production. Users reported the new UI as "basically the same but faster" — which, for a modernization project, is the ideal response. Reports are identical to the original SSRS output. The SSRS server has been decommissioned.
💡 Tip
This project was an experiment: how much can a single consultant modernize in a weekend using Claude Code as a pair? The answer, for a well-scoped application with a clean data model, is: all of it.