From d0f34f4577040655283ba054c8323eeeda7526cc Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Wed, 4 Mar 2026 10:30:00 -0800 Subject: [PATCH 1/3] Add convert-blazor-server-to-webapp skill Add a new skill to guide agents through converting pre-.NET 8 Blazor Server apps to the .NET 8+ Blazor Web App model. The skill covers: - Replacing AddServerSideBlazor/MapBlazorHub with AddRazorComponents/MapRazorComponents - Converting _Host.cshtml to an App.razor root component - Creating Routes.razor from the old App.razor - Replacing blazor.server.js with blazor.web.js - Adding UseAntiforgery middleware - Migrating CascadingAuthenticationState to a service - Optional improvements like MapStaticAssets Includes 4 eval scenarios: basic conversion, auth state migration, prerendering-disabled case, and a negative test for already-converted apps. --- .github/CODEOWNERS | 7 +- .../convert-blazor-server-to-webapp/SKILL.md | 294 ++++++++++++ .../convert-blazor-server-to-webapp/eval.yaml | 425 ++++++++++++++++++ 3 files changed, 724 insertions(+), 2 deletions(-) create mode 100644 plugins/dotnet/skills/convert-blazor-server-to-webapp/SKILL.md create mode 100644 tests/dotnet/convert-blazor-server-to-webapp/eval.yaml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5bbde028c8..c12a188ad6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -34,8 +34,8 @@ /plugins/dotnet/skills/migrate-nullable-references/ @danmoseley /tests/dotnet/migrate-nullable-references/ @danmoseley -/plugins/dotnet/skills/android-tombstone-symbolication/ @dotnet/dotnet-diag -/tests/dotnet/android-tombstone-symbolication/ @dotnet/dotnet-diag +/plugins/dotnet/skills/android-tombstone-symbolication/ @dotnet/dotnet-diag +/tests/dotnet/android-tombstone-symbolication/ @dotnet/dotnet-diag /plugins/dotnet/skills/clr-activation-debugging/ @marklio @ChrisAhna /tests/dotnet/clr-activation-debugging/ @marklio @ChrisAhna @@ -43,4 +43,7 @@ /plugins/dotnet/skills/microbenchmarking/ @dotnet/dotnet-perf-team /tests/dotnet/microbenchmarking/ @dotnet/dotnet-perf-team +/plugins/dotnet/skills/convert-blazor-server-to-webapp/ @dotnet/aspnet-blazor-eng +/tests/dotnet/convert-blazor-server-to-webapp/ @dotnet/aspnet-blazor-eng + /plugins/dotnet/agents/optimizing-dotnet-performance.agent.md @dotnet/appmodel diff --git a/plugins/dotnet/skills/convert-blazor-server-to-webapp/SKILL.md b/plugins/dotnet/skills/convert-blazor-server-to-webapp/SKILL.md new file mode 100644 index 0000000000..1b7a78b8a7 --- /dev/null +++ b/plugins/dotnet/skills/convert-blazor-server-to-webapp/SKILL.md @@ -0,0 +1,294 @@ +--- +name: convert-blazor-server-to-webapp +description: > + Guides conversion of a pre-.NET 8 Blazor Server app into a .NET 8+ Blazor Web App. + USE FOR: migrating apps that use AddServerSideBlazor and MapBlazorHub to the + AddRazorComponents/MapRazorComponents model, converting _Host.cshtml to an App.razor + root component, replacing blazor.server.js with blazor.web.js, migrating + CascadingAuthenticationState to a service, adopting new Blazor Web App features + like enhanced navigation and streaming rendering. + DO NOT USE FOR: apps that are already Blazor Web Apps (already use AddRazorComponents + and MapRazorComponents), Blazor WebAssembly or hosted Blazor WebAssembly apps + (different migration path), apps that should stay on the Blazor Server hosting + model without converting, or apps still targeting .NET Framework. +--- + +# Convert Blazor Server App to Blazor Web App + +This skill helps an agent convert a pre-.NET 8 Blazor Server app into a .NET 8+ Blazor Web App. The old hosting model uses `AddServerSideBlazor`/`MapBlazorHub` with a `_Host.cshtml` Razor Page as the entry point. The new Blazor Web App model uses `AddRazorComponents`/`MapRazorComponents` with an `App.razor` root component, enabling per-component render modes, enhanced navigation, streaming rendering, and other .NET 8+ features. The converted app uses `InteractiveServer` render mode to preserve existing interactive behavior. + +## When to Use + +- Migrating a Blazor Server app from .NET 6 or .NET 7 to .NET 8+ +- App currently uses `AddServerSideBlazor()` and `MapBlazorHub()` in `Program.cs` (or `Startup.cs`) +- App uses `Pages/_Host.cshtml` (or `_Host.razor`) as the host page with Component Tag Helpers +- Want to adopt new Blazor Web App features while keeping interactive server rendering + +## When Not to Use + +- **The app already uses `AddRazorComponents` and `MapRazorComponents`.** It is already a Blazor Web App — no conversion is needed. Stop here and tell the user the app is already using the Blazor Web App model. +- Blazor WebAssembly or hosted Blazor WebAssembly app — these have a different migration path +- The app should stay on the legacy Blazor Server hosting model (just update TFM and packages) +- The app targets .NET Framework — it must be migrated to .NET first + +## Inputs + +| Input | Required | Description | +|-------|----------|-------------| +| Blazor Server project | Yes | The `.csproj` and source files of the Blazor Server app | +| Target framework | Yes | .NET 8 or later (e.g., `net8.0`, `net9.0`, `net10.0`) | +| `Program.cs` or `Startup.cs` | Yes | The app's service and middleware configuration | +| `_Host.cshtml` location | Recommended | Usually `Pages/_Host.cshtml`; may be `_Host.razor` in some projects | + +## Workflow + +> **Commit strategy:** Commit after each logical step so the migration is reviewable and bisectable. + +### Step 1: Update the project file + +Update the `.csproj` file: + +1. Change the Target Framework Moniker (TFM) to the target version: + ```xml + net8.0 + ``` +2. Update all `Microsoft.AspNetCore.*`, `Microsoft.EntityFrameworkCore.*`, `Microsoft.Extensions.*`, and `System.Net.Http.Json` package references to the matching version. + +For non-Blazor project file changes (nullable reference types, implicit usings, HTTP/3 support, etc.), see the [general ASP.NET Core migration guide](https://learn.microsoft.com/aspnet/core/migration/70-to-80). + +### Step 2: Create `Routes.razor` from `App.razor` + +The old `App.razor` contains the `` component. This content moves to a new `Routes.razor` file so that `App.razor` can become the root HTML document component. + +1. Create a new file `Routes.razor` in the project root. +2. Move the entire content of `App.razor` into `Routes.razor`. +3. If the content is wrapped in ``, remove that wrapper (it will be replaced by a service in Step 5). +4. Leave `App.razor` empty for the next step. + +The resulting `Routes.razor` should look similar to: + +```razor + + + + + + + +

