Skip to content

Commit c1529a6

Browse files
csharpfritzCopilot
andauthored
refactor: make CLI transforms shim-first preserve Web Forms API calls (#536)
WebFormsPageBase provides Request, Response, Server, Session, Cache, ClientScript, IsPostBack, and ViewState as properties. Code inheriting from it compiles AS-IS with no transforms needed for these APIs. Changes: - ResponseRedirectTransform: stop rewriting Response.Redirect() to NavigationManager.NavigateTo(). Leave calls intact ResponseShim handles ~/ and .aspx automatically. Only strip Page./this. prefix. - SessionDetectTransform: remove redundant [Inject] SessionShim and CacheShim injection already on WebFormsPageBase. - ClientScriptTransform: fix guidance to say 'works via WebFormsPageBase' instead of suggesting @Inject (wrong syntax in .cs file). - RequestFormTransform: lead guidance with 'works automatically via RequestShim on WebFormsPageBase'. - ServerShimTransform: lead guidance with 'works automatically via ServerShim on WebFormsPageBase'. All 353 CLI tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 5088de9 commit c1529a6

File tree

5 files changed

+32
-66
lines changed

5 files changed

+32
-66
lines changed

src/BlazorWebFormsComponents.Cli/Transforms/CodeBehind/ClientScriptTransform.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ public string Apply(string content, FileMetadata metadata)
9292
!content.Contains("// TODO(bwfc-general): ClientScript calls preserved"))
9393
{
9494
var shimComment = hasScriptManagerCall
95-
? "\n // TODO(bwfc-general): ClientScript calls preserved — uses ClientScriptShim + ScriptManagerShim. Inject @inject ClientScriptShim ClientScript and @inject ScriptManagerShim ScriptManager if not using BaseWebFormsComponent.\n"
96-
: "\n // TODO(bwfc-general): ClientScript calls preserved — uses ClientScriptShim. Inject @inject ClientScriptShim ClientScript if not using BaseWebFormsComponent.\n";
95+
? "\n // TODO(bwfc-general): ClientScript calls preserved — works via WebFormsPageBase (no injection needed). ScriptManagerShim may need @inject ScriptManagerShim ScriptManager for non-page classes.\n"
96+
: "\n // TODO(bwfc-general): ClientScript calls preserved — works via WebFormsPageBase (no injection needed).\n";
9797
content = ClassOpenRegex.Replace(content, "$1" + shimComment, 1);
9898
}
9999

