Table of Contents

BLAZMVVM0003: MvvmComponentBase Usage

Summary

Blazor components with ViewModel properties should inherit from MvvmComponentBase<TViewModel> to ensure proper View-ViewModel binding.

Description

This analyzer detects Blazor components that have a ViewModel property but don't inherit from MvvmComponentBase<TViewModel>. Using the proper base class ensures automatic ViewModel injection, lifecycle management, and proper state synchronization.

Severity

Error - This is a critical issue that will prevent proper View-ViewModel binding.

Why This Rule Exists

  • Provides automatic ViewModel injection
  • Handles ViewModel lifecycle (initialization, disposal)
  • Enables proper parameter binding between View and ViewModel
  • Synchronizes component and ViewModel state
  • Reduces boilerplate code

✅ DO: Correct Usage

using Blazing.Mvvm.Components;
using Blazing.Mvvm.ComponentModel;
using Microsoft.AspNetCore.Components;

// ✅ Correct: Inherits from MvvmComponentBase<TViewModel>
@inherits MvvmComponentBase<ProductViewModel>

<h3>@ViewModel.ProductName</h3>

@code {
    // ViewModel is automatically injected and available
}

// ViewModel
[ViewModelDefinition(Lifetime = ServiceLifetime.Transient)]
public class ProductViewModel : ViewModelBase
{
    public string ProductName { get; set; } = "Sample Product";
}

// ✅ Correct: For components needing scoped services
@inherits MvvmOwningComponentBase<OrderViewModel>

<h3>Orders</h3>

// ViewModel with DbContext
[ViewModelDefinition(Lifetime = ServiceLifetime.Transient)]
public class OrderViewModel : ViewModelBase
{
    private readonly MyDbContext _context;

    public OrderViewModel(MyDbContext context)
    {
        _context = context;
    }
}

❌ DON'T: Incorrect Usage

using Microsoft.AspNetCore.Components;

// ❌ Wrong: ComponentBase with manual ViewModel injection
@inherits ComponentBase

<h3>@ViewModel.ProductName</h3>

@code {
    [Inject]
    private ProductViewModel ViewModel { get; set; } = null!;

    // Missing lifecycle management
    // Missing automatic parameter binding
    // Missing proper disposal
}

// ❌ Wrong: Manual property initialization
@inherits ComponentBase

<h3>@_viewModel.ProductName</h3>

@code {
    private ProductViewModel _viewModel = new();

    // Not using DI
    // No lifecycle management
    // Breaks testability
}

// ❌ Wrong: Mixing View logic in ComponentBase
@inherits ComponentBase

<h3>@ProductName</h3>

@code {
    [Parameter]
    public string ProductName { get; set; }

    private async Task LoadDataAsync()
    {
        // Business logic in the View - bad MVVM!
        var data = await _httpClient.GetFromJsonAsync<Product>("api/products");
        ProductName = data.Name;
    }
}

How to Fix

Replace ComponentBase with MvvmComponentBase

// Before
@inherits ComponentBase

<h3>@ViewModel.Title</h3>

@code {
    [Inject]
    private ProductViewModel ViewModel { get; set; } = null!;
}

// After
@inherits MvvmComponentBase<ProductViewModel>

<h3>@ViewModel.Title</h3>

@code {
    // ViewModel automatically injected!
    // No need for [Inject] attribute
}

Move Business Logic to ViewModel

// Before - Logic in View
@inherits ComponentBase

<button @onclick="SaveAsync">Save</button>

@code {
    [Inject]
    private HttpClient Http { get; set; }

    private async Task SaveAsync()
    {
        // Business logic in View - bad!
        await Http.PostAsJsonAsync("api/products", product);
    }
}

// After - Logic in ViewModel
@inherits MvvmComponentBase<ProductViewModel>

<button @onclick="ViewModel.SaveCommand">Save</button>

// ViewModel
[ViewModelDefinition]
public partial class ProductViewModel : ViewModelBase
{
    private readonly HttpClient _http;

    public ProductViewModel(HttpClient http)
    {
        _http = http;
    }

    [RelayCommand]
    private async Task SaveAsync()
    {
        await _http.PostAsJsonAsync("api/products", Product);
    }

    public Product Product { get; set; } = new();
}

Benefits of MvvmComponentBase

Automatic ViewModel Injection

@inherits MvvmComponentBase<MyViewModel>

// ViewModel property is automatically available
<h3>@ViewModel.Title</h3>

Lifecycle Synchronization

public class MyViewModel : ViewModelBase
{
    protected override async Task OnInitializedAsync()
    {
        // Called when component initializes
        await LoadDataAsync();
    }
}

Parameter Binding

// View
@inherits MvvmComponentBase<ProductViewModel>

@code {
    [Parameter]
    public int ProductId { get; set; }
    // Automatically synced to ViewModel.ProductId
}

// ViewModel
public class ProductViewModel : ViewModelBase
{
    [ViewParameter]
    public int ProductId { get; set; }
}

Code Fix

This analyzer provides an automatic code fix that:

  1. Replaces ComponentBase with MvvmComponentBase<TViewModel>
  2. Removes redundant [Inject] attributes from ViewModel properties
  3. Adds necessary using directives
  • BLAZMVVM0013: MvvmOwningComponentBase Usage - For scoped service dependencies
  • BLAZMVVM0004: ViewParameter Attribute - For parameter binding
  • BLAZMVVM0001: ViewModelBase Inheritance - Ensures ViewModel base class

Additional Resources