Blazor Roslyn Analyzer Components Render Nothing Fix
Fix Blazor components generated by Roslyn analyzer that compile but render nothing. Learn why analyzers fail, switch to source generators, debug steps, and build settings for proper Blazor rendering.
Generated Blazor components compile but render nothing when created by a Roslyn analyzer — why and how to fix?
I’m generating Blazor components using a custom Roslyn analyzer. The analyzer emits fully compiled Razor components (C# classes that inherit from ComponentBase and implement BuildRenderTree). The generated components compile without errors and can be referenced and used from Razor Pages or other Blazor components. However, the generated component renders nothing at runtime — no errors, warnings, or exceptions — it simply behaves like an empty component.
If I copy the exact same generated C# code into my project as a normal .razor.cs file, it works perfectly. So the generated code is correct, but something about being generated by an analyzer causes Blazor to ignore it at runtime.
Example of a generated component:
public partial class AutoCountryForm : ComponentBase
{
[Parameter, EditorRequired]
public ItemDataSource<Country, long>? DataSource { get; set; }
protected override void BuildRenderTree(RenderTreeBuilder __builder0)
{
var seq = 0;
__builder0.OpenElement(seq++, "div");
__builder0.AddContent(seq++, "Hello from generated component");
__builder0.CloseElement();
}
}
Usage inside a Razor page:
<EditForm Model="Country!.Model" OnValidSubmit="Country.HandleValidSubmit">
<AutoCountryForm DataSource="Country" />
</EditForm>
The project builds successfully, but the
Questions:
- Why do analyzer-generated Blazor components compile but render nothing at runtime?
- How can I make components generated by a Roslyn analyzer render correctly?
- Do I need to use a Roslyn Source Generator (IIncrementalGenerator) instead of an analyzer to produce compiled components that Blazor recognizes at runtime?
- What build settings, metadata, or runtime behaviors should I check (namespaces, partial class/namespace mismatches, generated file inclusion, Razor compilation pipeline, assembly linking) to troubleshoot this?
Blazor components generated by a Roslyn analyzer often compile but render nothing because diagnostic analyzers don’t produce source in the same way/source-phase that Razor’s compilation pipeline expects; the Razor source generator or tag-mapping step may not see or register the generated types at the right time. Convert the generator into a Roslyn source generator (preferably an IIncrementalGenerator), emit public/partial classes in the correct namespace, include the generator as an analyzer/package so the generated C# is part of the same compilation pass, and then verify _Imports.razor/usings, generated-file inclusion, and linker/trimming settings so the component is actually instantiated at runtime.
Contents
- Why analyzer-generated Blazor components render nothing
- Use a Roslyn source generator (IIncrementalGenerator) so Blazor recognizes components
- Practical fixes and build settings to check (namespaces, partials, inclusion, linker)
- Debugging checklist — how to confirm what’s happening at runtime
- Minimal IIncrementalGenerator example to generate a component
- Trimming / publishing concerns for Blazor WebAssembly and AOT
- Sources
- Conclusion
Why analyzer-generated Blazor components render nothing
You can compile a C# type that inherits ComponentBase and implements BuildRenderTree and still see no rendered output. Why? Because the problem is usually not the IL, it’s when and how that IL enters the Razor/Blazor compilation pipeline.
-
Diagnostic Roslyn analyzers are designed to inspect code and report diagnostics or provide fixes; they aren’t the supported mechanism to add new source into the same compiler run that other generators (including the Razor source generator) use. If your analyzer writes files to disk or modifies the project, those files frequently become visible only to a subsequent build pass — too late for Razor’s tag-mapping and metadata emission in the current compilation.
-
The Razor toolchain (the SDK/source generator that converts .razor into C# and produces component metadata) expects component types to be present in the compilation it is operating on so it can generate correct tag helper / OpenComponent
calls and any Razor metadata. If your generated class wasn’t present when the Razor generator performed type resolution, the parent component can end up compiled against a placeholder, unexpected token, or the markup may not wire up to the produced BuildRenderTree method — which results in no output at runtime even though the generated class exists somewhere. -
There are also other subtle runtime registration steps (Razor-compiled item manifests, assembly metadata used for discovery in some scenarios) and build-time ordering guarantees that source generators satisfy but plain analyzers do not. In short: analyzers and source generators are different tools with different guarantees; source generators are the correct tool when you need to inject C# into the same compilation pass that Razor uses. See the Roslyn source generators docs for design details.
Use a Roslyn source generator (IIncrementalGenerator) so Blazor recognizes components
The straightforward fix: convert your analyzer that emits C# into a Roslyn source generator — and prefer the incremental API (IIncrementalGenerator). Why incremental? It gives better performance and more predictable integration with the overall generator pipeline.
What to do, in short:
-
Implement an IIncrementalGenerator and call context.RegisterSourceOutput(…) (or other incremental patterns) to call AddSource() with a unique hint name for each generated component file. That makes the generated C# available to the current compilation pass.
-
Emit public partial classes in the same namespace you expect consumers to use (or ensure you add the namespace to your _Imports.razor). A partial is fine, but name/namespace must match whatever the consuming markup expects.
-
Package and deliver the generator as an analyzer (NuGet with analyzer DLL in analyzers/dotnet/cs or a PackageReference with PrivateAssets=“all” and analyzer assets). That ensures the consumer sees the generator at compile time.
-
After converting to a source generator, the Razor source generator will see the added types in the same compilation and will generate the appropriate OpenComponent/metadata that instantiates the component at runtime.
Useful references: the Roslyn source-generators overview and the incremental generator guidance explain the API and recommended packaging patterns.
Practical fixes and build settings to check (namespaces, partial class/namespace mismatches, generated file inclusion, Razor compilation pipeline, assembly linking)
Checklist (work through these one by one):
-
Namespace / _Imports.razor
-
Make sure the generated component’s namespace is imported into the Razor file that uses it (via a using or _Imports.razor). A tag name only binds to a component type if the compiler can resolve the type at Razor-compile time.
-
Class visibility and signatures
-
Emit the component as public (or appropriately accessible) and ensure the class name/namespace exactly matches what Razor expects. If you emit a partial class, all partial declarations must share the same namespace.
-
Generate code as part of the compilation (IIncrementalGenerator)
-
Don’t rely on writing files to disk from an analyzer. Use AddSource in a source generator so the generated file is present in the same compilation pass as the Razor generator.
-
Inspect generated sources
-
Enable compiler-generated file emission for debugging:
-
In your consumer project file:
true
$(BaseIntermediateOutputPath)Generated
-
Then inspect obj/
/Generated to confirm your generated .g.cs exists and contains the expected BuildRenderTree. -
Inspect Razor-produced C# for the parent
-
Look in obj/
/Razor or obj/ /generated… for the parent component’s generated C# and confirm it calls OpenComponent (or similar). If the parent never references your type, the Razor compiler did not resolve it during generation. -
Confirm final assembly contains the type and method
-
Use ILSpy/dotPeek or reflection (typeof(AutoCountryForm).AssemblyQualifiedName) to confirm the generated class and BuildRenderTree method are present in the final assembly that the app loads.
-
Linker / trimming behavior (Blazor WASM or published builds)
-
If the type is only referenced by reflection or registered via metadata, the linker may prune it. For debugging, set
false or add explicit preserve rules. See the trimming guidance for Blazor WebAssembly. -
Packaging/install method
-
When you ship the generator, include it as an analyzer asset in the NuGet package so dotnet build loads it as a source generator during compilation.
Debugging checklist — how to confirm what’s happening at runtime
Short checklist with actions you can run right now:
- Does BuildRenderTree run?
- Put a breakpoint or temporary logging in the generated BuildRenderTree (or override OnInitialized and log). If you never hit a breakpoint, the component instance is not being created.
- Try DynamicComponent as a diagnostic:
- In the page/component that should render AutoCountryForm do:
@using Microsoft.AspNetCore.Components
<DynamicComponent Type=“@typeof(AutoCountryForm)” Parameters=“new Dictionary<string, object?> { ["DataSource"]=Country }” /> - If DynamicComponent renders but the
tag does not, the problem is Razor tag resolution / compile-time mapping.
- Inspect the parent’s generated C# (obj) for OpenComponent usage:
- Search the obj folder for generated code and confirm the parent actually calls OpenComponent
and not some fallback.
- Confirm the generated class exists in the runtime assembly:
- At runtime call: Console.WriteLine(typeof(AutoCountryForm).AssemblyQualifiedName) in a component and check the output. If the type isn’t found or is in a different assembly than expected, fix packaging/namespace.
- Check for duplicate types
- Multiple types with same name in different namespaces/assemblies can cause accidental type resolution mismatches. Search the build outputs.
- Validate build ordering
- Use a diagnostic msbuild log (dotnet build -bl) and view the structured log to confirm the source generator ran and produced sources during the same compile pass. If the files are created only on disk after the compile completes, that’s the issue.
- Trimming problems
- For WASM, temporarily disable trimming to see if it’s related:
false . If disabling fixes it, add a linker descriptor or DynamicDependency attributes for the generated types.
Minimal IIncrementalGenerator example to generate a component
This is a tiny example to show the pattern — adapt to your real generator logic. Put this code in a generator project and reference it as an analyzer package from the project that consumes the generated components.
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
[Generator]
public class SimpleComponentGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// This is intentionally simple: trigger generation once per compilation.
var trigger = context.CompilationProvider;
context.RegisterSourceOutput(trigger, (spc, compilation) =>
{
var source = new StringBuilder();
source.AppendLine("using Microsoft.AspNetCore.Components;");
source.AppendLine("namespace GeneratedComponents");
source.AppendLine("{");
source.AppendLine(" public partial class AutoCountryForm : ComponentBase");
source.AppendLine(" {");
source.AppendLine(" protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)");
source.AppendLine(" {");
source.AppendLine(" __builder.OpenElement(0, \"div\");");
source.AppendLine(" __builder.AddContent(1, \"Hello from generated component\");");
source.AppendLine(" __builder.CloseElement();");
source.AppendLine(" }");
source.AppendLine(" }");
source.AppendLine("}");
spc.AddSource("AutoCountryForm.g.cs", SourceText.From(source.ToString(), Encoding.UTF8));
});
}
}
Packaging note:
- Package the generator DLL into the NuGet package under analyzers/dotnet/cs so the consuming project gets it as an analyzer. In the consuming csproj use a normal PackageReference (with PrivateAssets=“all”) so the generator runs at compile time but isn’t part of runtime assets.
Trimming / publishing concerns for Blazor WebAssembly and AOT
-
If you publish for WebAssembly with trimming/AOT, the IL linker can remove types that it thinks are unused. Generated components referenced from compiled markup are usually preserved, but if your generator emits types that are only referenced by name or metadata, they may be trimmed.
-
For debugging set
false in your csproj to rule out trimming as the cause. -
If trimming is the issue, add a linker descriptor XML or use [DynamicDependency] / [Preserve] patterns to ensure the types are preserved.
-
Also check AOT differences: AOT can expose different runtime behaviors; test with a normal (non-AOT) debug build first.
Sources
- Blazor components
- Roslyn source generators — overview
- Incremental generators (IIncrementalGenerator)
- Trimming and linking for Blazor WebAssembly
Conclusion
In most cases the reason your analyzer-generated Blazor component compiles but renders nothing is build-order and pipeline: analyzers aren’t the right tool for injecting component source that Razor must see in the same compile pass. Move generation into a Roslyn source generator (IIncrementalGenerator), make sure the generated class is public/partial in the expected namespace (or imported via _Imports.razor), package the generator as an analyzer so AddSource runs during compilation, and then verify generated-file presence, razor-generated parent code, and linker/trimming settings. Once generated code is produced early enough and properly named/packaged, the component will render as expected.