src/BlazorWebFormsComponents.Cli/Transforms/CodeBehind/RequestFormTransform.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public string Apply(string content, FileMetadata metadata)
5555
if (!content.Contains(GuidanceMarker) && ClassOpenRegex.IsMatch(content))
5656
{
5757
var guidanceBlock = "\n " + GuidanceMarker + "\n"
58-
+ " // TODO(bwfc-form): Request.Form calls work via FormShim on WebFormsPageBase.\n"
58+
+ " // TODO(bwfc-form): Request.Form calls work automatically via RequestShim on WebFormsPageBase.\n"
5959
+ " // For interactive mode, wrap your form in <WebFormsForm OnSubmit=\"SetRequestFormData\">.\n"
6060
+ (keys.Count > 0 ? $" // Form keys found: {string.Join(", ", keys)}\n" : "")
6161
+ " // For non-page classes, inject RequestShim via DI.\n";

src/BlazorWebFormsComponents.Cli/Transforms/CodeBehind/ResponseRedirectTransform.cs

Lines changed: 25 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
namespace BlazorWebFormsComponents.Cli.Transforms.CodeBehind;
55

66
/// <summary>
7-
/// Converts Response.Redirect() calls to NavigationManager.NavigateTo() and injects
8-
/// [Inject] NavigationManager property into the class.
7+
/// Detects Response.Redirect() calls and strips Page./this. prefixes so they compile
8+
/// against ResponseShim on WebFormsPageBase. ResponseShim handles ~/ prefix stripping
9+
/// and .aspx extension removal automatically — no NavigationManager injection needed.
910
/// </summary>
1011
public class ResponseRedirectTransform : ICodeBehindTransform
1112
{
@@ -32,56 +33,41 @@ public class ResponseRedirectTransform : ICodeBehindTransform
3233
@"Response\.Redirect\(\s*([^)]+)\s*\)",
3334
RegexOptions.Compiled);
3435

35-
// For injecting [Inject] NavigationManager
36+
// Strips "Page." or "this." prefix before Response.Redirect
37+
private static readonly Regex PageOrThisPrefixRegex = new(
38+
@"(?:Page\.|this\.)(?=Response\.Redirect\s*\()",
39+
RegexOptions.Compiled);
40+
3641
private static readonly Regex ClassOpenRegex = new(
3742
@"((?:public|internal|private)\s+(?:partial\s+)?class\s+\w+[^{]*\{)",
3843
RegexOptions.Compiled);
3944

45+
private const string GuidanceMarker = "// --- Response.Redirect Migration ---";
46+
4047
public string Apply(string content, FileMetadata metadata)
4148
{
42-
var hasRedirectConversion = false;
43-
44-
// Pattern 1: literal URL with endResponse bool
45-
if (RedirectLitBoolRegex.IsMatch(content))
46-
{
47-
content = RedirectLitBoolRegex.Replace(content, m =>
48-
{
49-
var url = Regex.Replace(m.Groups[1].Value, @"^~/", "/");
50-
return $"NavigationManager.NavigateTo(\"{url}\")";
51-
});
52-
hasRedirectConversion = true;
53-
}
49+
// Count redirect calls for guidance (using existing detection regexes)
50+
var hasRedirect = RedirectLitBoolRegex.IsMatch(content)
51+
|| RedirectLitRegex.IsMatch(content)
52+
|| RedirectExprBoolRegex.IsMatch(content)
53+
|| RedirectExprRegex.IsMatch(content);
5454

55-
// Pattern 2: simple literal URL
56-
if (RedirectLitRegex.IsMatch(content))
57-
{
58-
content = RedirectLitRegex.Replace(content, m =>
59-
{
60-
var url = Regex.Replace(m.Groups[1].Value, @"^~/", "/");
61-
return $"NavigationManager.NavigateTo(\"{url}\")";
62-
});
63-
hasRedirectConversion = true;
64-
}
55+
if (!hasRedirect) return content;
6556

66-
// Pattern 3: expression with endResponse bool
67-
if (RedirectExprBoolRegex.IsMatch(content))
57+
// Strip Page.Response.Redirect → Response.Redirect and this.Response.Redirect → Response.Redirect
58+
if (PageOrThisPrefixRegex.IsMatch(content))
6859
{
69-
content = RedirectExprBoolRegex.Replace(content, "NavigationManager.NavigateTo($1) /* TODO(bwfc-navigation): Verify navigation target */");
70-
hasRedirectConversion = true;
60+
content = PageOrThisPrefixRegex.Replace(content, "");
7161
}
7262

73-
// Pattern 4: remaining expression URLs
74-
if (RedirectExprRegex.IsMatch(content))
63+
// Emit guidance (idempotent)
64+
if (!content.Contains(GuidanceMarker) && ClassOpenRegex.IsMatch(content))
7565
{
76-
content = RedirectExprRegex.Replace(content, "NavigationManager.NavigateTo($1) /* TODO(bwfc-navigation): Verify navigation target */");
77-
hasRedirectConversion = true;
78-
}
66+
var guidanceBlock = "\n " + GuidanceMarker + "\n"
67+
+ " // TODO(bwfc-navigation): Response.Redirect() works via ResponseShim on WebFormsPageBase. Handles ~/ and .aspx automatically.\n"
68+
+ " // For non-page classes, inject ResponseShim via DI.\n";
7969

80-
// Inject [Inject] NavigationManager if conversions were made
81-
if (hasRedirectConversion && ClassOpenRegex.IsMatch(content))
82-
{
83-
var injectLine = "\n [Inject] private NavigationManager NavigationManager { get; set; } // TODO(bwfc-navigation): Add @using Microsoft.AspNetCore.Components to _Imports.razor if needed\n";
84-
content = ClassOpenRegex.Replace(content, "$1" + injectLine, 1);
70+
content = ClassOpenRegex.Replace(content, "$1" + guidanceBlock, 1);
8571
}
8672

8773
return content;

src/BlazorWebFormsComponents.Cli/Transforms/CodeBehind/ServerShimTransform.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public string Apply(string content, FileMetadata metadata)
5858
}
5959

6060
var guidanceBlock = "\n " + GuidanceMarker + "\n"
61-
+ " // TODO(bwfc-server): Server.* calls work via ServerShim on WebFormsPageBase.\n"
61+
+ " // TODO(bwfc-server): Server.* calls work automatically via ServerShim on WebFormsPageBase.\n"
6262
+ $" // Methods found: {string.Join(", ", methods)}\n"
6363
+ " // For non-page classes, inject ServerShim via DI.\n"
6464
+ (hasMapPath ? " // MapPath(\"~/path\") maps to IWebHostEnvironment.WebRootPath.\n" : "");

src/BlazorWebFormsComponents.Cli/Transforms/CodeBehind/SessionDetectTransform.cs

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public string Apply(string content, FileMetadata metadata)
6262
}
6363

