Blazor Component Created: 01 Feb 2026 Updated: 01 Feb 2026

Understanding Code-Behind Approaches in Blazor

One of the most debated topics among Blazor developers is where to place component logic. Coming from different backgrounds—ASP.NET MVC, Web Forms, or even Angular—developers bring varying perspectives on code organization. This article explores four distinct approaches to organizing code in Blazor components, helping you make informed decisions for your projects.

The Four Approaches

1. Inline Code with @code Directive

The most straightforward approach is writing code directly in the .razor file using the @code directive.

@page "/products"

<h3>Products</h3>

@if (products == null)
{
<p>Loading...</p>
}
else
{
@foreach (var product in products)
{
<p>@product.Name - $@product.Price</p>
}
}

@code {
private Product[]? products;

protected override async Task OnInitializedAsync()
{
await Task.Delay(500);
products = await LoadProductsAsync();
}

private async Task<Product[]> LoadProductsAsync()
{
// Load products logic
return Array.Empty<Product>();
}

private class Product
{
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
}
}

Pros:

  1. No context switching between files
  2. Everything is visible in one place
  3. Faster development for simple components
  4. Easier to understand component flow

Cons:

  1. Can become cluttered with complex logic
  2. Mixing presentation and logic concerns
  3. May be harder to test in isolation

Best for: Simple to medium complexity components where logic is straightforward and directly related to the UI.

2. Partial Class (Modern Code-Behind)

This approach separates code into a .razor.cs file with the same name as the razor component, using the partial keyword.

ProductList.razor:

@page "/products"

<h3>Products</h3>

@if (products == null)
{
<p>Loading...</p>
}
else
{
<p>Total Value: $@TotalValue</p>
@foreach (var product in products)
{
<p>@product.Name - $@product.Price</p>
}
}

ProductList.razor.cs:

namespace MyApp.Pages;

public partial class ProductList
{
private Product[]? products;

protected override async Task OnInitializedAsync()
{
await Task.Delay(500);
products = await LoadProductsAsync();
}

private decimal TotalValue =>
products?.Sum(p => p.Price) ?? 0;

private async Task<Product[]> LoadProductsAsync()
{
// Load products logic
return Array.Empty<Product>();
}

private class Product
{
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
}
}

Pros:

  1. Clean separation of concerns
  2. Razor file focuses on presentation
  3. No inheritance required
  4. Better organization for complex components
  5. Easier to navigate in large projects

Cons:

  1. Need to switch between files
  2. Slightly more setup required
  3. Fields must be accessible to the razor file (protected/public/internal)

Best for: Medium to complex components where separating logic improves readability and maintainability.

3. Inherited Base Class

This older approach involves creating a base class that inherits from ComponentBase and using the @inherits directive in the razor file.

ProductList.razor:

@page "/products"
@inherits ProductListBase

<h3>Products</h3>

@if (Products == null)
{
<p>Loading...</p>
}
else
{
<button @onclick="RefreshProducts">Refresh</button>
@foreach (var product in Products)
{
<p>@product.Name - $@product.Price</p>
}
}

ProductListBase.cs:

using Microsoft.AspNetCore.Components;

namespace MyApp.Pages;

public class ProductListBase : ComponentBase
{
protected Product[]? Products { get; set; }

protected override async Task OnInitializedAsync()
{
await LoadProductsAsync();
}

protected async Task RefreshProducts()
{
Products = null;
StateHasChanged();
await LoadProductsAsync();
}

private async Task LoadProductsAsync()
{
await Task.Delay(500);
Products = Array.Empty<Product>();
}

protected class Product
{
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
}
}

Pros:

  1. Can share common functionality across multiple components
  2. Useful for creating reusable component hierarchies
  3. Clear inheritance chain

Cons:

  1. All fields must be protected or public (not private)
  2. More complex than partial classes
  3. Can only inherit from one base class
  4. Older pattern, less commonly used today

Best for: Sharing common logic across multiple related components or when building component libraries with shared base functionality.

4. Code-Only Components

The most advanced approach involves writing components entirely in C# without a .razor file, using RenderTreeBuilder.

