Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Create and register new CLI migration transforms for the webforms-to-blazor pipeline. Covers IMarkupTransform and ICodeBehindTransform interfaces, transform ordering, dual-registration in Program.cs and TestHelpers.cs, testing patterns, and when to use a transform vs. a semantic pattern. Use when adding a new markup or code-behind transform, debugging transform output, or understanding transform execution order.
.claude/skills/fritzandfriends-cli-transform-authoring/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-17 | ✗→✓ | ▲ Improved | 1% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 21% | 0% |
| case-02 | ✗→✓ | ▲ Improved | -7% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 21% | 0% |
| case-08 | ✗→✓ | ▲ Improved | -11% | 0% |
This skill covers creating new transforms for the webforms-to-blazor CLI migration pipeline.
The migration CLI uses two transform interfaces that run in sequence during file conversion:
IMarkupTransform) — Convert .aspx/.ascx/.master markup to .razor syntaxICodeBehindTransform) — Convert .aspx.cs code-behind to .razor.csBoth interfaces share the same contract:
csharppublic interface IMarkupTransform // (or ICodeBehindTransform) { string Name { get; } int Order { get; } string Apply(string content, FileMetadata metadata); }
Name — Human-readable identifier (used in logs and diagnostics)Order — Execution sequence (ascending). Lower numbers run first.Apply — Takes current content + file metadata, returns transformed contentMigrationPipeline runs transforms in this order:
Order ascending) — all run on .razor contentOrder ascending) — all run on .razor.cs contentSemanticPatternCatalog) — page-level rewrites after all transformsEvery transform receives FileMetadata which carries context about the file being processed:
OriginalPath — Source .aspx pathOutputPath — Target .razor pathPageDirectives — Parsed directive info (CodeBehind, Inherits, MasterPageFile)MarkupContent — Can be set by code-behind transforms to modify the markup fileCodeBehindContent — The code-behind sourceIsUserControl / IsMasterPage — File type flagssrc/BlazorWebFormsComponents.Cli/Transforms/Markup/MyNewTransform.cs
— or —
src/BlazorWebFormsComponents.Cli/Transforms/CodeBehind/MyNewTransform.cscsharpusing BlazorWebFormsComponents.Cli.Pipeline; namespace BlazorWebFormsComponents.Cli.Transforms.Markup; // or .CodeBehind public class MyNewTransform : IMarkupTransform // or ICodeBehindTransform { public string Name => "MyNewTransform"; public int Order => 500; // Choose based on dependencies public string Apply(string content, FileMetadata metadata) { // Return content unchanged if this transform doesn't apply if (!content.Contains("pattern-to-match")) return content; // Apply transformation return content.Replace("old-pattern", "new-pattern"); } }
⚠️ CRITICAL: Every transform must be registered in TWO places.
src/BlazorWebFormsComponents.Cli/Program.cs (runtime DI):
csharpservices.AddSingleton<IMarkupTransform, MyNewTransform>(); // — or — services.AddSingleton<ICodeBehindTransform, MyNewTransform>();
tests/BlazorWebFormsComponents.Cli.Tests/TestHelpers.cs (test pipeline):
csharpvar markupTransforms = new List<IMarkupTransform> { // ... existing transforms ... new MyNewTransform(), }; // — or for code-behind — var codeBehindTransforms = new List<ICodeBehindTransform> { // ... existing transforms ... new MyNewTransform(), };
Create a test file at tests/BlazorWebFormsComponents.Cli.Tests/MyNewTransformTests.cs:
csharpusing BlazorWebFormsComponents.Cli.Pipeline; using BlazorWebFormsComponents.Cli.Transforms.Markup; using Xunit; namespace BlazorWebFormsComponents.Cli.Tests; public class MyNewTransformTests { private readonly MyNewTransform _transform = new(); [Fact] public void Apply_WithMatchingPattern_TransformsCorrectly() { var input = "<asp:CustomControl runat=\"server\" />"; var metadata = new FileMetadata { OriginalPath = "Test.aspx" }; var result = _transform.Apply(input, metadata); Assert.Contains("expected-output", result); Assert.DoesNotContain("asp:", result); } [Fact] public void Apply_WithoutMatchingPattern_ReturnsUnchanged() { var input = "<div>No Web Forms here</div>"; var metadata = new FileMetadata { OriginalPath = "Test.aspx" }; var result = _transform.Apply(input, metadata); Assert.Equal(input, result); } }
bashdotnet test tests\BlazorWebFormsComponents.Cli.Tests --nologo
| Order Range | Category | Examples | |-------------|----------|----------| | 0–99 | Directive stripping | PageDirectiveTransform, RegisterDirectiveTransform | | 100–199 | Structural transforms | MasterPageTransform, ContentWrapperTransform | | 200–299 | Prefix/attribute removal | AspPrefixTransform, AttributeStripTransform | | 300–399 | Expression/binding rewrites | ExpressionTransform, DataBindingAttributeTransform | | 400–499 | Template/column processing | TemplateFieldChildComponentsTransform, GridViewColumnItemTypeTransform | | 500–599 | Event/form wiring | EventWiringTransform, FormWrapperTransform | | 600–699 | URL/reference cleanup | UrlReferenceTransform, ScriptManagerStripTransform | | 700+ | Post-processing | FormAntiforgeryPostProcessor, ValidatorGenericTypeTransform |
Key ordering dependencies:
AspPrefixTransform must run before AttributeStripTransform (strip asp: before removing runat)TemplateContextTransform must run before TemplateFieldChildComponentsTransformSelectMethodTransform must run before EventWiringTransform| Use a Transform when... | Use a Semantic Pattern when... | |--------------------------|-------------------------------| | Converting a specific Web Forms tag/attribute | Rewriting page-level structure | | Change is mechanical (regex/string) | Change requires understanding page intent | | Applies to ALL files universally | Applies only to specific page types (query, detail, action) | | No context about surrounding markup needed | Needs to understand the full page layout | | Example: stripping runat="server" | Example: converting a search form + GridView into a query page |
src/BlazorWebFormsComponents.Cli/Transforms/Markup/src/BlazorWebFormsComponents.Cli/Transforms/Directives/src/BlazorWebFormsComponents.Cli/Transforms/CodeBehind/IMarkupTransform or ICodeBehindTransformOrder is set appropriately (check ordering dependencies)Program.cs DI containerTestHelpers.cs test pipelinedotnet test tests\BlazorWebFormsComponents.Cli.Tests passesOther measured skills in the registry, with their headline benchmark lift.