6464
var sessionBlock = SessionGuidanceMarker + "\n"
65-
+ "// TODO(bwfc-session-state): SessionShim auto-wired via [Inject] — Session[\"key\"] calls compile against the shim's indexer.\n"
65+
+ "// TODO(bwfc-session-state): Session[\"key\"] calls work automatically via SessionShim on WebFormsPageBase.\n"
6666
+ (sessionKeys.Count > 0 ? $"// Session keys found: {string.Join(", ", sessionKeys)}\n" : "")
6767
+ "// Options for long-term replacement:\n"
6868
+ "// (1) ProtectedSessionStorage (Blazor Server) — persists across circuits\n"
@@ -91,7 +91,7 @@ public string Apply(string content, FileMetadata metadata)
9191
}
9292

9393
var cacheBlock = CacheGuidanceMarker + "\n"
94-
+ "// TODO(bwfc-session-state): CacheShim auto-wired via [Inject] — Cache[\"key\"] calls compile against the shim's indexer.\n"
94+
+ "// TODO(bwfc-session-state): Cache[\"key\"] calls work automatically via CacheShim on WebFormsPageBase.\n"
9595
+ (cacheKeys.Count > 0 ? $"// Cache keys found: {string.Join(", ", cacheKeys)}\n" : "")
9696
+ "// CacheShim wraps IMemoryCache — items are per-server, not distributed.\n"
9797
+ "// For distributed caching, consider IDistributedCache.\n\n";
@@ -122,27 +122,7 @@ public string Apply(string content, FileMetadata metadata)
122122
}
123123
}
124124

125-
// Inject [Inject] properties after class opening brace (idempotent)
126-
if (ClassOpenRegex.IsMatch(content))
127-
{
128-
var injectLines = "";
129-
130-
if (hasSession && !content.Contains("[Inject] private SessionShim Session"))
131-
{
132-
injectLines += "\n [Inject] private SessionShim Session { get; set; }";
133-
}
134-
135-
if (hasCache && !content.Contains("[Inject] private CacheShim Cache"))
136-
{
137-
injectLines += "\n [Inject] private CacheShim Cache { get; set; }";
138-
}
139-
140-
if (!string.IsNullOrEmpty(injectLines))
141-
{
142-
injectLines += "\n";
143-
content = ClassOpenRegex.Replace(content, "$1" + injectLines, 1);
144-
}
145-
}
125+
// Session and Cache are provided by WebFormsPageBase — no [Inject] needed.
146126

147127
return content;
148128
}

0 commit comments

Comments
 (0)