Sorry, there's nothing at this address.

+
+
+
+``` + +If the app uses `` instead of ``, keep it — it works the same way in Blazor Web Apps. + +### Step 3: Convert `_Host.cshtml` to `App.razor` + +Move the HTML shell from `Pages/_Host.cshtml` into the now-empty `App.razor` and transform it from a Razor Page into a Razor component: + +1. **Remove Razor Page directives** — delete `@page "/"`, `@using Microsoft.AspNetCore.Components.Web`, `@namespace`, and `@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers`. + +2. **Add component injection** — if using environment-conditional error UI, add: + ```razor + @inject IHostEnvironment Env + ``` + +3. **Fix the base tag** — replace `` with ``. + +4. **Replace HeadOutlet Component Tag Helper** — replace: + ```html + + ``` + with: + ```razor + + ``` + +5. **Replace App Component Tag Helper with Routes** — replace: + ```html + + ``` + with: + ```razor + + ``` + +6. **Replace Environment Tag Helpers** — replace: + ```html + + An error has occurred. This application may no longer respond until reloaded. + + + An unhandled exception has occurred. See browser dev tools for details. + + ``` + with: + ```razor + @if (Env.IsDevelopment()) + { + + An unhandled exception has occurred. See browser dev tools for details. + + } + else + { + + An error has occurred. This app may no longer respond until reloaded. + + } + ``` + +7. **Update the Blazor script** — replace: + ```html + + ``` + with: + ```html + + ``` + +8. **Add render mode import** — add to `_Imports.razor`: + ```razor + @using static Microsoft.AspNetCore.Components.Web.RenderMode + ``` + +9. **Delete `Pages/_Host.cshtml`** (and `Pages/_Host.cshtml.cs` if it exists). + +**Prerendering note:** If the original app used `render-mode="Server"` (not `"ServerPrerendered"`), prerendering was disabled. Preserve this by using `new InteractiveServerRenderMode(prerender: false)` instead of `InteractiveServer` for both `HeadOutlet` and `Routes`. + +### Step 4: Update `Program.cs` + +Make the following changes to `Program.cs` (or `Startup.cs` if the app uses the older hosting pattern): + +1. **Replace Blazor Server services** — replace: + ```csharp + builder.Services.AddServerSideBlazor(); + ``` + with: + ```csharp + builder.Services.AddRazorComponents() + .AddInteractiveServerComponents(); + ``` + + If `AddServerSideBlazor` had options configured (e.g., circuit options, hub options, detailed errors), migrate them to `AddInteractiveServerComponents`: + ```csharp + // Old: + builder.Services.AddServerSideBlazor(options => + { + options.DetailedErrors = true; + options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10); + }); + + // New: + builder.Services.AddRazorComponents() + .AddInteractiveServerComponents(options => + { + options.DetailedErrors = true; + options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10); + }); + ``` + +2. **Replace Blazor endpoint mapping** — replace: + ```csharp + app.MapBlazorHub(); + ``` + with: + ```csharp + app.MapRazorComponents() + .AddInteractiveServerRenderMode(); + ``` + + Ensure there is a `using` statement for the project's root namespace so that `App` resolves to the `App.razor` component. + +3. **Remove the fallback route** — delete: + ```csharp + app.MapFallbackToPage("/_Host"); + ``` + +4. **Remove explicit routing middleware** — delete if present: + ```csharp + app.UseRouting(); + ``` + Endpoint routing is the default and explicit `UseRouting()` is no longer needed. + +5. **Add antiforgery middleware** — add after `app.UseHttpsRedirection()` (and after `UseAuthentication`/`UseAuthorization` if present): + ```csharp + app.UseAntiforgery(); + ``` + `AddRazorComponents` registers antiforgery services automatically, but the middleware must be explicitly added to the pipeline. Without it, form POST requests fail with 400 errors. + +### Step 5: Migrate `CascadingAuthenticationState` (if present) + +If the app used `` to wrap the router: + +1. Remove the `` component wrapper (already done in Step 2 if following this workflow). +2. Add the cascading authentication state service in `Program.cs`: + ```csharp + builder.Services.AddCascadingAuthenticationState(); + ``` + +The component wrapper approach does not work across render mode boundaries in Blazor Web Apps. The service-based approach provides `Task` as a cascading value to all components regardless of render mode. + +### Step 6: Recommended improvements (optional) + +These are optional modernization improvements — not required for the conversion to work. If you suggest any of these, state explicitly that they are optional. + +- **Replace `UseStaticFiles` with `MapStaticAssets`** (.NET 9+): `app.MapStaticAssets()` provides optimized static file serving with fingerprinting, pre-compression, and content-based ETags. See [MapStaticAssets documentation](https://learn.microsoft.com/aspnet/core/fundamentals/static-files#mapstaticassets). +- **Add `@attribute [StreamRendering]`** to pages with async data loading (`OnInitializedAsync`) for improved perceived performance. The page renders its initial synchronous content immediately and re-renders when async data arrives. +- **Update CSS isolation bundle reference** if the `` tag referenced a `_Host` assembly name; ensure it matches the project's actual assembly name: ``. +- For other non-Blazor improvements (minimal hosting, HTTP/3, output caching, etc.), see the [general ASP.NET Core migration guide](https://learn.microsoft.com/aspnet/core/migration/70-to-80). + +### Step 7: Verify the migration + +1. Build the project targeting the new framework. Confirm no compile errors. +2. Search for remaining references to removed APIs: + - `AddServerSideBlazor` + - `MapBlazorHub` + - `MapFallbackToPage` + - `blazor.server.js` + - `_Host.cshtml` +3. Run the app and verify: + - Pages load and render correctly + - Interactive features work (forms, event handlers, SignalR circuits) + - Navigation between pages works + - Authentication and authorization flows work if present +4. Run existing tests. + +## Validation + +- [ ] No references to `AddServerSideBlazor` remain +- [ ] No references to `MapBlazorHub` remain +- [ ] No references to `MapFallbackToPage("/_Host")` remain +- [ ] No references to `blazor.server.js` remain +- [ ] `Pages/_Host.cshtml` has been deleted +- [ ] `App.razor` serves as the root component with a full HTML document structure +- [ ] `Routes.razor` contains the `` configuration +- [ ] `Program.cs` uses `AddRazorComponents().AddInteractiveServerComponents()` +- [ ] `Program.cs` uses `MapRazorComponents().AddInteractiveServerRenderMode()` +- [ ] `app.UseAntiforgery()` is present in the middleware pipeline +- [ ] If the app used ``, it has been replaced with `AddCascadingAuthenticationState()` service registration +- [ ] App builds and runs successfully on the target framework + +## Common Pitfalls + +| Pitfall | Solution | +|---------|----------| +| Missing `UseAntiforgery()` middleware | `AddRazorComponents` registers antiforgery services, but the middleware must be explicitly added. Place `app.UseAntiforgery()` after `UseAuthentication`/`UseAuthorization`. Without it, form POST requests fail with 400 errors. | +| Forgetting to replace `blazor.server.js` with `blazor.web.js` | The old script does not work with the Blazor Web App model. Replace all references to `_framework/blazor.server.js` with `_framework/blazor.web.js`. | +| Not removing `` wrapper | The component wrapper does not work across render mode boundaries in Blazor Web Apps. Use `builder.Services.AddCascadingAuthenticationState()` instead. | +| Leaving `app.UseRouting()` in the pipeline | Explicit `UseRouting()` is no longer needed and can interfere with endpoint routing. Remove it unless other middleware specifically requires it. | +| Using `InteractiveServer` when prerendering was disabled | If the original app used `render-mode="Server"` (not `"ServerPrerendered"`), use `new InteractiveServerRenderMode(prerender: false)` to preserve the same behavior. Using `InteractiveServer` enables prerendering which can cause unexpected issues with components that depend on JS interop during initialization. | +| Not migrating `AddServerSideBlazor` circuit options | If circuit options, hub options, or detailed error settings were configured, migrate them to `AddInteractiveServerComponents(options => { ... })`. Otherwise those settings are silently lost. | +| `UseAntiforgery()` placed before authentication middleware | The antiforgery middleware must be placed after `UseAuthentication` and `UseAuthorization`. Placing it before causes antiforgery validation to run before the user identity is established. | +| CSS isolation bundle link has wrong assembly name | If the `` tag referenced the old project name, update it to match the current assembly name. | + +## More Info + +- [Convert a Blazor Server app into a Blazor Web App](https://learn.microsoft.com/aspnet/core/migration/70-to-80#convert-a-blazor-server-app-into-a-blazor-web-app) — the official step-by-step migration guide +- [ASP.NET Core Blazor render modes](https://learn.microsoft.com/aspnet/core/blazor/components/render-modes) — understanding InteractiveServer, InteractiveWebAssembly, and InteractiveAuto +- [Migrate CascadingAuthenticationState to services](https://learn.microsoft.com/aspnet/core/migration/70-to-80#migrate-the-cascadingauthenticationstate-component-to-cascading-authentication-state-services) — replacing the component wrapper with a service +- [MapStaticAssets](https://learn.microsoft.com/aspnet/core/fundamentals/static-files#mapstaticassets) — optimized static file serving in .NET 9+ +- [Migrate from ASP.NET Core 7.0 to 8.0](https://learn.microsoft.com/aspnet/core/migration/70-to-80) — general migration guide for all ASP.NET Core changes +- [Stream rendering with Blazor](https://learn.microsoft.com/aspnet/core/blazor/components/render-modes#streaming-rendering) — `@attribute [StreamRendering]` for async data loading +- [Cascading values and render mode boundaries](https://learn.microsoft.com/aspnet/core/blazor/components/cascading-values-and-parameters#cascading-valuesparameters-and-render-mode-boundaries) — why cascading parameters do not cross render mode boundaries diff --git a/tests/dotnet/convert-blazor-server-to-webapp/eval.yaml b/tests/dotnet/convert-blazor-server-to-webapp/eval.yaml new file mode 100644 index 0000000000..3032813440 --- /dev/null +++ b/tests/dotnet/convert-blazor-server-to-webapp/eval.yaml @@ -0,0 +1,425 @@ +scenarios: + - name: "Basic Blazor Server app conversion to Blazor Web App" + prompt: | + Convert this .NET 7 Blazor Server app to a .NET 8 Blazor Web App. Provide the complete + updated files needed. The app should use the new AddRazorComponents/MapRazorComponents + APIs and no longer use AddServerSideBlazor or MapBlazorHub. + + BlazorServerApp.csproj: + ```xml + + + net7.0 + enable + enable + + + ``` + + Program.cs: + ```csharp + var builder = WebApplication.CreateBuilder(args); + + builder.Services.AddRazorPages(); + builder.Services.AddServerSideBlazor(); + + var app = builder.Build(); + + if (!app.Environment.IsDevelopment()) + { + app.UseExceptionHandler("/Error"); + app.UseHsts(); + } + + app.UseHttpsRedirection(); + app.UseStaticFiles(); + app.UseRouting(); + + app.MapBlazorHub(); + app.MapFallbackToPage("/_Host"); + + app.Run(); + ``` + + Pages/_Host.cshtml: + ```html + @page "/" + @using Microsoft.AspNetCore.Components.Web + @namespace BlazorServerApp.Pages + @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers + + + + + + + + + + + + + + + +
+ + An error has occurred. This application may no longer respond until reloaded. + + + An unhandled exception has occurred. See browser dev tools for details. + + Reload + 🗙 +
+ + + + + ``` + + App.razor: + ```razor + + + + + + + +

Sorry, there's nothing at this address.

+
+
+
+ ``` + + _Imports.razor: + ```razor + @using System.Net.Http + @using Microsoft.AspNetCore.Components.Forms + @using Microsoft.AspNetCore.Components.Routing + @using Microsoft.AspNetCore.Components.Web + @using Microsoft.JSInterop + @using BlazorServerApp + @using BlazorServerApp.Shared + ``` + assertions: + - type: output_contains + value: "AddRazorComponents" + - type: output_contains + value: "AddInteractiveServerComponents" + - type: output_contains + value: "MapRazorComponents" + - type: output_contains + value: "AddInteractiveServerRenderMode" + - type: output_contains + value: "blazor.web.js" + - type: output_contains + value: "Routes" + - type: output_contains + value: "UseAntiforgery" + - type: output_not_matches + pattern: "AddServerSideBlazor" + - type: output_not_matches + pattern: "MapBlazorHub" + - type: exit_success + rubric: + - "Replaces AddServerSideBlazor with AddRazorComponents().AddInteractiveServerComponents()" + - "Replaces MapBlazorHub with MapRazorComponents().AddInteractiveServerRenderMode()" + - "Removes MapFallbackToPage and UseRouting" + - "Adds UseAntiforgery middleware" + - "Creates a Routes.razor with the Router content from the old App.razor" + - "Converts _Host.cshtml into the new App.razor with HeadOutlet and Routes components using InteractiveServer render mode" + - "Replaces blazor.server.js with blazor.web.js" + - "Deletes Pages/_Host.cshtml" + timeout: 120 + + - name: "Blazor Server app with CascadingAuthenticationState" + prompt: | + Convert this .NET 7 Blazor Server app to a .NET 8 Blazor Web App. The app uses + authentication with CascadingAuthenticationState. Provide the complete updated files. + The app should no longer use AddServerSideBlazor or MapBlazorHub. + + Program.cs: + ```csharp + using BlazorAuthApp; + + var builder = WebApplication.CreateBuilder(args); + + builder.Services.AddRazorPages(); + builder.Services.AddServerSideBlazor(); + + var app = builder.Build(); + + if (!app.Environment.IsDevelopment()) + { + app.UseExceptionHandler("/Error"); + app.UseHsts(); + } + + app.UseHttpsRedirection(); + app.UseStaticFiles(); + app.UseRouting(); + + app.UseAuthentication(); + app.UseAuthorization(); + + app.MapControllers(); + app.MapBlazorHub(); + app.MapFallbackToPage("/_Host"); + + app.Run(); + ``` + + App.razor: + ```razor + + + + + + + + + + + + +

