diff --git a/README.md b/README.md index 21e465e..16658b0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,17 @@ SampleMvcWebApp =============== +> **.NET Upgrade in progress.** This repository is being migrated from +> ASP.NET MVC 5 (.NET Framework 4.5.1) to ASP.NET Core 6 using the +> [Strangler Fig pattern](https://martinfowler.com/bliki/StranglerFigApplication.html). +> The legacy app described below stays under `SampleWebApp/`, +> `BizLayer/`, `DataLayer/`, `ServiceLayer/`, and `Tests/`. The new +> ASP.NET Core 6 app lives in `netcore/`. See [`docs/`](docs/README.md) +> for the migration documentation index, starting with the +> [Phase 1 Migration Guide](docs/Phase1-Migration-Guide.md) and the +> [Onboarding doc](docs/Onboarding.md). The Jira epic is +> [NET-1](https://cog-gtm.atlassian.net/browse/NET-1). + SampleMvcWebApp is a ASP.NET MVC5 web site designed to show number of useful methods for building enterprise grade web applications using ASP.NET MVC5 and Entity Framework 6. The code for this sample MVC web application, and the associated diff --git a/docs/CodeReview-Guidelines.md b/docs/CodeReview-Guidelines.md new file mode 100644 index 0000000..cf7574b --- /dev/null +++ b/docs/CodeReview-Guidelines.md @@ -0,0 +1,172 @@ +# Code Review Guidelines — .NET Upgrade + +> Applies to every PR that touches `netcore/` or this `docs/` folder. +> The legacy MVC5 app under `SampleWebApp/`, `BizLayer/`, etc. is frozen +> during the strangle — review there is just "should we be touching this at +> all in Phase 1?". + +This document is a **checklist**, not a style guide. The intent is that a +reviewer can step through it linearly during a code review and the author +can self-review against it before requesting a review. + +--- + +## 1. Scope and ticket linkage + +- [ ] PR title is `[NET-XX] ` matching the Jira ticket. +- [ ] PR description links the Jira ticket. +- [ ] PR description states **why** the change is being made, not just what. +- [ ] PR description lists the migration-pattern decisions used (e.g. + "uses constructor injection per Phase 1 Migration Guide §4.6"). +- [ ] Diff is scoped to one ticket. Multi-ticket PRs are rejected. + +## 2. Migration correctness + +For controller / model / view migrations specifically: + +- [ ] Controller inherits `Microsoft.AspNetCore.Mvc.Controller`, not + `System.Web.Mvc.Controller`. +- [ ] Action signatures return `IActionResult` (or a specific subtype) — + never `ActionResult`. +- [ ] No `System.Web.*` namespaces remain in migrated files. +- [ ] No `PerformanceCounter` (or any Windows-only API) without an + `OperatingSystem.IsWindows()` guard. +- [ ] No `GC.GetTotalMemory(true)` — forcing a full GC in a request path + is a bug. +- [ ] `Models/` and `Controllers/` namespaces follow the + `SampleWebApp..` convention. +- [ ] Static assets live under `wwwroot/`, not `Content/` or `Scripts/`. +- [ ] Views use tag helpers (`asp-controller`, `asp-action`, `asp-route-*`) + for new markup; `@Html.*` helpers are acceptable when copied + verbatim from the legacy view. +- [ ] `_ViewImports.cshtml` exists in `Views/` and registers + `@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers`. +- [ ] Routes are registered with `MapControllerRoute` — never `app.UseMvc`. + +## 3. Configuration + +- [ ] No new entries in `Web.config` (the legacy file is frozen). +- [ ] New configuration lives in `appsettings.json` and is read via + `IOptions` — not `IConfiguration` ad-hoc. +- [ ] Secrets are **not** committed. Use `dotnet user-secrets` or + environment variables. +- [ ] Per-environment overrides use `appsettings..json`, never + `Web..config` transforms. + +## 4. Dependency injection + +- [ ] No Autofac registrations added to the new app in Phase 1. +- [ ] Constructor injection only — no service-locator anti-pattern + (`HttpContext.RequestServices.GetService()`). +- [ ] Service lifetimes match expectations: `AddScoped` for + per-request, `AddSingleton` for stateless infra, `AddTransient` for + lightweight stateless factories. Document non-obvious choices in a + single comment on the registration line. + +## 5. Testing + +- [ ] Every public controller action has at least one unit test. +- [ ] Every public route has at least one integration test asserting + status code and content type. +- [ ] Test names follow `MethodUnderTest_State_ExpectedBehavior`. +- [ ] Tests use FluentAssertions (`x.Should().Be(y)`) — not raw + `Assert.Equal(...)` — for new test files. Legacy NUnit tests under + `Tests/` are exempt; do not modify them. +- [ ] No `Thread.Sleep` in tests. Use `await Task.Delay` (which is also + suspicious in tests — usually a deterministic alternative exists). +- [ ] No tests skipped (`Skip="..."`) without a TODO referencing a Jira + ticket. +- [ ] Coverage on touched controller / model files is ≥ 90% line. + +## 6. Code quality + +- [ ] No dead code (unused `using`s, unreachable branches, leftover + `Console.WriteLine`, debug attributes). +- [ ] No `// TODO` without an associated Jira ticket reference. +- [ ] No commented-out code. Delete it — git remembers. +- [ ] Naming follows .NET conventions: PascalCase types and methods, + `_camelCase` private fields, `camelCase` locals/parameters. +- [ ] Nullable annotations are honoured — no `!` (null-forgiving) operator + to silence warnings; fix the root cause. +- [ ] `async` methods end in `Async` and return `Task`/`Task` (or + `ValueTask` for hot paths). No `async void` outside event handlers. +- [ ] No new dependencies added unless justified in the PR description. + Each new NuGet package adds attack surface and version drift risk. + +## 7. Formatting and lint + +- [ ] `dotnet format --verify-no-changes netcore/SampleWebApp.NetCore.sln` + reports clean. +- [ ] No mixed line endings — `.gitattributes` enforces this, but check + if you committed from a tool that ignores it. +- [ ] No trailing whitespace. +- [ ] `` versions match across projects; central package + management via `Directory.Packages.props` if version drift starts. + +## 8. Documentation + +- [ ] If the PR establishes a new pattern, the relevant `docs/` file is + updated in the same PR — not deferred. +- [ ] Public APIs and non-obvious internal methods have XML doc comments. +- [ ] Onboarding-affecting changes (new prereqs, new env vars, new run + commands) update [Onboarding.md](Onboarding.md). +- [ ] Configuration changes update + [Phase1-Configuration-Migration.md](Phase1-Configuration-Migration.md). +- [ ] New troubleshooting cases land in + [Phase1-Troubleshooting.md](Phase1-Troubleshooting.md). + +## 9. Security + +- [ ] No SQL string concatenation. EF Core parameterised queries only. +- [ ] Antiforgery tokens on every state-changing endpoint (`[HttpPost]`, + `[HttpDelete]`, etc.). +- [ ] No user input flowing into `Response.Redirect` without + `Url.IsLocalUrl` validation. +- [ ] Logging never includes PII or secrets (connection strings, tokens, + Authorization headers). +- [ ] No `dynamic` on data-bound input. +- [ ] If `Autofac` is reintroduced later, it must be the + `Autofac.Extensions.DependencyInjection` integration — not a parallel + container. + +## 10. Performance + +- [ ] No synchronous I/O in async paths (`File.ReadAllText` in an action + method, `Task.Result`, `Task.Wait`). +- [ ] No `.ToList()` immediately after `IQueryable` unless the result + is consumed eagerly with intent. +- [ ] No N+1 query patterns in EF Core (Phase 2 onward). +- [ ] Long-running endpoints use `CancellationToken` and pass it through. + +## 11. Reviewer's final pass + +Before approving: + +- [ ] Read the diff top-to-bottom one more time, assuming you've never + seen the ticket. Does the change still make sense? +- [ ] Do all CI checks pass? Including code-quality (`dotnet format`, + security audit, build warnings). +- [ ] Is there a single coherent commit message, or has the author left + "fix lint", "wip", "address review" commits? Prefer a clean rebase + before merge. +- [ ] Are CodeRabbit / Devin / Graphite bot comments resolved or + explicitly waived with reasoning? +- [ ] Did the author tick the Jira acceptance-criteria boxes in the PR + description? + +## 12. Author's pre-request-review pass + +Run this verbatim before clicking "Ready for review": + +```bash +git fetch origin +git diff --merge-base origin/master +dotnet format netcore/SampleWebApp.NetCore.sln +dotnet build netcore/SampleWebApp.NetCore.sln -c Release /warnaserror +dotnet test netcore/SampleWebApp.NetCore.sln \ + --collect:"XPlat Code Coverage" \ + --settings netcore/coverlet.runsettings +``` + +If any step is red, fix before requesting review. Reviewers should not be +the lint runner. diff --git a/docs/Development-Workflow.md b/docs/Development-Workflow.md new file mode 100644 index 0000000..32f6f51 --- /dev/null +++ b/docs/Development-Workflow.md @@ -0,0 +1,275 @@ +# Development Workflow — .NET Upgrade + +> Applies to every Jira ticket in the `NET` project that touches +> `SampleMvcWebApp`. Pair with [CodeReview-Guidelines.md](CodeReview-Guidelines.md). + +This document captures **how** we ship code during the migration. It is +intentionally short; the rules are easy to follow and there are not many of +them. + +--- + +## 1. Branching + +### Naming + +``` +/-net- +``` + +Examples: + +- `alice/1778767055-net11-homecontroller-net6` +- `bob/1779999999-net20-blogs-controller-migration` +- `devin/1778767055-net14-phase1-docs` + +Why a timestamp? It prevents collisions when two people work the same +ticket and makes ordering trivial in `git branch --sort=committerdate`. + +### Base branch + +- Branch from `origin/master`. +- Long-lived feature flags or stacked PRs use Graphite. Do not branch off + another open feature branch unless the dependency is explicit and tracked + in Jira. + +### Lifetime + +- Open a PR within 48 hours of the first commit (early draft is fine). +- Merge or close within two weeks. Long-lived branches drift. +- Delete the branch on merge — GitHub does this automatically when the + repo setting is on; if your repo fork disables it, do it manually. + +--- + +## 2. Commits + +### Message format + +``` +NET-: + + + +Refs: NET- +``` + +Examples: + +``` +NET-14: Document Phase 1 migration patterns + +Adds top-level docs/ folder covering the migration guide, configuration +migration, troubleshooting, onboarding, code-review guidelines, and the +team-training agenda. References netcore/docs/{testing,cicd}.md produced +by NET-12 and NET-13 rather than duplicating their content. + +Refs: NET-14 +``` + +``` +NET-11: Migrate HomeController to .NET Core 6 + +Drops System.Web.Mvc dependency and ports the five Home actions to +IActionResult. Replaces the Windows-only PerformanceCounter usage in +InternalsInfo with GC.GetGCMemoryInfo() so the model is cross-platform. + +Refs: NET-11 +``` + +### Rules + +- One logical change per commit. "Fix lint" and "Address review" commits + should be squashed before merge (Graphite / GitHub squash-merge handles + this automatically; configure your branch). +- Never commit generated files (bin/, obj/, .vs/, *.user). The repo + `.gitignore` covers these; double-check before `git add`. +- Never commit secrets. `.env`, `appsettings.Development.json` + containing real credentials, `*.pfx`, anything with `secret` in the + filename — all banned. +- Never amend a commit that has been pushed and reviewed. Add a new + commit instead. + +--- + +## 3. Pull requests + +### Title + +``` +[NET-] +``` + +Examples: + +- `[NET-11] Migrate HomeController to .NET Core 6` +- `[NET-14] Document Phase 1 migration patterns` + +### Description template + +```markdown +## Summary +What changes and why. 2–5 sentences. + +## Jira +[NET-](https://cog-gtm.atlassian.net/browse/NET-) + +## Acceptance criteria +- [ ] (paste from the Jira ticket, ticked where this PR satisfies them) + +## How to verify +1. Reproducible steps a reviewer can run locally +2. Expected output / screenshots / log snippets + +## Assumptions / decisions +Any non-obvious decision the reviewer should know about. + +## Out of scope +What the PR deliberately does not do (and which ticket owns it). +``` + +### Reviewers + +- At least one human reviewer. +- Bot reviewers (CodeRabbit, Devin, Graphite) auto-assign on PR open and + are not a substitute for human review. + +### Draft vs ready + +- Open as **draft** while you're still pushing fixups. +- Mark **ready for review** only after running the author's checklist in + [CodeReview-Guidelines.md §12](CodeReview-Guidelines.md#12-authors-pre-request-review-pass). + +### Stacked PRs + +For multi-ticket work, prefer **one PR per ticket** stacked via Graphite. +Avoid combining tickets into a single PR — it slows review and makes revert +harder. + +--- + +## 4. CI expectations + +Every PR that touches `netcore/` or `docs/` triggers the workflows in +`.github/workflows/`: + +| Workflow | What it does | Must pass before merge | +|------------------|--------------|------------------------| +| `ci.yml` | Build + unit/integration tests + coverage | Yes | +| `code-quality.yml` | `dotnet format`, NuGet audit, build with `/warnaserror` | Yes | +| `cd.yml` | Staging/production deploys (post-merge to `master`) | n/a for PR | + +Bot CI: + +- **CodeRabbit** posts inline review comments. Treat them like a human + reviewer — resolve or explicitly waive. +- **Devin Review** posts a PR-wide review comment. Same handling. +- **Graphite Diamond** flags risky diffs. Same handling. + +CI checks reporting "passed" with bot reviewers does **not** mean the bot +liked the PR. Read the inline comments. + +--- + +## 5. Ticket linkage + +### On branch creation + +1. Move the Jira ticket to **In Progress** before pushing your first commit. +2. The Devin GTM service account auto-comments the session URL on the + ticket when working from the playbook. + +### On PR open + +1. Add the PR URL as a remote link on the ticket (Atlassian MCP + `addRemoteIssueLink` or via the GitHub-Jira integration if installed). +2. Post a brief comment on the ticket: `PR: `. No body needed. + +### On merge + +1. Move the ticket to **Done** **after** the PR merges to `master` and + CD has been observed green on staging. Do **not** mark Done on PR open. +2. If the PR introduces follow-ups, file them as separate tickets linked + to the parent epic (`NET-1` for Phase 1). + +### On revert + +If a PR is reverted post-merge: + +1. Open a follow-up ticket linked to the original. +2. Re-open or clone the original ticket; do not silently leave it at + "Done" while the change is no longer in `master`. + +--- + +## 6. Working with the legacy MVC5 app + +The legacy app stays frozen during Phase 1–3. The only acceptable diffs +under `SampleWebApp/`, `BizLayer/`, `DataLayer/`, `ServiceLayer/`, +`Tests/`, `Licence.txt`, `packages/` are: + +- Build-breaking fixes that block CI. +- Security patches to NuGet packages with public CVEs. + +Anything else — including "tiny refactor", "delete obviously dead code", +"upgrade jQuery while we're here" — is rejected. File a separate ticket. + +--- + +## 7. Working with Confluence + +The long-form planning artifacts live in Confluence: + +- [Phase 1: Foundation - HomeController Migration](https://cog-gtm.atlassian.net/wiki/spaces/NU/pages/19562813) +- [ASP.NET MVC to .NET Core 6 Controller Migration Assessment (parent)](https://cog-gtm.atlassian.net/wiki/spaces/NU/pages/19071008) + +Rule of thumb: + +- **Repo docs (`docs/`)**: technical reference that a developer touching + the code needs. +- **Confluence pages**: planning, scope, sign-offs, cross-team + communication. + +When repo docs land on `master`, mirror the new content (or just a link) +to the relevant Confluence page so non-engineers know it exists. + +--- + +## 8. Communication + +- Per-PR questions: GitHub PR comments. +- Per-ticket questions: Jira comments. +- Broad architectural questions: `#net-upgrade` Slack channel. +- Cross-team alignment: Confluence + scheduled meeting; not Slack. + +Avoid DMs for technical decisions — they don't survive personnel changes. + +--- + +## 9. Cheat sheet + +```bash +# Start work on NET-XX +git checkout master +git pull +git checkout -b $USER/$(date +%s)-net- + +# ...do work... + +git status # double-check what's staged +git add # never `git add .` +git commit -m "NET-: " -m "" +git push -u origin HEAD + +# Open PR via gh CLI +gh pr create --fill --draft \ + --title "[NET-] " \ + --base master + +# Run the full local pipeline +dotnet format netcore/SampleWebApp.NetCore.sln +dotnet build netcore/SampleWebApp.NetCore.sln -c Release /warnaserror +dotnet test netcore/SampleWebApp.NetCore.sln \ + --collect:"XPlat Code Coverage" \ + --settings netcore/coverlet.runsettings +``` diff --git a/docs/Onboarding.md b/docs/Onboarding.md new file mode 100644 index 0000000..0676237 --- /dev/null +++ b/docs/Onboarding.md @@ -0,0 +1,239 @@ +# Developer Onboarding — `SampleMvcWebApp` .NET Core 6 side + +> First-time setup guide for engineers joining the .NET Upgrade. ~30 minutes +> from clone to first integration test pass. If anything in this guide fails, +> open a PR fixing it — onboarding docs are only useful if they are kept +> current. + +This guide gets you productive on the **`netcore/` half** of the repository +(the migrated ASP.NET Core 6 app). The legacy MVC5 app under `SampleWebApp/` +keeps its existing Visual Studio workflow and is documented in the root +`README.md`. + +--- + +## 1. Prerequisites + +| Tool | Required version | How to verify | +|------|------------------|---------------| +| .NET SDK | **6.0.x** | `dotnet --version` shows `6.0.*` | +| Git | any recent | `git --version` | +| IDE | Visual Studio 2022 (17.0+), Rider 2022.1+, or VS Code with the C# Dev Kit | open this repo | +| OS | Windows 10/11, macOS 12+, Ubuntu 20.04+ | `dotnet --info` | + +Optional but recommended: + +- **Docker Desktop** — for CI parity and the Dockerfile under `netcore/`. +- **SQL Server LocalDB** (Windows) or `mcr.microsoft.com/mssql/server` Docker + image (Linux/macOS) — needed in Phase 2 when the data layer migrates. + +### Pinning the SDK + +`global.json` (at the repo root) pins the .NET SDK version used for the +`netcore/` build. If you have several SDKs installed, `dotnet --version` +inside the repo will report the pinned one. If it doesn't, install the exact +version listed in `global.json` from +[dotnet.microsoft.com/download/dotnet/6.0](https://dotnet.microsoft.com/download/dotnet/6.0). + +--- + +## 2. Clone and build + +```bash +git clone https://github.com/COG-GTM/SampleMvcWebApp.git +cd SampleMvcWebApp + +# Restore packages and build only the new app +dotnet restore netcore/SampleWebApp.NetCore.sln +dotnet build netcore/SampleWebApp.NetCore.sln -c Debug +``` + +Expected: `Build succeeded. 0 Warning(s), 0 Error(s)`. If you see warnings +treat them as a blocker — the CI gate uses `/warnaserror`. + +--- + +## 3. Run the app + +```bash +dotnet run --project netcore/src/SampleWebApp +``` + +Open: + +| URL | Expected | +|------------------------------------|----------| +| `http://localhost:5000/` | `Home/Index` page renders | +| `http://localhost:5000/Home/About` | "Your application description page." | +| `http://localhost:5000/Home/Contact` | static page | +| `http://localhost:5000/Home/Internals` | runtime metrics (worker threads, available threads, available MB, heap KB) | +| `http://localhost:5000/Home/CodeView` | introduction to GenericServices | + +If HTTPS redirection complains, accept the dev cert: + +```bash +dotnet dev-certs https --trust +``` + +To run under a specific environment: + +```bash +ASPNETCORE_ENVIRONMENT=Staging dotnet run --project netcore/src/SampleWebApp +``` + +--- + +## 4. Run the tests + +```bash +dotnet test netcore/SampleWebApp.NetCore.sln \ + --collect:"XPlat Code Coverage" \ + --settings netcore/coverlet.runsettings +``` + +Three suites run: + +| Project | What it does | +|---------|--------------| +| `SampleWebApp.UnitTests` | xUnit + Moq + FluentAssertions unit tests for controller, model, providers | +| `SampleWebApp.IntegrationTests` | Boots the app in-process via `WebApplicationFactory`; HTTP assertions on every route | +| `SampleWebApp.PerformanceTests` | BenchmarkDotNet benchmarks (run manually, not in CI) | + +Run a single suite: + +```bash +dotnet test netcore/tests/SampleWebApp.UnitTests/SampleWebApp.UnitTests.csproj +``` + +Run a single test by name: + +```bash +dotnet test netcore/tests/SampleWebApp.UnitTests --filter "FullyQualifiedName~HomeControllerTests.Index" +``` + +Full test stack details: [`netcore/docs/testing.md`](../netcore/docs/testing.md). + +--- + +## 5. Editor setup + +### Visual Studio 2022 + +1. **Open**: `netcore/SampleWebApp.NetCore.sln`. Do **not** open + `SampleWebApp.sln` (root) — that loads the legacy MVC5 solution. +2. Set `netcore/src/SampleWebApp` as the startup project. +3. The debug profile defaults to `https`. Switch to `http` if you don't + want the dev cert prompt. + +### Rider + +1. **Open** the `SampleMvcWebApp` folder. Rider detects both solutions — + pick `netcore/SampleWebApp.NetCore.sln`. +2. Enable "Auto-import" for using statements and "Run with Hot Reload" for + the run configuration. + +### VS Code + +1. Install the **C# Dev Kit** extension (Microsoft). +2. Trust the workspace. +3. In the Status Bar, click "Select solution" and choose + `netcore/SampleWebApp.NetCore.sln`. + +A `.vscode/launch.json` for `dotnet run --project netcore/src/SampleWebApp` +will be auto-generated on first F5. + +--- + +## 6. Project tour (5 minutes) + +``` +netcore/ +├── SampleWebApp.NetCore.sln ← open this one +├── src/SampleWebApp/ +│ ├── Program.cs ← startup, DI, middleware +│ ├── Controllers/HomeController.cs ← 5 actions, no DI in Phase 1 +│ ├── Models/InternalsInfo.cs ← cross-platform runtime metrics +│ ├── Views/Home/*.cshtml ← migrated from legacy SampleWebApp/Views/Home +│ ├── wwwroot/ ← static assets (CSS, JS, fonts, favicon) +│ ├── appsettings*.json ← replaces Web.config +│ └── SampleWebApp.csproj ← Microsoft.NET.Sdk.Web, net6.0 +├── tests/ +│ ├── SampleWebApp.UnitTests/ +│ ├── SampleWebApp.IntegrationTests/ +│ └── SampleWebApp.PerformanceTests/ +├── docs/ +│ ├── testing.md ← NET-12 +│ └── cicd.md ← NET-13 +├── Dockerfile +├── docker-compose.yml +└── coverlet.runsettings +``` + +**Where to put new code as Phase 2 / 3 land:** + +- New controller → `netcore/src/SampleWebApp/Controllers/Controller.cs`. +- New model / DTO → `netcore/src/SampleWebApp/Models/`. +- Domain or repository code (Phase 2) → `netcore/src/DataLayer/`, + `netcore/src/BizLayer/`, `netcore/src/ServiceLayer/` (parallel structure + to the legacy `*Layer/` directories at the root). +- Service registration → an `*ServiceExtensions.cs` file under + `Startup/` in the relevant project, called from `Program.cs`. + +--- + +## 7. The legacy app (for reference only) + +Phase 1 does **not** modify the legacy MVC5 app. You will rarely need to +build it, but if you do: + +- Open `SampleWebApp.sln` (root) with Visual Studio on Windows. +- Install the .NET Framework 4.5.1 targeting pack. +- Build target: `Debug` / `AnyCPU`. + +The legacy app cannot be built from Linux/macOS or from `dotnet build` on +the command line. Treat it as a frozen reference until Phase 4. + +Root `README.md` documents the legacy app's features (GenericServices, +DTOs, Action Runner, etc.). + +--- + +## 8. Read these next + +In priority order: + +1. [Phase1-Migration-Guide.md](Phase1-Migration-Guide.md) — patterns and + procedures you'll reuse in every later phase. +2. [Phase1-Configuration-Migration.md](Phase1-Configuration-Migration.md) — + `Web.config` ↔ `appsettings.json` mapping. +3. [Phase1-Troubleshooting.md](Phase1-Troubleshooting.md) — error messages + you'll inevitably see. +4. [CodeReview-Guidelines.md](CodeReview-Guidelines.md) — what we look for + in PRs. +5. [Development-Workflow.md](Development-Workflow.md) — branching, commits, + PRs, ticket linkage. +6. [`netcore/docs/testing.md`](../netcore/docs/testing.md) — the test stack + and coverage expectations. +7. [`netcore/docs/cicd.md`](../netcore/docs/cicd.md) — CI workflows, Docker, + health checks. + +--- + +## 9. Smoke-test checklist before your first PR + +You're set up correctly if **all** of the following work without errors: + +- [ ] `dotnet --version` inside the repo reports `6.0.*`. +- [ ] `dotnet build netcore/SampleWebApp.NetCore.sln -c Debug` succeeds with + 0 warnings. +- [ ] `dotnet run --project netcore/src/SampleWebApp` serves + `http://localhost:5000/` and `/Home/About`, `/Home/Contact`, + `/Home/Internals`, `/Home/CodeView`. +- [ ] `dotnet test netcore/SampleWebApp.NetCore.sln` is green. +- [ ] `dotnet format netcore/SampleWebApp.NetCore.sln` makes zero changes. +- [ ] You can open the repo in your chosen IDE and step through + `HomeController.Index` under the debugger. + +If any step fails, fix it locally, then update +[Phase1-Troubleshooting.md](Phase1-Troubleshooting.md) with the fix so the +next person has it easier. diff --git a/docs/Phase1-Configuration-Migration.md b/docs/Phase1-Configuration-Migration.md new file mode 100644 index 0000000..6dd78aa --- /dev/null +++ b/docs/Phase1-Configuration-Migration.md @@ -0,0 +1,390 @@ +# Phase 1 Configuration Migration + +> Companion to [Phase1-Migration-Guide.md](Phase1-Migration-Guide.md). +> Scope: only the configuration touched by Phase 1 (`HomeController`). +> Anything not listed here is deferred to Phase 2. + +This document maps every configuration concern in the legacy MVC5 app to its +ASP.NET Core 6 replacement. + +--- + +## 1. Top-level mapping + +| Legacy file / concept | .NET Core 6 replacement | Phase 1 status | +|--------------------------------------|--------------------------------------------------------|----------------| +| `SampleWebApp/Web.config` | `netcore/src/SampleWebApp/appsettings.json` | Done | +| `Web.Debug.config` / `Web.Release.config` transforms | `appsettings.Development.json` / `Production.json` / `Staging.json` | Done | +| `SampleWebApp/Web.AzureRelease.config` / `Web.WebWizRelease.config` | Environment-specific `appsettings..json` + `ASPNETCORE_ENVIRONMENT` | Done | +| `SampleWebApp/Properties/Settings.Settings` (`SampleWebApp.Properties.Settings`) | `IOptions` typed configuration | Done | +| `Log4Net.xml` + `log4net.Config.XmlConfigurator` | Built-in `ILogger` + `builder.Logging` | Done | +| `Global.asax.cs` `Application_Start` | `Program.cs` top-level statements | Done | +| `App_Start/RouteConfig.cs` | `app.MapControllerRoute(...)` | Done | +| `App_Start/FilterConfig.cs` | `AddControllersWithViews(o => o.Filters.Add(...))` | Done | +| `App_Start/BundleConfig.cs` | `wwwroot/` static files (no bundling in Phase 1) | Done | +| `Infrastructure/AutofacDi.cs` | `builder.Services.Add*` calls / `*ServiceExtensions.cs`| Deferred (no DI consumers in Phase 1) | +| `Infrastructure/DiModelBinder.cs` | Constructor injection | Deferred | +| `Infrastructure/WebUiInitialise.cs` | `Program.cs` + extension methods | Done | +| `packages.config` | `` in `*.csproj` | Done | + +--- + +## 2. Connection strings + +### Legacy (`SampleWebApp/Web.config`) + +```xml + + + +``` + +### .NET Core 6 (`netcore/src/SampleWebApp/appsettings.json`) + +```json +{ + "ConnectionStrings": { + "SampleWebAppDb": "Data Source=(localdb)\\mssqllocaldb;Initial Catalog=SampleWebAppDb;MultipleActiveResultSets=True;Integrated Security=SSPI;Trusted_Connection=True" + } +} +``` + +Read with: + +```csharp +var cs = builder.Configuration.GetConnectionString("SampleWebAppDb"); +``` + +In Phase 1 nothing in the new app actually opens this connection — it is +preserved for Phase 2 (`DataLayer` migration to EF Core). + +**Provider names** are no longer part of the connection string in EF Core. +You select the provider at registration time: +`options.UseSqlServer(connectionString)` (Phase 2). + +--- + +## 3. App settings + +### Legacy `` + +```xml + + + + + + + +``` + +None of these have direct .NET Core 6 equivalents — `webpages:*` and `owin:*` +are legacy. Unobtrusive/client validation is on by default via the +`Microsoft.AspNetCore.Mvc.DataAnnotations` package included by +`AddControllersWithViews()`. Drop these keys. + +### Legacy `Properties.Settings.HostTypeString` + +`SampleWebApp/Properties/Settings.Settings` exposes a strongly-typed setting +read in `WebUiInitialise`: + +```csharp +HostType = DecodeHostType(Settings.Default.HostTypeString); // LocalHost | WebWiz | Azure +``` + +### .NET Core 6 — typed options + +```json +// appsettings.json +{ + "Hosting": { + "HostType": "LocalHost" + } +} +``` + +```csharp +// Options class +public sealed record HostingOptions +{ + public HostType HostType { get; init; } = HostType.LocalHost; +} + +public enum HostType { LocalHost, WebWiz, Azure } + +// Program.cs +builder.Services.Configure(builder.Configuration.GetSection("Hosting")); + +// Usage in a controller / service +public sealed class SomeController : Controller +{ + private readonly HostingOptions _hosting; + public SomeController(IOptions hosting) => _hosting = hosting.Value; +} +``` + +Phase 1 does not yet have a consumer for `HostingOptions`. The shape is +documented here so Phase 2 / 3 can adopt it without re-inventing. + +### Environment-specific overrides + +| Environment | File | `ASPNETCORE_ENVIRONMENT` | +|---------------|-------------------------------|--------------------------| +| Development | `appsettings.Development.json`| `Development` | +| Staging | `appsettings.Staging.json` | `Staging` | +| Production | `appsettings.Production.json` | `Production` | + +These override values from `appsettings.json` on a per-key basis. Environment +variables override JSON files: `ConnectionStrings__SampleWebAppDb=...` works +out of the box. + +--- + +## 4. Logging — log4net → `ILogger` + +### Legacy + +`SampleWebApp/Infrastructure/WebUiInitialise.cs`: + +```csharp +case HostTypes.LocalHost: +case HostTypes.WebWiz: + var log4NetPath = application.Server.MapPath(WebWizLog4NetRelPath); + log4net.Config.XmlConfigurator.ConfigureAndWatch(new System.IO.FileInfo(log4NetPath)); + GenericLibsBaseConfig.SetLoggerMethod = name => new Log4NetGenericLogger(name); + break; +case HostTypes.Azure: + GenericLibsBaseConfig.SetLoggerMethod = name => new TraceGenericLogger(name); + break; +``` + +`Log4Net.xml` (root of `SampleWebApp/`) configures appenders. + +### .NET Core 6 + +`Program.cs` already calls `WebApplication.CreateBuilder(args)`, which adds: + +- `Console`, `Debug`, `EventSource`, `EventLog` (Windows) providers. +- Reads `Logging:LogLevel` from configuration. + +`appsettings.json`: + +```json +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Warning" + } + } +} +``` + +Per-environment overrides (e.g. `appsettings.Production.json` raising +`Default` to `Warning`) are documented in +[`netcore/docs/cicd.md`](../netcore/docs/cicd.md). + +### Custom log4net pipeline + +`Log4NetGenericLogger` and `TraceGenericLogger` are not ported — they exist +only to satisfy `GenericLibsBaseConfig.SetLoggerMethod`, which is part of +the legacy `GenericServices` plumbing not used in Phase 1. + +If structured log output is required, add **Serilog** in a separate PR +(Phase 2 candidate). Until then, the built-in providers are sufficient. + +--- + +## 5. Application startup + +### Legacy + +`Global.asax.cs`: + +```csharp +protected void Application_Start() +{ + AreaRegistration.RegisterAllAreas(); + FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); + RouteConfig.RegisterRoutes(RouteTable.Routes); + BundleConfig.RegisterBundles(BundleTable.Bundles); + + ModelBinders.Binders.DefaultBinder = new DiModelBinder(); + WebUiInitialise.InitialiseThis(this); +} +``` + +### .NET Core 6 (`Program.cs`) + +```csharp +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllersWithViews(); + +var app = builder.Build(); + +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Home/Error"); + app.UseHsts(); +} + +app.UseHttpsRedirection(); +app.UseStaticFiles(); +app.UseRouting(); + +app.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}"); + +app.Run(); + +public partial class Program { } +``` + +Things removed and why: + +| Removed | Why | +|------------------------------------|-----| +| `AreaRegistration.RegisterAllAreas` | Areas are auto-registered by `AddControllersWithViews()`. | +| `FilterConfig.RegisterGlobalFilters` | Filters register via `AddControllersWithViews(o => o.Filters.Add(...))`. No filters in Phase 1. | +| `BundleConfig.RegisterBundles` | No bundling in Phase 1; `wwwroot/` files are served as-is. | +| `ModelBinders.Binders.DefaultBinder = new DiModelBinder()` | DI happens via constructor injection in Core; no custom default binder needed. | +| `WebUiInitialise.InitialiseThis` | Logic split between `Program.cs` and per-layer service-extension methods. | + +The `public partial class Program { }` line at the bottom is **load-bearing** — +it makes `Program` accessible to `WebApplicationFactory` in the +integration test project. See [`netcore/docs/testing.md`](../netcore/docs/testing.md). + +--- + +## 6. Dependency injection — Autofac → built-in + +### Legacy + +`Infrastructure/AutofacDi.cs` builds an `IContainer`, `WebUiInitialise` passes +it to `AutofacDependencyResolver`, and the global model binder routes +action-method parameters through DI. + +### Phase 1 + +`HomeController` has **zero** dependencies, so the built-in DI container is +all we need: + +```csharp +builder.Services.AddControllersWithViews(); +``` + +No `DiModelBinder` equivalent exists or is needed — controller constructor +parameters are resolved automatically by `Microsoft.Extensions.DependencyInjection`. + +### Phase 2 / 3 — when DI gets interesting + +When `DataLayer` and `BizLayer` land, the recommended grouping is per-layer +extension methods: + +```csharp +// netcore/src/DataLayer/Startup/DataLayerServiceExtensions.cs +public static class DataLayerServiceExtensions +{ + public static IServiceCollection AddDataLayer(this IServiceCollection services, IConfiguration config) + { + services.AddDbContext(o => o.UseSqlServer(config.GetConnectionString("SampleWebAppDb"))); + services.AddScoped(); + return services; + } +} + +// Program.cs +builder.Services + .AddControllersWithViews() + .Services + .AddDataLayer(builder.Configuration) + .AddBizLayer() + .AddServiceLayer(); +``` + +If Autofac becomes necessary (multi-tenancy, conditional registrations, +named services), add it via `Autofac.Extensions.DependencyInjection`: + +```csharp +builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); +builder.Host.ConfigureContainer(cb => cb.RegisterModule(new MyModule())); +``` + +Defer this decision until Phase 2 — do not add Autofac speculatively. + +--- + +## 7. Static files and bundling + +### Legacy + +- `Content/`, `Scripts/`, `fonts/`, `favicon.ico` served via IIS static + handlers. +- `BundleConfig` produces minified bundles served from `~/bundles/...`. + +### .NET Core 6 + +- `wwwroot/` is the only directory served as static content by + `app.UseStaticFiles()`. +- Bundling: not done in Phase 1. If we need it later: + - **LibMan + WebOptimizer** (Microsoft, integrates with ASP.NET Core). + - **npm + Vite/esbuild** (heavier toolchain, full JS pipeline). +- Asset cache busting in views uses tag helpers: + ``. + +Mapping: + +| Legacy path | New path | +|------------------------------------------------|-----------------------------------| +| `SampleWebApp/Content/Site.css` | `netcore/src/SampleWebApp/wwwroot/css/site.css` | +| `SampleWebApp/Content/bootstrap.css` | `netcore/src/SampleWebApp/wwwroot/lib/bootstrap/dist/css/bootstrap.css` | +| `SampleWebApp/Scripts/jquery-*.js` | `netcore/src/SampleWebApp/wwwroot/lib/jquery/jquery.js` | +| `SampleWebApp/Scripts/site.js` | `netcore/src/SampleWebApp/wwwroot/js/site.js` | +| `SampleWebApp/fonts/*` | `netcore/src/SampleWebApp/wwwroot/lib/bootstrap/dist/fonts/*` | +| `SampleWebApp/favicon.ico` | `netcore/src/SampleWebApp/wwwroot/favicon.ico` | + +--- + +## 8. NuGet packages — Phase 1 subset + +Only the packages actually consumed by the .NET Core `HomeController` are +listed. The full migration matrix lives on the NET-10 infra branch's +`docs/PackageMigration.md` and will be re-published when NET-10 merges. + +| Legacy package | Phase 1 status | Replacement | +|-----------------------------------------|---------------------------|-------------| +| `Microsoft.AspNet.Mvc` 5.2.3 | Removed (new project SDK) | `Microsoft.NET.Sdk.Web` | +| `Microsoft.AspNet.Razor` 3.2.3 | Removed | Included in `Microsoft.NET.Sdk.Web` | +| `Microsoft.AspNet.WebPages` 3.2.3 | Removed | n/a | +| `Microsoft.AspNet.Web.Optimization` 1.1.3 | Removed | `wwwroot/` static files; LibMan if bundling needed | +| `bootstrap` 3.3.2 | Kept | Bootstrap 5 upgrade is a separate ticket | +| `jQuery` 1.10.2 | Kept | jQuery 3.x upgrade is a separate ticket | +| `Microsoft.jQuery.Unobtrusive.Validation` 3.2.3 | Kept | Library script under `wwwroot/lib/` | +| `log4net` 2.0.3 | Removed (Phase 1) | Built-in `ILogger` | +| `Autofac` / `Autofac.Mvc5` | Removed (Phase 1) | Built-in DI; revisit in Phase 2 | +| `EntityFramework` 6.1.3 | Not in Phase 1 scope | `Microsoft.EntityFrameworkCore` 6.x (Phase 2) | +| `AutoMapper` 4.2.1 | Not in Phase 1 scope | `AutoMapper` 12.x (Phase 2 — API changed) | +| `GenericServices` 1.0.9 | Not in Phase 1 scope | Replacement strategy TBD (Phase 2 / 3) | +| `Microsoft.AspNet.SignalR.*` 2.0.3 | Not in Phase 1 scope | `Microsoft.AspNetCore.SignalR` (Phase 4) | +| `Microsoft.AspNet.Identity.*` | N/A | Identity was removed from the legacy app already (commit `64c0585`). | + +--- + +## 9. Configuration anti-patterns to avoid + +- **Don't** read `IConfiguration` ad-hoc inside controllers or services. + Use typed `IOptions`. +- **Don't** call `builder.Configuration["Foo:Bar"]` in tests — use + `WebApplicationFactory.WithWebHostBuilder(b => b.ConfigureAppConfiguration(...))` + to override. +- **Don't** add new keys to `appsettings.json` without binding them to an + options class — that is how typos turn into silent missing config. +- **Don't** mix log4net and `ILogger` after Phase 1. The migrated app is + `ILogger` only. +- **Don't** persist secrets in `appsettings.json`. Use `dotnet user-secrets` + locally, environment variables in CI, and a secret store in production. diff --git a/docs/Phase1-Migration-Guide.md b/docs/Phase1-Migration-Guide.md new file mode 100644 index 0000000..912b9ce --- /dev/null +++ b/docs/Phase1-Migration-Guide.md @@ -0,0 +1,329 @@ +# Phase 1 Migration Guide — HomeController to ASP.NET Core 6 + +> Audience: any engineer working on the .NET Upgrade. Reading time: ~20 min. +> Companion docs: [Phase1-Configuration-Migration.md](Phase1-Configuration-Migration.md), +> [Phase1-Troubleshooting.md](Phase1-Troubleshooting.md), [Onboarding.md](Onboarding.md). + +This guide documents the patterns established in Phase 1 of the migration from +ASP.NET MVC 5 (.NET Framework 4.5.1) to ASP.NET Core 6. Phase 1 is intentionally +small — it migrates one controller (`HomeController`) — so subsequent phases +have a verified blueprint to follow. + +--- + +## 1. Why we are migrating + +`SampleMvcWebApp` is on .NET Framework 4.5.1, which is out of mainstream support. +The migration to .NET Core 6 unlocks: + +- Cross-platform hosting (Linux containers). +- Built-in dependency injection, configuration, logging. +- A modern test stack (xUnit + `WebApplicationFactory`). +- Long-term security patching. + +Phase 1 is the **foundation phase** — it does not touch the data layer, +`GenericServices`, or any controller other than `HomeController`. The goal is +to prove the migration mechanics on the simplest possible target. + +--- + +## 2. Migration pattern: Strangler Fig + +We are using the [Strangler Fig pattern](https://martinfowler.com/bliki/StranglerFigApplication.html). +The legacy MVC5 app and the new ASP.NET Core app **live in the same repository +and run side by side** until every controller has been migrated. + +``` +SampleMvcWebApp/ <-- repository root +├── SampleWebApp/ legacy MVC5 (.NET Framework 4.5.1) +├── BizLayer/ legacy +├── DataLayer/ legacy +├── ServiceLayer/ legacy +├── Tests/ legacy NUnit tests +│ +├── netcore/ new ASP.NET Core 6 app (Phase 1 scope) +│ ├── SampleWebApp.NetCore.sln +│ ├── src/ +│ │ └── SampleWebApp/ migrated controllers + views +│ ├── tests/ +│ │ ├── SampleWebApp.UnitTests/ +│ │ ├── SampleWebApp.IntegrationTests/ +│ │ └── SampleWebApp.PerformanceTests/ +│ ├── docs/ NET-12 / NET-13 docs +│ ├── Dockerfile +│ └── docker-compose.yml +│ +├── docs/ cross-phase documentation (this folder) +├── .github/workflows/ ci.yml, cd.yml, code-quality.yml +└── README.md +``` + +Rules during the strangle: + +- **Never** modify the legacy MVC5 app to make the Core app work. The legacy + app is a frozen reference until its last controller is migrated. +- **Never** delete legacy code in the same PR that migrates a controller — + keep removal in a separate PR so revert is trivial. +- Both apps are buildable from `master` at all times. CI must build both. +- The two apps may temporarily duplicate logic. Duplication is acceptable + during Phase 1; it will be removed in the cleanup phase. + +--- + +## 3. Target architecture (Phase 1) + +``` +netcore/src/SampleWebApp/ +├── SampleWebApp.csproj <-- Microsoft.NET.Sdk.Web, net6.0 +├── Program.cs <-- top-level statements, no Startup.cs +├── appsettings.json <-- replaces Web.config +├── appsettings.Development.json +├── appsettings.Staging.json +├── appsettings.Production.json +├── Controllers/ +│ └── HomeController.cs <-- IActionResult instead of ActionResult +├── Models/ +│ └── InternalsInfo.cs <-- cross-platform replacements +├── Views/ +│ ├── _ViewImports.cshtml <-- tag helpers + namespaces +│ ├── _ViewStart.cshtml +│ ├── Home/ +│ │ ├── Index.cshtml +│ │ ├── About.cshtml +│ │ ├── Contact.cshtml +│ │ ├── Internals.cshtml +│ │ └── CodeView.cshtml +│ └── Shared/ +│ └── _Layout.cshtml +└── wwwroot/ <-- replaces /Content, /Scripts, /fonts + ├── css/ + ├── js/ + └── lib/ +``` + +Key shape differences vs. the legacy app: + +| Legacy MVC5 location | ASP.NET Core 6 location | +|-------------------------------|---------------------------------------| +| `Global.asax.cs` | `Program.cs` (top-level statements) | +| `App_Start/RouteConfig.cs` | `app.MapControllerRoute(...)` in `Program.cs` | +| `App_Start/FilterConfig.cs` | `builder.Services.AddControllersWithViews(o => o.Filters.Add(...))` | +| `App_Start/BundleConfig.cs` | `wwwroot/` static files; LibMan/npm if bundling is needed | +| `Infrastructure/WebUiInitialise.cs` | `Program.cs` + `Extensions/*ServiceExtensions.cs` | +| `Infrastructure/AutofacDi.cs` | `builder.Services.Add*(...)` calls | +| `Web.config` `` | `appsettings.json` `"ConnectionStrings"` | +| `Web.config` `` | `appsettings.json` typed `IOptions` | +| `Log4Net.xml` + `log4net.Config` | `builder.Logging` (built-in `ILogger`) | +| `Content/*.css`, `Scripts/*.js`, `fonts/`, `favicon.ico` | `wwwroot/css/`, `wwwroot/js/`, `wwwroot/lib/`, `wwwroot/favicon.ico` | + +See [Phase1-Configuration-Migration.md](Phase1-Configuration-Migration.md) for +the full mapping. + +--- + +## 4. Migration patterns (apply to every controller in later phases) + +### 4.1 Controller signature + +```diff +- using System.Web.Mvc; +- using SampleWebApp.Models; ++ using Microsoft.AspNetCore.Mvc; ++ using SampleWebApp.Core.Models; + + namespace SampleWebApp.Controllers + { + public class HomeController : Controller + { +- public ActionResult Index() ++ public IActionResult Index() + { + return View(); + } +``` + +- `System.Web.Mvc` → `Microsoft.AspNetCore.Mvc`. +- `ActionResult` → `IActionResult` (or a specific `ViewResult` / `JsonResult` / + `IActionResult`). `IActionResult` matches every helper return on `Controller`. +- `HttpStatusCodeResult` → `StatusCode(...)` helper. +- `JsonResult` constructor → `Json(...)` helper. + +### 4.2 ViewBag, ViewData, TempData + +- `ViewBag` is unchanged on the surface — `ViewBag.Message = "..."` works. +- `ViewData` is unchanged. +- `TempData` requires `builder.Services.AddControllersWithViews()` (already + on by default) and a session provider for cross-request retention. + +### 4.3 Models with framework-specific APIs + +The legacy `InternalsInfo` model uses three APIs that need attention on +.NET Core: + +| Legacy API | Phase 1 replacement | Notes | +|------------|--------------------|-------| +| `System.Threading.ThreadPool.GetMaxThreads` | Unchanged | Works as-is on .NET Core. | +| `System.Threading.ThreadPool.GetAvailableThreads` | Unchanged | Works as-is. | +| `new PerformanceCounter("Memory", "Available MBytes", true)` | `GC.GetGCMemoryInfo().TotalAvailableMemoryBytes` | `PerformanceCounter` is Windows-only and not in .NET 6 BCL; the GC API is cross-platform. | +| `GC.GetTotalMemory(true)` | `GC.GetTotalMemory(false)` | Forcing a full collection in a metrics endpoint is a real bug — use `false`. | + +If a Windows-only metric is genuinely required, gate it behind +`OperatingSystem.IsWindows()` and use `System.Diagnostics.PerformanceCounter` +from the `System.Diagnostics.PerformanceCounter` NuGet package. + +### 4.4 Views + +Razor itself is largely compatible, but the file-level imports differ: + +```cshtml +@* Views/_ViewImports.cshtml *@ +@using SampleWebApp.Core +@using SampleWebApp.Core.Models +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +``` + +- `@Html.ActionLink("Posts", "Index", "Posts")` still works, but the idiomatic + Core form uses tag helpers: `Posts`. +- `@Url.Content("~/Content/Site.css")` → `` + served by `app.UseStaticFiles()`. +- `Html.AntiForgeryToken()` → `
` + (tag helper auto-injects the token) or `@Html.AntiForgeryToken()` still works. + +### 4.5 Static assets + +`wwwroot/` is the only path served as static files by default. + +- `Content/Site.css` → `wwwroot/css/site.css` +- `Scripts/site.js` → `wwwroot/js/site.js` +- `fonts/*` → `wwwroot/lib//fonts/*` +- `favicon.ico` → `wwwroot/favicon.ico` + +Bundling/minification: `System.Web.Optimization` has no direct .NET Core +equivalent. Phase 1 does not bundle — files are served as-is. If bundling +is required later, evaluate LibMan + WebOptimizer or move to npm + Vite. + +### 4.6 Dependency injection + +Phase 1 deliberately does **not** port Autofac. The migrated controllers in +Phase 1 have no constructor dependencies, so the built-in +`Microsoft.Extensions.DependencyInjection` container is sufficient. + +```csharp +// Program.cs +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddControllersWithViews(); +// Phase 2 will add: builder.Services.AddDbContext(...) +var app = builder.Build(); +``` + +Service registration is grouped into extension methods per layer to keep +`Program.cs` short: + +```csharp +// netcore/src/DataLayer/Startup/DataLayerServiceExtensions.cs (Phase 2) +public static IServiceCollection AddDataLayer(this IServiceCollection services, IConfiguration config) { ... } +``` + +`DiModelBinder` (action-parameter injection via Autofac) is **not** ported. +Phase 2 controllers will receive their dependencies via constructor injection. + +--- + +## 5. End-to-end migration procedure (template for future controllers) + +Use this checklist when migrating any controller in Phase 2+: + +1. **Plan the slice.** Identify the controller's actions, the views they + render, every model class touched, every service injected. +2. **Create the new controller** under `netcore/src/SampleWebApp/Controllers/` + with `Microsoft.AspNetCore.Mvc.Controller` base class. Keep route shape + identical so deep links keep working post cut-over. +3. **Port models.** Replace framework-specific APIs (see §4.3). Keep DTOs + identical on the wire where possible. +4. **Port views.** Copy the `.cshtml` files into + `netcore/src/SampleWebApp/Views//`. Update `_ViewImports` + and tag-helper usage. Update asset paths to `wwwroot/`. +5. **Port any required services.** Register them in `Program.cs` or in an + `*ServiceExtensions.cs` extension method. +6. **Add unit tests.** Follow the conventions in + [`netcore/docs/testing.md`](../netcore/docs/testing.md) (xUnit + Moq + + FluentAssertions). Aim for >90% line coverage on controller + model. +7. **Add integration tests** with `WebApplicationFactory` for every + endpoint, asserting status code, content type, and rendered markers. +8. **Run locally** — see §6. +9. **Lint** — `dotnet format --verify-no-changes netcore/SampleWebApp.NetCore.sln`. +10. **Open a PR** following [Development-Workflow.md](Development-Workflow.md). + Link the Jira ticket in the title. +11. **Self-review** against [CodeReview-Guidelines.md](CodeReview-Guidelines.md). + +--- + +## 6. Build and run + +Prerequisites: see [Onboarding.md](Onboarding.md). + +```bash +# from repo root + +# Restore + build +dotnet build netcore/SampleWebApp.NetCore.sln -c Debug + +# Run the migrated app (default http://localhost:5000, https://localhost:5001) +dotnet run --project netcore/src/SampleWebApp + +# Run all tests with coverage +dotnet test netcore/SampleWebApp.NetCore.sln \ + --collect:"XPlat Code Coverage" \ + --settings netcore/coverlet.runsettings +``` + +Visit: + +- `http://localhost:5000/` → `Home/Index` +- `http://localhost:5000/Home/About` → ViewBag string +- `http://localhost:5000/Home/Contact` +- `http://localhost:5000/Home/Internals` → live runtime metrics +- `http://localhost:5000/Home/CodeView` + +The legacy MVC5 app continues to be buildable with Visual Studio against +`SampleWebApp.sln` / IIS Express; nothing in Phase 1 changes that. + +--- + +## 7. Success criteria (Phase 1) + +These are restated from the Jira epic so reviewers can tick them off +directly from this doc: + +- [ ] `HomeController` fully functional in .NET Core 6. +- [ ] All 5 action methods (`Index`, `About`, `Contact`, `Internals`, + `CodeView`) return `200 OK` with `text/html` and render their views. +- [ ] Views render with correct styling (Bootstrap, layout, scripts). +- [ ] Unit tests passing with **>90% line coverage** on + `Controllers/HomeController.cs` and `Models/InternalsInfo.cs`. +- [ ] Integration tests passing (every endpoint hits `200 OK`; `/Home/DoesNotExist` → `404`). +- [ ] CI pipeline (`.github/workflows/ci.yml`) green on `master`. +- [ ] Performance equal to or better than the .NET Framework version on a + single-instance baseline (see `netcore/tests/SampleWebApp.PerformanceTests`). +- [ ] Migration patterns documented (this folder). +- [ ] Team trained — see [training/Phase1-Training-Agenda.md](training/Phase1-Training-Agenda.md). + +--- + +## 8. What Phase 1 deliberately does *not* do + +To keep Phase 1 small and reversible, the following are explicitly out of +scope and deferred to later phases: + +- Migrating `BlogsController`, `PostsController`, `PostsAsyncController`, + `TagsController`, `TagsAsyncController`. +- Migrating the `DataLayer` to EF Core (Phase 2). +- Replacing `GenericServices` (Phase 2 / 3). +- Replacing `AutoMapper 4.x` (Phase 2 — API changed substantially since 4.x). +- Migrating SignalR. +- Migrating `Microsoft.AspNet.Identity` (no auth is currently in use; was + deliberately removed in commit `64c0585`). +- Replacing the legacy NUnit `Tests` project — Phase 1 adds a parallel + xUnit suite under `netcore/tests/`. The legacy suite stays put. + +Touching any of the above in a Phase 1 PR will get rejected in code review. diff --git a/docs/Phase1-Troubleshooting.md b/docs/Phase1-Troubleshooting.md new file mode 100644 index 0000000..a108606 --- /dev/null +++ b/docs/Phase1-Troubleshooting.md @@ -0,0 +1,275 @@ +# Phase 1 Troubleshooting + +> Companion to [Phase1-Migration-Guide.md](Phase1-Migration-Guide.md). +> If you hit a problem not listed here, add it. Pull requests improving this +> doc count toward Phase 1 acceptance criteria. + +A catalogue of issues encountered while migrating `HomeController` to +ASP.NET Core 6, with the resolutions actually used. Symptoms are quoted +verbatim where possible so error-message searches land here. + +--- + +## Build / restore + +### "error NETSDK1045: The current .NET SDK does not support targeting .NET 6.0." + +You don't have the .NET 6 SDK installed. + +```bash +# Linux / WSL +sudo apt-get update && sudo apt-get install -y dotnet-sdk-6.0 + +# macOS +brew install --cask dotnet-sdk + +# Windows: install from https://dotnet.microsoft.com/download/dotnet/6.0 +dotnet --list-sdks # confirm 6.0.x appears +``` + +Phase 1 pins the SDK in `global.json` at the repo root. If you have multiple +SDKs installed, that file is what `dotnet` resolves against — verify with +`dotnet --version` from inside the repo. + +### "error CS0234: The type or namespace name 'Mvc' does not exist in the namespace 'System.Web'" + +You are compiling the legacy MVC5 project with the .NET 6 SDK. The legacy +`SampleWebApp.csproj` (root-level) requires .NET Framework / msbuild on +Windows. Build the legacy solution from Visual Studio with the +.NET Framework 4.5.1 targeting pack installed, or build only the new +solution from the command line: + +```bash +dotnet build netcore/SampleWebApp.NetCore.sln +``` + +### "warning MSB3277: Found conflicts between different versions of …" + +Common in mixed solutions during migration. As long as it's a warning, not +an error, the new app is fine. Pin the version in `Directory.Packages.props` +(central package management) if it bleeds across projects. + +### `dotnet restore` hangs / times out behind a proxy + +Set: + +```bash +export DOTNET_CLI_TELEMETRY_OPTOUT=1 +export NUGET_XMLDOC_MODE=skip +dotnet nuget locals all --clear +``` + +If you are on an enterprise network with a NuGet proxy, copy the corporate +`NuGet.Config` into the repo root or into `%AppData%/NuGet/NuGet.Config`. + +--- + +## Runtime + +### Static files (CSS, JS, images) return 404 + +ASP.NET Core only serves files from `wwwroot/`. Two common causes: + +1. The file is under `Content/` or `Scripts/` (legacy path). Move it under + `wwwroot/css/` or `wwwroot/js/`. See + [Phase1-Configuration-Migration.md §7](Phase1-Configuration-Migration.md#7-static-files-and-bundling). +2. `app.UseStaticFiles()` is missing from `Program.cs`. It must appear + **before** `app.UseRouting()`. + +### Views render but Bootstrap / jQuery don't load + +Two possibilities: + +- The Razor layout still references legacy bundle paths like + `Scripts/bootstrap.bundle.min.js` or `~/bundles/jquery`. Replace with + ``. +- The libraries haven't been copied under `wwwroot/lib/`. Use LibMan + (`libman restore`) or copy them from the legacy `Scripts/` and `Content/` + folders. The legacy `bootstrap` 3.3.2 ships under + `SampleWebApp/Content/bootstrap.*` and `Scripts/bootstrap.*`. + +### `/Home/Internals` throws `PlatformNotSupportedException` + +Symptom: + +``` +System.PlatformNotSupportedException: System.Diagnostics.PerformanceCounter +is not supported on this platform. +``` + +You ported `InternalsInfo` literally instead of swapping the Windows-only +`PerformanceCounter` for the cross-platform GC API. Apply: + +```csharp +var gcInfo = GC.GetGCMemoryInfo(); +AvailableMbytes = (int)(gcInfo.TotalAvailableMemoryBytes / (1024L * 1024L)); +``` + +See [Phase1-Migration-Guide.md §4.3](Phase1-Migration-Guide.md#43-models-with-framework-specific-apis). + +### Antiforgery token errors after migrating a POST action + +ASP.NET Core enforces antiforgery on every POST by default if you use +``. Either: + +- Add `@Html.AntiForgeryToken()` (still works) or use the tag-helper form, + and ensure the controller action is decorated with `[ValidateAntiForgeryToken]`. +- For genuinely cross-origin POSTs (e.g. webhooks), opt out with + `[IgnoreAntiforgeryToken]` and document why. + +### "InvalidOperationException: Endpoint Routing does not support IRouter-based actions." + +You added `app.UseMvc(routes => routes.MapRoute(...))`. That API is removed. +Use `app.MapControllerRoute(...)` after `app.UseRouting()` instead. + +--- + +## Tests + +### Integration tests fail with `Could not find a part of the path '…/Views/Home/Index.cshtml'` + +`WebApplicationFactory` runs the app from the test binary's output +directory, which doesn't contain the views by default. Add to the web app +`.csproj`: + +```xml + + true + true + true + +``` + +…and in the test project add a `ProjectReference` to the web app with +`none`. + +`Microsoft.AspNetCore.Mvc.Testing` 6.0 typically handles this automatically +when the test project references the web project — confirm the reference is +not marked `Private="true"` or `IncludeAssets="all"` aggressively. + +### `WebApplicationFactory` complains `Program is not accessible` + +Top-level statements compile `Program` as `internal`. Either add +`public partial class Program { }` at the bottom of `Program.cs`, or add to +the test project: + +```xml + + + +``` + +We prefer the `partial class Program {}` approach because it is local to the +file consumers need it. + +### `xunit.runner.visualstudio` doesn't discover any tests + +Make sure the test project has **all three** packages: + +```xml + + + +``` + +Missing `Microsoft.NET.Test.Sdk` is the usual culprit — the runner exists +but VSTest can't find a host. + +### Coverage report claims 0% for `HomeController` + +Coverlet excludes anonymous types and async state machines by default but +otherwise needs `[assembly: ExcludeFromCodeCoverage]` markers respected. +Check the `coverlet.runsettings`: + +```xml + + + + + cobertura + [SampleWebApp]* + [xunit*]*,[*Tests]* + **/Program.cs,**/*.cshtml,**/obj/**/*.cs + + + + +``` + +If `Include` is wrong (e.g. matches the test assembly), nothing in the +SUT contributes to coverage. + +--- + +## CI + +### CI builds twice — once for the legacy solution, once for `netcore` + +That is intentional during the strangle. See +[`netcore/docs/cicd.md`](../netcore/docs/cicd.md). If only one of the two +build legs runs, your branch's path filter is too tight — `ci.yml` includes +both `netcore/**` and the legacy paths. + +### CI on the legacy `.NET Framework 4.5.1` solution times out / fails on Linux + +It will. The legacy solution **cannot** build on Linux runners. Use +`windows-latest` for the legacy build leg and `ubuntu-latest` for the +`netcore` leg. + +### `dotnet format --verify-no-changes` fails in CI but the local build is clean + +`dotnet format` honours `.editorconfig` plus the .NET 6 default formatter. +Run locally: + +```bash +dotnet format netcore/SampleWebApp.NetCore.sln +git diff --stat # shows what would change +``` + +Commit the formatter's changes and push. + +--- + +## Local environment + +### Visual Studio opens but the new `netcore` solution targets are greyed out + +Install the **ASP.NET and web development** workload from the VS Installer. +Verify .NET 6 SDK appears under Tools → Options → Environment → Preview +Features → "Use previews of the .NET SDK". + +### `https://localhost:5001` shows a certificate warning in the browser + +Trust the dev cert once per machine: + +```bash +dotnet dev-certs https --trust +``` + +On Linux/WSL, this only writes the cert under your user profile. Browsers +in WSL2 still won't trust it unless launched from the same user. + +### SQL LocalDB is unreachable in WSL / Linux + +`(localdb)\mssqllocaldb` is Windows-only. For Phase 1, `HomeController` +does not open the database, so this is not blocking. For Phase 2 on +Linux dev boxes, run SQL Server in Docker: + +```bash +docker run --name sql -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=YourStrong!Passw0rd" \ + -p 1433:1433 -d mcr.microsoft.com/mssql/server:2022-latest +``` + +Update the connection string in `appsettings.Development.json` to point +to `Server=localhost;...` accordingly. + +--- + +## When this doc isn't enough + +1. Re-read [Phase1-Migration-Guide.md](Phase1-Migration-Guide.md). +2. Search closed PRs touching `netcore/` — they will have hit the same + issues. +3. Post in `#net-upgrade` Slack with the exact error message, your OS, + `dotnet --info` output, and the failing command. Add the resolution + back to this doc as a new section once solved. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..2916154 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,63 @@ +# SampleMvcWebApp — Documentation + +Reference documentation for the ASP.NET MVC 5 → ASP.NET Core 6 migration of +`SampleMvcWebApp` (Jira project [.NET Upgrade](https://cog-gtm.atlassian.net/browse/NET-1)). + +The docs in this folder cover **Phase 1: Foundation — HomeController Migration** +(Jira epic [NET-1](https://cog-gtm.atlassian.net/browse/NET-1)). Later phases +will extend this index. + +## Phase 1 documentation index + +| Doc | What it covers | Primary Phase 1 ticket | +|-----|----------------|------------------------| +| [Phase1-Migration-Guide.md](Phase1-Migration-Guide.md) | Migration overview, target architecture, repo layout, Strangler Fig approach, controller / view / model migration patterns, build & run procedures | [NET-14](https://cog-gtm.atlassian.net/browse/NET-14) | +| [Phase1-Configuration-Migration.md](Phase1-Configuration-Migration.md) | `Web.config` → `appsettings.json`, connection strings, log4net → `ILogger`, Autofac → ASP.NET Core DI, environment-specific configuration | [NET-14](https://cog-gtm.atlassian.net/browse/NET-14) | +| [Phase1-Troubleshooting.md](Phase1-Troubleshooting.md) | Common issues encountered during HomeController migration and their fixes | [NET-14](https://cog-gtm.atlassian.net/browse/NET-14) | +| [Onboarding.md](Onboarding.md) | Prerequisites, clone-to-running-app walkthrough, debug profiles, project tour for the .NET Core side | [NET-14](https://cog-gtm.atlassian.net/browse/NET-14) | +| [CodeReview-Guidelines.md](CodeReview-Guidelines.md) | PR review checklist for controller migrations in Phase 2+ | [NET-14](https://cog-gtm.atlassian.net/browse/NET-14) | +| [Development-Workflow.md](Development-Workflow.md) | Branching, commits, PRs, ticket linkage, CI expectations | [NET-14](https://cog-gtm.atlassian.net/browse/NET-14) | +| [training/Phase1-Training-Agenda.md](training/Phase1-Training-Agenda.md) | Team training session agenda, slide notes, hands-on exercises | [NET-14](https://cog-gtm.atlassian.net/browse/NET-14) | + +## Companion documentation produced by sibling tickets + +The Phase 1 work is split across multiple tickets. Where another ticket already +owns a specific area, this index links to its documentation rather than +duplicating it: + +| Area | Document | Ticket | +|------|----------|--------| +| Testing framework, coverage, integration tests | [`netcore/docs/testing.md`](../netcore/docs/testing.md) | [NET-12](https://cog-gtm.atlassian.net/browse/NET-12) | +| CI/CD, Docker, health checks, environment config | [`netcore/docs/cicd.md`](../netcore/docs/cicd.md) | [NET-13](https://cog-gtm.atlassian.net/browse/NET-13) | +| Project / EF Core scaffolding decisions | [`netcore/`](../netcore/) project structure | [NET-10](https://cog-gtm.atlassian.net/browse/NET-10) | +| HomeController, views, static assets migration | [`netcore/src/SampleWebApp/`](../netcore/src/SampleWebApp/) | [NET-11](https://cog-gtm.atlassian.net/browse/NET-11) | + +If a file referenced above does not yet exist on `master`, it is being produced +on the corresponding feature branch and will land as that ticket merges. + +## Canonical Phase 1 decisions + +These decisions are fixed for Phase 1 and govern every document in this folder. +Phase 2 may revisit them. + +| Decision | Value | Source | +|----------|-------|--------| +| Target framework | **.NET 6** | Jira epic [NET-1](https://cog-gtm.atlassian.net/browse/NET-1) | +| Migration pattern | **Strangler Fig** — legacy MVC5 app and new ASP.NET Core 6 app coexist in the same repo until cut-over | Jira epic NET-1 + Phase 1 Confluence | +| New-app layout | `netcore/` at the repo root (sln, src/, tests/, docs/, Dockerfile) | Jira ticket [NET-12](https://cog-gtm.atlassian.net/browse/NET-12) / [NET-13](https://cog-gtm.atlassian.net/browse/NET-13) | +| Legacy app layout | Unchanged — stays under `SampleWebApp/`, `BizLayer/`, `DataLayer/`, `ServiceLayer/`, `Tests/` | Repo `master` | +| First controller migrated | `HomeController` (5 actions: `Index`, `About`, `Contact`, `Internals`, `CodeView`) | Jira ticket [NET-11](https://cog-gtm.atlassian.net/browse/NET-11) | +| DI container | Built-in `Microsoft.Extensions.DependencyInjection`. Autofac may return in a later phase if Phase 2 needs its richer registration features | Phase 1 decision | +| Test stack | xUnit + Moq + FluentAssertions + `Microsoft.AspNetCore.Mvc.Testing` + Coverlet | NET-12 | +| Build / CI | GitHub Actions workflows under `.github/workflows/` (ci.yml, cd.yml, code-quality.yml) | NET-13 | + +## Confluence + +The longer-form planning artifacts live in Confluence and stay the source of +truth for Phase planning: + +- [Phase 1: Foundation - HomeController Migration](https://cog-gtm.atlassian.net/wiki/spaces/NU/pages/19562813) +- [ASP.NET MVC to .NET Core 6 Controller Migration Assessment (parent)](https://cog-gtm.atlassian.net/wiki/spaces/NU/pages/19071008) + +When changes land on `master`, mirror the relevant new content back to those +pages and link to the doc file from here. diff --git a/docs/training/Phase1-Training-Agenda.md b/docs/training/Phase1-Training-Agenda.md new file mode 100644 index 0000000..1a1b2e0 --- /dev/null +++ b/docs/training/Phase1-Training-Agenda.md @@ -0,0 +1,229 @@ +# Phase 1 Team Training — Agenda & Slide Notes + +> Target audience: every engineer who will work on the .NET Upgrade beyond +> Phase 1. Total time: **90 minutes**. Format: live session + hands-on lab. +> Pre-reading: [Onboarding.md](../Onboarding.md) and the first half of +> [Phase1-Migration-Guide.md](../Phase1-Migration-Guide.md). + +This document is the script for the Phase 1 knowledge-transfer session +mandated by Jira ticket [NET-14](https://cog-gtm.atlassian.net/browse/NET-14). +It doubles as the source-of-truth slide notes — record the session and +attach the recording link in the "Recording" section at the bottom. + +--- + +## Pre-session checklist (presenter) + +- [ ] All Phase 1 PRs (NET-10, NET-11, NET-12, NET-13) are merged or at + least demo-able on a feature branch. +- [ ] Demo machine has the .NET 6 SDK, the repo cloned, and the netcore + app running on `http://localhost:5000`. +- [ ] [`netcore/docs/testing.md`](../../netcore/docs/testing.md) and + [`netcore/docs/cicd.md`](../../netcore/docs/cicd.md) are open in + tabs for live reference. +- [ ] Slack `#net-upgrade` is open for Q&A and parking-lot questions. +- [ ] Recording started (Zoom / Teams / Granola). + +--- + +## Section 1 — Why are we migrating? (10 min) + +Speaker notes: + +- ASP.NET MVC 5 + .NET Framework 4.5.1 is out of mainstream support. +- Security patches only via paid extended support; no new features. +- Hosting cost: Windows-only IIS vs Linux containers under .NET Core. +- Strategic alignment: rest of the org is on .NET 6 / .NET 8. + +Key slide ("Why now"): + +> "The legacy stack costs us money and risk. .NET Core 6 gives us +> cross-platform hosting, modern DI/logging/configuration, and a +> supported LTS runtime until November 2024 — at which point we hop to +> .NET 8 in Phase 5." + +Audience question to ask: + +- "What's one thing the legacy app currently does that worries you about + the migration?" → write answers in the parking lot, address in Section 5. + +--- + +## Section 2 — The Strangler Fig pattern (10 min) + +Show the directory tree from +[Phase1-Migration-Guide.md §2](../Phase1-Migration-Guide.md#2-migration-pattern-strangler-fig). + +Speaker notes: + +- Both apps live in the same repo. Both build on `master`. +- We migrate one controller at a time. After each merge, the new app + is functionally a tiny subset; the legacy app stays whole. +- Cut-over is the **last** step, after every controller is migrated and + the data layer is on EF Core. Not in Phase 1. +- Why this pattern? Reversible at every step. Small PRs, easy reverts, + no big-bang weekend. + +Anti-pattern to call out: + +- "Don't refactor the legacy app to make the new app easier." Legacy is + frozen. Period. + +--- + +## Section 3 — What changed in Phase 1 (15 min, live walk-through) + +Walk the audience through the four merged tickets: + +### NET-10 — Project scaffolding +- New `netcore/` folder, solution, csproj layout. +- `Microsoft.NET.Sdk.Web` + top-level `Program.cs`. +- `global.json` pinning the .NET 6 SDK. + +### NET-11 — HomeController migration +- 5 actions ported: `Index`, `About`, `Contact`, `Internals`, `CodeView`. +- `System.Web.Mvc.ActionResult` → `Microsoft.AspNetCore.Mvc.IActionResult`. +- `InternalsInfo` ported with cross-platform GC API (no `PerformanceCounter`). +- Views moved into `netcore/src/SampleWebApp/Views/Home/`. +- Static assets relocated under `wwwroot/`. + +### NET-12 — Testing framework +- xUnit + Moq + FluentAssertions. +- `WebApplicationFactory` for integration tests. +- BenchmarkDotNet for performance tests. +- Coverage via Coverlet. Phase 1 target ≥ 90% on touched files; the + actual achievement is 100% on `HomeController` and `InternalsInfo`. + +### NET-13 — CI/CD +- Three GitHub Actions workflows: `ci.yml`, `code-quality.yml`, `cd.yml`. +- Docker support: `netcore/Dockerfile`, `netcore/docker-compose.yml`. +- Health-check endpoints: `/health`, `/health/startup`, `/health/live`. +- Per-environment configuration via `appsettings..json`. + +For each ticket, show the actual diff (`git diff --merge-base origin/master`) +in the editor. Don't read code aloud — narrate the **shape** of the change. + +--- + +## Section 4 — The patterns you will reuse (20 min) + +These are the deltas every later phase will replay. Reference +[Phase1-Migration-Guide.md §4](../Phase1-Migration-Guide.md#4-migration-patterns-apply-to-every-controller-in-later-phases). + +For each pattern, show a before/after on screen: + +1. **Controller signature** — `ActionResult` → `IActionResult`, + `System.Web.Mvc` → `Microsoft.AspNetCore.Mvc`. +2. **ViewBag / ViewData / TempData** — mostly unchanged; flag `TempData` + needing session. +3. **Cross-platform model APIs** — `PerformanceCounter` → `GC.GetGCMemoryInfo`, + `GC.GetTotalMemory(true)` → `GC.GetTotalMemory(false)`. +4. **Views** — `_ViewImports.cshtml`, tag helpers, asset paths. +5. **Static assets** — `wwwroot/` is the only served root. +6. **DI** — built-in container, constructor injection. Autofac defers to + Phase 2 if needed. +7. **Configuration** — `Web.config` → `appsettings.json`, `IOptions`. +8. **Logging** — log4net → built-in `ILogger`. +9. **Routing** — `App_Start/RouteConfig` → `app.MapControllerRoute`. +10. **Bundling** — gone in Phase 1; LibMan / npm if reintroduced later. + +Pause after each pattern: "Any questions on this one before we move on?" + +--- + +## Section 5 — Live troubleshooting drill (15 min) + +Open [Phase1-Troubleshooting.md](../Phase1-Troubleshooting.md). Pick three +real symptoms and have the audience diagnose them before revealing the +fix. Suggested picks: + +1. `/Home/Internals` throws `PlatformNotSupportedException` on Linux. +2. Integration test fails with `Program is not accessible`. +3. Static files return 404 after migrating views. + +For each, ask: "What's the first thing you'd check?" Reveal the fix from +the troubleshooting doc only after the room has guessed. + +--- + +## Section 6 — Hands-on lab (15 min) + +Each attendee runs the smoke-test checklist from +[Onboarding.md §9](../Onboarding.md#9-smoke-test-checklist-before-your-first-pr) +on their own machine while the presenter watches the room. + +Success criteria: + +- App runs on `http://localhost:5000/`. +- All five Home routes respond `200 OK`. +- `dotnet test` is green. + +Pair attendees who hit setup issues; capture every new troubleshooting +case into [Phase1-Troubleshooting.md](../Phase1-Troubleshooting.md) as a +follow-up PR. + +--- + +## Section 7 — Workflow and review (10 min) + +Walk through: + +- [Development-Workflow.md](../Development-Workflow.md) — branching, + commits, PRs, CI expectations. +- [CodeReview-Guidelines.md](../CodeReview-Guidelines.md) — the checklist + every reviewer (and author) runs against the diff. +- Bot reviewers (CodeRabbit, Devin, Graphite) — treat as human, resolve + or explicitly waive. + +Make the point: "If you do nothing else, run the author's pre-review +checklist in CodeReview-Guidelines §12 before clicking 'Ready for review'. +Reviewers are not your lint runner." + +--- + +## Section 8 — Q&A and parking lot (5 min) + +Address the parking-lot items from Section 1. Anything that can't be +answered live becomes a Slack follow-up — capture it in +`#net-upgrade` and link to the answer here in a follow-up PR. + +--- + +## Post-session checklist (presenter) + +- [ ] Upload the recording. Add the link to the **Recording** section below. +- [ ] File any "new troubleshooting case" PRs against + [Phase1-Troubleshooting.md](../Phase1-Troubleshooting.md). +- [ ] File follow-up Jira tickets for anything from the parking lot. +- [ ] Update the NET-14 ticket: tick the "Team training session conducted" + and "All team members understand migration process" acceptance + criteria boxes once attendees confirm. +- [ ] Schedule a 30-minute follow-up retrospective two weeks after the + first Phase-2-style PR lands, to confirm the patterns from Section 4 + are actually being applied. + +--- + +## Recording + +| Session | Date | Recording | Attendees | +|---------|------|-----------|-----------| +| _to fill in after the live session_ | YYYY-MM-DD | (link) | (names) | + +--- + +## Hands-on lab — quick commands + +For copy/paste during Section 6: + +```bash +git clone https://github.com/COG-GTM/SampleMvcWebApp.git +cd SampleMvcWebApp + +dotnet --version # expect 6.0.* +dotnet build netcore/SampleWebApp.NetCore.sln -c Debug # expect 0 warnings +dotnet test netcore/SampleWebApp.NetCore.sln \ + --collect:"XPlat Code Coverage" \ + --settings netcore/coverlet.runsettings # expect all green +dotnet run --project netcore/src/SampleWebApp # browse to http://localhost:5000/ +```