ProductList.cs:

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;

namespace MyApp.Pages;

[Route("/products")]
public class ProductList : ComponentBase
{
private Product[]? products;

protected override async Task OnInitializedAsync()
{
await Task.Delay(500);
products = Array.Empty<Product>();
}

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
var seq = 0;

builder.OpenElement(seq++, "h3");
builder.AddContent(seq++, "Products");
builder.CloseElement();

if (products == null)
{
builder.OpenElement(seq++, "p");
builder.AddContent(seq++, "Loading...");
builder.CloseElement();
}
else
{
foreach (var product in products)
{
builder.OpenElement(seq++, "p");
builder.AddContent(seq++, $"{product.Name} - ${product.Price}");
builder.CloseElement();
}
}
}

private class Product
{
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
}
}

Pros:

  1. Maximum control over rendering
  2. Can be more performant in specific scenarios
  3. Useful for dynamic component generation
  4. No razor compilation needed

Cons:

  1. Much more verbose
  2. Harder to read and maintain
  3. No syntax highlighting for HTML
  4. Difficult to visualize the UI structure
  5. Sequence numbers must be managed carefully

Best for: Advanced scenarios requiring precise render control, dynamic component generation, or component libraries where razor syntax isn't available.

Use Inline Code When:

  1. Building simple pages with minimal logic
  2. Rapid prototyping
  3. Logic is tightly coupled to the UI
  4. Components are under 200 lines total
  5. The team prefers fewer files

Use Partial Classes When:

  1. Components have substantial business logic
  2. You want clean separation of concerns
  3. Testing logic separately from UI
  4. Multiple developers work on the same component
  5. Components exceed 200 lines

Use Inherited Classes When:

  1. Multiple components share common functionality
  2. Building a component library
  3. Creating a family of related components
  4. Need to enforce common behavior across components

Use Code-Only When:

  1. Building reusable component libraries
  2. Dynamic UI generation based on metadata
  3. Performance-critical scenarios requiring render control
  4. Creating wrapper components
  5. Working without razor tooling

Real-World Recommendations

After years of Blazor development, here are practical recommendations:

1. Start with Inline Code Begin with @code blocks. Many components remain simple and don't need separation. Don't over-engineer early.

2. Refactor to Partial Classes as Needed When a component grows beyond ~150-200 lines or becomes complex, refactor to a partial class. This is a seamless transition.

3. Use Inheritance Sparingly Only create base classes when you have proven shared logic across 3+ components. Premature abstraction adds complexity.

4. Avoid Code-Only Unless Necessary Reserve this approach for specialized scenarios. The verbosity and maintenance burden usually outweigh the benefits.

5. Team Consistency Matters Most Pick an approach and stick with it. Consistency across your codebase is more valuable than finding the "perfect" pattern.

Migration Between Approaches

From Inline to Partial Class

Before (ProductList.razor):

@page "/products"

<h3>Products</h3>

@code {
private List<Product> products = new();
protected override async Task OnInitializedAsync()
{
products = await GetProductsAsync();
}
private async Task<List<Product>> GetProductsAsync()
{
// Logic here
return new List<Product>();
}
}

After (ProductList.razor):

@page "/products"

<h3>Products</h3>

After (ProductList.razor.cs):

public partial class ProductList
{
private List<Product> products = new();
protected override async Task OnInitializedAsync()
{
products = await GetProductsAsync();
}
private async Task<List<Product>> GetProductsAsync()
{
// Logic here
return new List<Product>();
}
}

The transition is seamless—just move the code and add partial to the class declaration.

Conclusion

Blazor's flexibility in code organization is a strength, not a weakness. Each approach has valid use cases:

  1. Inline code for simplicity and rapid development
  2. Partial classes for clean separation and maintainability
  3. Inherited classes for shared functionality
  4. Code-only for advanced scenarios

The key is understanding your project's needs, team preferences, and maintaining consistency. Start simple with inline code, and refactor to more complex patterns only when the benefits justify the additional complexity.

Remember: the best code organization is the one that makes your team most productive and your codebase most maintainable. Don't let perfect be the enemy of good.


Share this lesson: