Skip to content

Commit 46cb20d

Browse files
csharpfritzCopilot
andauthored
docs: Roslyn Analyzer documentation and samples (#486)
Add 8 Roslyn analyzers with code fixes for Web Forms migration patterns: - BWFC001: Missing [Parameter] attribute - BWFC002: ViewState usage - BWFC003: IsPostBack usage - BWFC004: Response.Redirect - BWFC005: Session usage - BWFC010: Required component attributes - BWFC011: Web Forms event handler signatures - BWFC012: runat=server leftovers Includes 89 tests, MkDocs documentation, and sample app content. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent be68b5e commit 46cb20d

36 files changed

Lines changed: 4758 additions & 0 deletions

docs/Migration/Analyzers.md

Lines changed: 523 additions & 0 deletions
Large diffs are not rendered by default.

docs/Migration/readme.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,4 +199,14 @@ More information and strategy for re-writing these controls can be found in the
199199

200200
Use the tool from Telerik at: https://converter.telerik.com/
201201

202+
## Follow-up: Clean Up with Roslyn Analyzers
203+
204+
After migrating your markup and code-behind, install the **BlazorWebFormsComponents.Analyzers** NuGet package to catch leftover Web Forms patterns in your C# code:
205+
206+
```shell
207+
dotnet add package BlazorWebFormsComponents.Analyzers
208+
```
209+
210+
The analyzer suite includes 8 rules that detect `ViewState` usage, `IsPostBack` checks, `Response.Redirect()` calls, `Session` access, missing `[Parameter]` attributes, and more. Each diagnostic comes with an automatic code fix. See the [Roslyn Analyzers guide](Analyzers.md) for full details.
211+
202212
## Follow-up: Move components to Razor Component Library

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ nav:
168168
- Getting started: Migration/readme.md
169169
- Known Fidelity Divergences: MigrationGuides/KnownFidelityDivergences.md
170170
- Automated Migration Guide: Migration/AutomatedMigration.md
171+
- Roslyn Analyzers: Migration/Analyzers.md
171172
- Deprecation Guidance: Migration/DeprecationGuidance.md
172173
- Migration Strategies: Migration/Strategies.md
173174
- Custom Controls: Migration/Custom-Controls.md

samples/AfterBlazorServerSide/Components/Pages/ComponentList.razor

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,11 @@
101101
<li><a href="/ControlSamples/ViewState">ViewState</a></li>
102102
</ul>
103103
</div>
104+
105+
<div class="col-md-3">
106+
<h3>Migration Tools</h3>
107+
<ul>
108+
<li><a href="/MigrationTools/Analyzers">Roslyn Analyzers</a></li>
109+
</ul>
110+
</div>
104111
</div>
Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
@page "/MigrationTools/Analyzers"
2+
3+
<PageTitle>BWFC Roslyn Analyzers</PageTitle>
4+
5+
<h2>BWFC Roslyn Analyzers</h2>
6+
<p>
7+
The <strong>BlazorWebFormsComponents.Analyzers</strong> package provides Roslyn analyzers that automatically
8+
detect leftover Web Forms patterns in your migrated Blazor code and offer one-click code fixes in your IDE.
9+
</p>
10+
11+
<hr />
12+
13+
<!-- ==================== Installation ==================== -->
14+
15+
<h3>Installation</h3>
16+
<p>Install via the .NET CLI:</p>
17+
<pre><code>dotnet add package BlazorWebFormsComponents.Analyzers</code></pre>
18+
<p>Or add a <code>PackageReference</code> to your <code>.csproj</code>:</p>
19+
<pre><code>&lt;PackageReference Include="BlazorWebFormsComponents.Analyzers" Version="*" /&gt;</code></pre>
20+
<p>
21+
Once installed, the analyzers run automatically during build and inside Visual Studio, VS Code, and any
22+
Roslyn-powered IDE. Warnings appear in the Error List and as squiggly underlines — press <kbd>Ctrl+.</kbd>
23+
to apply the suggested code fix.
24+
</p>
25+
26+
<hr />
27+
28+
<!-- ==================== Analyzer Reference ==================== -->
29+
30+
<h3>Analyzer Reference</h3>
31+
<p>
32+
The package includes <strong>8 analyzers</strong> covering the most common Web Forms migration pitfalls.
33+
Each rule has an automatic code fix.
34+
</p>
35+
36+
<table class="table table-striped table-bordered">
37+
<thead>
38+
<tr>
39+
<th>Rule</th>
40+
<th>Severity</th>
41+
<th>Description</th>
42+
</tr>
43+
</thead>
44+
<tbody>
45+
<tr><td><a href="#bwfc001">BWFC001</a></td><td>⚠️ Warning</td><td>Missing <code>[Parameter]</code> attribute on public property</td></tr>
46+
<tr><td><a href="#bwfc002">BWFC002</a></td><td>⚠️ Warning</td><td>ViewState usage detected</td></tr>
47+
<tr><td><a href="#bwfc003">BWFC003</a></td><td>⚠️ Warning</td><td>IsPostBack check detected</td></tr>
48+
<tr><td><a href="#bwfc004">BWFC004</a></td><td>⚠️ Warning</td><td>Response.Redirect usage detected</td></tr>
49+
<tr><td><a href="#bwfc005">BWFC005</a></td><td>⚠️ Warning</td><td>Session state usage detected</td></tr>
50+
<tr><td><a href="#bwfc010">BWFC010</a></td><td>ℹ️ Info</td><td>Required attribute may be missing on BWFC component</td></tr>
51+
<tr><td><a href="#bwfc011">BWFC011</a></td><td>ℹ️ Info</td><td>Web Forms event handler signature detected</td></tr>
52+
<tr><td><a href="#bwfc012">BWFC012</a></td><td>⚠️ Warning</td><td>Leftover <code>runat="server"</code> in string literal</td></tr>
53+
</tbody>
54+
</table>
55+
56+
<hr />
57+
58+
<!-- ==================== BWFC001 ==================== -->
59+
60+
<h4 id="bwfc001">BWFC001 — Missing [Parameter] Attribute</h4>
61+
<p>
62+
Public properties in <code>WebControl</code>-derived classes need <code>[Parameter]</code> to work as
63+
Blazor component parameters. The analyzer skips inherited base properties (ID, CssClass, etc.).
64+
</p>
65+
<div class="row mb-4">
66+
<div class="col-md-6">
67+
<h5>❌ Before (flagged)</h5>
68+
<pre><code>public class HelloLabel : WebControl
69+
{
70+
// ⚠️ BWFC001
71+
public string Text { get; set; }
72+
}</code></pre>
73+
</div>
74+
<div class="col-md-6">
75+
<h5>✅ After (fixed)</h5>
76+
<pre><code>public class HelloLabel : WebControl
77+
{
78+
[Parameter]
79+
public string Text { get; set; }
80+
}</code></pre>
81+
</div>
82+
</div>
83+
84+
<!-- ==================== BWFC002 ==================== -->
85+
86+
<h4 id="bwfc002">BWFC002 — ViewState Usage</h4>
87+
<p>
88+
Blazor has no ViewState. The analyzer flags <code>ViewState["key"]</code> access and replaces it with
89+
a TODO comment prompting you to use component fields or properties instead.
90+
</p>
91+
<div class="row mb-4">
92+
<div class="col-md-6">
93+
<h5>❌ Before (flagged)</h5>
94+
<pre><code>protected void Page_Load(object sender, EventArgs e)
95+
{
96+
// ⚠️ BWFC002
97+
var count = ViewState["ClickCount"];
98+
ViewState["ClickCount"] = (int)(count ?? 0) + 1;
99+
}</code></pre>
100+
</div>
101+
<div class="col-md-6">
102+
<h5>✅ After (fixed)</h5>
103+
<pre><code>// Use a component field instead:
104+
private int _clickCount;
105+
106+
protected override void OnInitialized()
107+
{
108+
_clickCount++;
109+
}</code></pre>
110+
</div>
111+
</div>
112+
113+
<!-- ==================== BWFC003 ==================== -->
114+
115+
<h4 id="bwfc003">BWFC003 — IsPostBack Check</h4>
116+
<p>
117+
Blazor doesn't have postbacks. The analyzer flags <code>IsPostBack</code> and
118+
<code>Page.IsPostBack</code> checks and suggests using Blazor lifecycle methods instead.
119+
</p>
120+
<div class="row mb-4">
121+
<div class="col-md-6">
122+
<h5>❌ Before (flagged)</h5>
123+
<pre><code>protected void Page_Load(object sender, EventArgs e)
124+
{
125+
// ⚠️ BWFC003
126+
if (!IsPostBack)
127+
{
128+
LoadInitialData();
129+
}
130+
}</code></pre>
131+
</div>
132+
<div class="col-md-6">
133+
<h5>✅ After (fixed)</h5>
134+
<pre><code>// Move first-load logic into OnInitialized:
135+
protected override void OnInitialized()
136+
{
137+
LoadInitialData();
138+
}</code></pre>
139+
</div>
140+
</div>
141+
142+
<!-- ==================== BWFC004 ==================== -->
143+
144+
<h4 id="bwfc004">BWFC004 — Response.Redirect</h4>
145+
<p>
146+
In Blazor, navigation is handled by <code>NavigationManager</code>. The analyzer flags
147+
<code>Response.Redirect()</code> calls and suggests <code>NavigationManager.NavigateTo()</code>.
148+
</p>
149+
<div class="row mb-4">
150+
<div class="col-md-6">
151+
<h5>❌ Before (flagged)</h5>
152+
<pre><code>protected void LoginButton_Click(object s, EventArgs e)
153+
{
154+
if (ValidateUser())
155+
{
156+
// ⚠️ BWFC004
157+
Response.Redirect("/Dashboard");
158+
}
159+
}</code></pre>
160+
</div>
161+
<div class="col-md-6">
162+
<h5>✅ After (fixed)</h5>
163+
<pre><code>// Inject NavigationManager:
164+
[Inject] NavigationManager Nav { get; set; }
165+
166+
private void LoginButton_Click()
167+
{
168+
if (ValidateUser())
169+
{
170+
Nav.NavigateTo("/Dashboard");
171+
}
172+
}</code></pre>
173+
</div>
174+
</div>
175+
176+
<!-- ==================== BWFC005 ==================== -->
177+
178+
<h4 id="bwfc005">BWFC005 — Session State Usage</h4>
179+
<p>
180+
Blazor Server uses scoped services or <code>ProtectedSessionStorage</code> instead of
181+
<code>Session["key"]</code>. The analyzer flags Session access and HttpContext.Current usage.
182+
</p>
183+
<div class="row mb-4">
184+
<div class="col-md-6">
185+
<h5>❌ Before (flagged)</h5>
186+
<pre><code>protected void Page_Load(object sender, EventArgs e)
187+
{
188+
// ⚠️ BWFC005
189+
var userId = Session["UserId"];
190+
var ctx = HttpContext.Current;
191+
}</code></pre>
192+
</div>
193+
<div class="col-md-6">
194+
<h5>✅ After (fixed)</h5>
195+
<pre><code>// Use a scoped service or ProtectedSessionStorage:
196+
[Inject] ProtectedSessionStorage SessionStore { get; set; }
197+
198+
protected override async Task OnAfterRenderAsync(bool first)
199+
{
200+
if (first)
201+
{
202+
var result = await SessionStore
203+
.GetAsync&lt;string&gt;("UserId");
204+
_userId = result.Success ? result.Value : null;
205+
}
206+
}</code></pre>
207+
</div>
208+
</div>
209+
210+
<!-- ==================== BWFC010 ==================== -->
211+
212+
<h4 id="bwfc010">BWFC010 — Required Attribute Missing on BWFC Component</h4>
213+
<p>
214+
Certain BWFC components require specific properties for correct rendering (e.g., <code>GridView</code>
215+
needs <code>DataSource</code>). This informational rule flags object creations that may be incomplete.
216+
</p>
217+
<div class="row mb-4">
218+
<div class="col-md-6">
219+
<h5>❌ Before (flagged)</h5>
220+
<pre><code>// ⚠️ BWFC010 — missing DataSource
221+
var grid = new GridView();
222+
223+
// ⚠️ BWFC010 — missing NavigateUrl
224+
var link = new HyperLink();</code></pre>
225+
</div>
226+
<div class="col-md-6">
227+
<h5>✅ After (fixed)</h5>
228+
<pre><code>var grid = new GridView
229+
{
230+
DataSource = products
231+
};
232+
233+
var link = new HyperLink
234+
{
235+
NavigateUrl = "/products"
236+
};</code></pre>
237+
</div>
238+
</div>
239+
240+
<!-- ==================== BWFC011 ==================== -->
241+
242+
<h4 id="bwfc011">BWFC011 — Web Forms Event Handler Signature</h4>
243+
<p>
244+
Methods with the classic <code>(object sender, EventArgs e)</code> signature indicate un-migrated
245+
event handlers. In Blazor, use <code>EventCallback</code> patterns without the sender parameter.
246+
</p>
247+
<div class="row mb-4">
248+
<div class="col-md-6">
249+
<h5>❌ Before (flagged)</h5>
250+
<pre><code>// ⚠️ BWFC011
251+
protected void BtnSave_Click(
252+
object sender, EventArgs e)
253+
{
254+
SaveData();
255+
}</code></pre>
256+
</div>
257+
<div class="col-md-6">
258+
<h5>✅ After (fixed)</h5>
259+
<pre><code>// Blazor EventCallback pattern:
260+
private void BtnSave_Click()
261+
{
262+
SaveData();
263+
}</code></pre>
264+
</div>
265+
</div>
266+
267+
<!-- ==================== BWFC012 ==================== -->
268+
269+
<h4 id="bwfc012">BWFC012 — Leftover runat="server"</h4>
270+
<p>
271+
String literals containing <code>runat="server"</code> are remnants of Web Forms markup.
272+
The code fix strips the attribute from the string automatically.
273+
</p>
274+
<div class="row mb-4">
275+
<div class="col-md-6">
276+
<h5>❌ Before (flagged)</h5>
277+
<pre><code>// ⚠️ BWFC012
278+
string html = "&lt;input type='text'"
279+
+ " runat='server' /&gt;";</code></pre>
280+
</div>
281+
<div class="col-md-6">
282+
<h5>✅ After (fixed)</h5>
283+
<pre><code>string html = "&lt;input type='text' /&gt;";</code></pre>
284+
</div>
285+
</div>
286+
287+
<hr />
288+
289+
<!-- ==================== Suppressing Warnings ==================== -->
290+
291+
<h3>Suppressing Warnings</h3>
292+
<p>If you have a valid reason to keep a flagged pattern, suppress the specific rule:</p>
293+
<pre><code>#pragma warning disable BWFC002
294+
var legacy = ViewState["KeepThis"]; // Intentionally preserved
295+
#pragma warning restore BWFC002</code></pre>
296+
<p>Or use the <code>[SuppressMessage]</code> attribute:</p>
297+
<pre><code>[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "BWFC003")]
298+
protected void Page_Load(object sender, EventArgs e) { ... }</code></pre>
299+
300+
<hr />
301+
302+
<!-- ==================== Full Example ==================== -->
303+
304+
<h3>Full Before &amp; After Example</h3>
305+
<p>
306+
See the complete before/after comparison files in the repository at
307+
<code>samples/AnalyzerExamples/</code>. These show a realistic Blazor component code-behind
308+
containing every pattern the 8 analyzers detect, followed by the fully-fixed version.
309+
</p>
310+
311+
<hr />
312+
313+
<!-- ==================== Links ==================== -->
314+
315+
<h3>Learn More</h3>
316+
<ul>
317+
<li><a href="https://github.com/FritzAndFriends/BlazorWebFormsComponents">BlazorWebFormsComponents on GitHub</a></li>
318+
<li><a href="https://www.nuget.org/packages/BlazorWebFormsComponents.Analyzers">NuGet Package</a></li>
319+
<!-- TODO: Link to full analyzer documentation once Beast creates it -->
320+
</ul>

0 commit comments

Comments
 (0)