Sorry, there's nothing at this address.

+
+
+
+
+ ``` + + Pages/_Host.cshtml: + ```html + @page "/" + @using Microsoft.AspNetCore.Components.Web + @namespace BlazorAuthApp.Pages + @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers + + + + + + + + + + + + + +
+ + An error has occurred. This application may no longer respond until reloaded. + + + An unhandled exception has occurred. See browser dev tools for details. + + Reload + 🗙 +
+ + + + + ``` + assertions: + - type: output_contains + value: "AddCascadingAuthenticationState" + - type: output_contains + value: "AddRazorComponents" + - type: output_contains + value: "MapRazorComponents" + - type: output_contains + value: "UseAntiforgery" + - type: output_not_matches + pattern: "AddServerSideBlazor" + - type: output_not_matches + pattern: "MapBlazorHub" + - type: exit_success + rubric: + - "Removes the CascadingAuthenticationState component wrapper from App.razor/Routes.razor" + - "Adds builder.Services.AddCascadingAuthenticationState() to Program.cs" + - "Places UseAntiforgery after UseAuthentication and UseAuthorization in the middleware pipeline" + - "Keeps AuthorizeRouteView in the Routes component" + - "Does NOT switch to static rendering — preserves interactive server rendering" + timeout: 120 + + - name: "Blazor Server app with prerendering disabled" + prompt: | + Convert this .NET 6 Blazor Server app to a .NET 8 Blazor Web App. Note that + this app has prerendering disabled (render-mode="Server" not "ServerPrerendered"). + Preserve this behavior in the converted app. Provide the complete updated files. + The app should no longer use AddServerSideBlazor or MapBlazorHub. + + Program.cs: + ```csharp + var builder = WebApplication.CreateBuilder(args); + + builder.Services.AddRazorPages(); + builder.Services.AddServerSideBlazor(); + + var app = builder.Build(); + + if (!app.Environment.IsDevelopment()) + { + app.UseExceptionHandler("/Error"); + app.UseHsts(); + } + + app.UseHttpsRedirection(); + app.UseStaticFiles(); + app.UseRouting(); + + app.MapBlazorHub(); + app.MapFallbackToPage("/_Host"); + + app.Run(); + ``` + + Pages/_Host.cshtml: + ```html + @page "/" + @using Microsoft.AspNetCore.Components.Web + @namespace BlazorNoPrerenderApp.Pages + @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers + + + + + + + + + + + + + +
+ + An error has occurred. This application may no longer respond until reloaded. + + + An unhandled exception has occurred. See browser dev tools for details. + + Reload + 🗙 +
+ + + + + ``` + + App.razor: + ```razor + + + + + + +

