|
| 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><PackageReference Include="BlazorWebFormsComponents.Analyzers" Version="*" /></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<string>("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 = "<input type='text'" |
| 279 | + + " runat='server' />";</code></pre> |
| 280 | + </div> |
| 281 | + <div class="col-md-6"> |
| 282 | + <h5>✅ After (fixed)</h5> |
| 283 | + <pre><code>string html = "<input type='text' />";</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 & 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