Sorry, there's nothing at this address.

+
+
+
+ ``` + assertions: + - type: output_contains + value: "AddRazorComponents" + - type: output_contains + value: "MapRazorComponents" + - type: output_matches + pattern: "prerender.*false|InteractiveServerRenderMode\\(prerender:\\s*false\\)" + - type: output_contains + value: "blazor.web.js" + - type: output_not_matches + pattern: "AddServerSideBlazor" + - type: output_not_matches + pattern: "MapBlazorHub" + - type: exit_success + rubric: + - "Detects that the original app used render-mode=\"Server\" (not ServerPrerendered) which means prerendering was disabled" + - "Uses new InteractiveServerRenderMode(prerender: false) instead of InteractiveServer to preserve the disabled prerendering behavior" + - "Applies the prerender: false setting to both HeadOutlet and Routes components" + - "Does NOT use plain InteractiveServer which would enable prerendering and change behavior" + timeout: 120 + + - name: "App already using Blazor Web App model — no migration needed" + prompt: | + I want to convert this Blazor Server app to a Blazor Web App. Check if it needs + conversion and provide guidance. The app should not use AddServerSideBlazor or MapBlazorHub. + + Program.cs: + ```csharp + using BlazorWebApp; + + var builder = WebApplication.CreateBuilder(args); + + builder.Services.AddRazorComponents() + .AddInteractiveServerComponents(); + + var app = builder.Build(); + + if (!app.Environment.IsDevelopment()) + { + app.UseExceptionHandler("/Error"); + app.UseHsts(); + } + + app.UseHttpsRedirection(); + app.UseStaticFiles(); + app.UseAntiforgery(); + + app.MapRazorComponents() + .AddInteractiveServerRenderMode(); + + app.Run(); + ``` + + App.razor: + ```razor + @inject IHostEnvironment Env + + + + + + + + + + + + + + + ``` + + Routes.razor: + ```razor + + + + + + +

Sorry, there's nothing at this address.

+
+
+
+ ``` + assertions: + - type: output_matches + pattern: "(already|no).{0,80}(Blazor Web App|converted|migration|changes needed|changes required|using.*AddRazorComponents)" + - type: exit_success + rubric: + - "Correctly identifies that this app is already a Blazor Web App using AddRazorComponents and MapRazorComponents" + - "Does NOT suggest converting AddRazorComponents to AddServerSideBlazor or making other backwards changes" + - "Does NOT fabricate migration steps that are not needed" + - "May suggest optional improvements but clearly marks them as optional, not migration requirements" + timeout: 120 From 3c3fc9324dec4acf10758a1ab048a6517a2b8f4f Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Wed, 4 Mar 2026 14:15:45 -0800 Subject: [PATCH 2/3] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../skills/convert-blazor-server-to-webapp/SKILL.md | 2 +- .../convert-blazor-server-to-webapp/eval.yaml | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/plugins/dotnet/skills/convert-blazor-server-to-webapp/SKILL.md b/plugins/dotnet/skills/convert-blazor-server-to-webapp/SKILL.md index 1b7a78b8a7..35c0b0010c 100644 --- a/plugins/dotnet/skills/convert-blazor-server-to-webapp/SKILL.md +++ b/plugins/dotnet/skills/convert-blazor-server-to-webapp/SKILL.md @@ -212,7 +212,7 @@ Make the following changes to `Program.cs` (or `Startup.cs` if the app uses the ``` Endpoint routing is the default and explicit `UseRouting()` is no longer needed. -5. **Add antiforgery middleware** — add after `app.UseHttpsRedirection()` (and after `UseAuthentication`/`UseAuthorization` if present): +5. **Add antiforgery middleware** — add after `UseAuthentication`/`UseAuthorization` if present: ```csharp app.UseAntiforgery(); ``` diff --git a/tests/dotnet/convert-blazor-server-to-webapp/eval.yaml b/tests/dotnet/convert-blazor-server-to-webapp/eval.yaml index 3032813440..813beaaf12 100644 --- a/tests/dotnet/convert-blazor-server-to-webapp/eval.yaml +++ b/tests/dotnet/convert-blazor-server-to-webapp/eval.yaml @@ -119,9 +119,9 @@ scenarios: - type: output_contains value: "UseAntiforgery" - type: output_not_matches - pattern: "AddServerSideBlazor" + pattern: "AddServerSideBlazor\\s*\\(" - type: output_not_matches - pattern: "MapBlazorHub" + pattern: "MapBlazorHub\\s*\\(" - type: exit_success rubric: - "Replaces AddServerSideBlazor with AddRazorComponents().AddInteractiveServerComponents()" @@ -236,9 +236,9 @@ scenarios: - type: output_contains value: "UseAntiforgery" - type: output_not_matches - pattern: "AddServerSideBlazor" + pattern: "AddServerSideBlazor\\s*\\(" - type: output_not_matches - pattern: "MapBlazorHub" + pattern: "MapBlazorHub\\s*\\(" - type: exit_success rubric: - "Removes the CascadingAuthenticationState component wrapper from App.razor/Routes.razor" @@ -338,9 +338,9 @@ scenarios: - type: output_contains value: "blazor.web.js" - type: output_not_matches - pattern: "AddServerSideBlazor" + pattern: "AddServerSideBlazor\\s*\\(" - type: output_not_matches - pattern: "MapBlazorHub" + pattern: "MapBlazorHub\\s*\\(" - type: exit_success rubric: - "Detects that the original app used render-mode=\"Server\" (not ServerPrerendered) which means prerendering was disabled" @@ -417,6 +417,7 @@ scenarios: - type: output_matches pattern: "(already|no).{0,80}(Blazor Web App|converted|migration|changes needed|changes required|using.*AddRazorComponents)" - type: exit_success + expect_activation: false rubric: - "Correctly identifies that this app is already a Blazor Web App using AddRazorComponents and MapRazorComponents" - "Does NOT suggest converting AddRazorComponents to AddServerSideBlazor or making other backwards changes" From 43d21a0baaa7087c95b6ae0fdd384658d54243ef Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Thu, 5 Mar 2026 17:51:30 -0800 Subject: [PATCH 3/3] Move convert-blazor-server-to-webapp to new dotnet-aspnet plugin Move the Blazor Server migration skill out of the generic dotnet plugin and into a dedicated dotnet-aspnet plugin, per review feedback. - Create plugins/dotnet-aspnet/ with plugin.json - Move SKILL.md and eval.yaml to the new plugin structure - Register dotnet-aspnet in marketplace.json - Update CODEOWNERS with dotnet-aspnet section - Add dotnet-aspnet to README.md plugins table Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/CODEOWNERS | 7 ++++--- .github/plugin/marketplace.json | 5 +++++ README.md | 1 + plugins/dotnet-aspnet/plugin.json | 6 ++++++ .../skills/convert-blazor-server-to-webapp/SKILL.md | 0 .../convert-blazor-server-to-webapp/eval.yaml | 0 6 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 plugins/dotnet-aspnet/plugin.json rename plugins/{dotnet => dotnet-aspnet}/skills/convert-blazor-server-to-webapp/SKILL.md (100%) rename tests/{dotnet => dotnet-aspnet}/convert-blazor-server-to-webapp/eval.yaml (100%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 78de238ac5..e18e51bf0a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -49,7 +49,8 @@ /plugins/dotnet/skills/dotnet-aot-compat/ @agocke @dotnet/appmodel /tests/dotnet/dotnet-aot-compat/ @agocke @dotnet/appmodel -/plugins/dotnet/skills/convert-blazor-server-to-webapp/ @dotnet/aspnet-blazor-eng -/tests/dotnet/convert-blazor-server-to-webapp/ @dotnet/aspnet-blazor-eng - /plugins/dotnet/agents/optimizing-dotnet-performance.agent.md @dotnet/appmodel + +# dotnet-aspnet +/plugins/dotnet-aspnet/ @dotnet/aspnet-blazor-eng +/tests/dotnet-aspnet/ @dotnet/aspnet-blazor-eng diff --git a/.github/plugin/marketplace.json b/.github/plugin/marketplace.json index 877b28ab55..c3c77b404c 100644 --- a/.github/plugin/marketplace.json +++ b/.github/plugin/marketplace.json @@ -13,6 +13,11 @@ "name": "dotnet-msbuild", "source": "./plugins/dotnet-msbuild", "description": "Comprehensive MSBuild and .NET build skills: failure diagnosis, performance optimization, code quality, and modernization." + }, + { + "name": "dotnet-aspnet", + "source": "./plugins/dotnet-aspnet", + "description": "Skills for ASP.NET Core development: Blazor migration, web app modernization, and best practices." } ] } \ No newline at end of file diff --git a/README.md b/README.md index 1ca4a41ce5..f5074b5864 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ This repository contains the .NET team's curated set of core skills and custom a |--------|-------------| | [dotnet](plugins/dotnet/) | Collection of core .NET skills for handling common .NET coding tasks. | | [dotnet-msbuild](plugins/dotnet-msbuild/) | Comprehensive MSBuild and .NET build skills: failure diagnosis, performance optimization, code quality, and modernization. | +| [dotnet-aspnet](plugins/dotnet-aspnet/) | Skills for ASP.NET Core development: Blazor migration, web app modernization, and best practices. | ## Installation diff --git a/plugins/dotnet-aspnet/plugin.json b/plugins/dotnet-aspnet/plugin.json new file mode 100644 index 0000000000..aad2781a89 --- /dev/null +++ b/plugins/dotnet-aspnet/plugin.json @@ -0,0 +1,6 @@ +{ + "name": "dotnet-aspnet", + "version": "0.1.0", + "description": "Skills for ASP.NET Core development: Blazor migration, web app modernization, and best practices.", + "skills": "./skills/" +} diff --git a/plugins/dotnet/skills/convert-blazor-server-to-webapp/SKILL.md b/plugins/dotnet-aspnet/skills/convert-blazor-server-to-webapp/SKILL.md similarity index 100% rename from plugins/dotnet/skills/convert-blazor-server-to-webapp/SKILL.md rename to plugins/dotnet-aspnet/skills/convert-blazor-server-to-webapp/SKILL.md diff --git a/tests/dotnet/convert-blazor-server-to-webapp/eval.yaml b/tests/dotnet-aspnet/convert-blazor-server-to-webapp/eval.yaml similarity index 100% rename from tests/dotnet/convert-blazor-server-to-webapp/eval.yaml rename to tests/dotnet-aspnet/convert-blazor-server-to-webapp/eval.yaml