Skip to content

excubo-ag/Generators.Blazor

Repository files navigation

Excubo.Generators.Blazor

Nuget Nuget GitHub

This project improves the performance of Blazor components using source generators and provides helpful diagnostics.

Installation

Nuget

Excubo.Generators.Blazor is distributed via nuget.org. Nuget

Package Manager:

Install-Package Excubo.Generators.Blazor

.NET Cli:

dotnet add package Excubo.Generators.Blazor

Package Reference

<PackageReference Include="Excubo.Generators.Blazor" />

Project settings

Since roslyn does not support roslyn source generator dependencies, the razor source generator is incompatible with this project. You need to opt-out using the setting

<UseRazorSourceGenerator>false</UseRazorSourceGenerator>

in your project file.

Example:

<Project Sdk="Microsoft.NET.Sdk.Razor">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <UseRazorSourceGenerator>false</UseRazorSourceGenerator>
  </PropertyGroup>
  ...
</Project>

SetParametersAsync Source Generator

How does it work

Blazor uses C#-Reflection to handle the setting of component's [Parameter]s which is slower than a compile-time approach. The SetParametersAsync generator overrides the default reflection-based implementation of Task SetParametersAsync(ParameterView parameters) following this recommendation by MS.

This increases the performance of setting parameters of components up to 6x.

How to enable

Add @attribute [Excubo.Generators.Blazor.GenerateSetParametersAsync] to your _Imports.razor file. This enables the source generator on all components. As sometimes you might want to override the method yourself, you can opt-out of the source generator by adding @attribute [Excubo.Generators.Blazor.DoNotGenerateSetParametersAsync] to a component.

You can use [GenerateSetParametersAsync(RequireExactMatch = true)], if you do not require parameters to match when they differ in case.

Implementation details

If you write the code

using Excubo.Generators.Blazor;
using Microsoft.AspNetCore.Components;

namespace IntegrationTest
{
    [GenerateSetParametersAsync]
    public partial class Component : ComponentBase
    {
        [Parameter] public string Parameter1 { get; set; }
        // usually you have more parameters
    }
}

the source generator generates

using Microsoft.AspNetCore.Components;
using System.Threading.Tasks;
using System;

namespace IntegrationTest
{
    public partial class Component
    {
        public override Task SetParametersAsync(ParameterView parameters)
        {
            foreach (var parameter in parameters)
            {
                BlazorImplementation__WriteSingleParameter(parameter.Name, parameter.Value);
            }

            // Run the normal lifecycle methods, but without assigning parameters again
            return base.SetParametersAsync(ParameterView.Empty);
        }

        private void BlazorImplementation__WriteSingleParameter(string name, object value)
        {
            switch (name) // parameter properties are actually case insensitive. This is ignored here for performance, but handled later for correctness
            {
                case "Parameter1":
                    this.Parameter1 = (string)value;
                    break;
                // more parameters would create more cases
                default:
                {
                    switch (name.ToLowerInvariant()) // parameter properties are actually case insensitive.
                    {
                        case "parameter1":
                            this.Parameter1 = (string)value;
                            break;
                        // more parameters would create more cases
                        default:
                            throw new ArgumentException($"Unknown parameter: {name}");
                    }
                    break;
                }
            }
        }
    }
}

Diagnostic for missing @key in loops

There is a common source of issues when loops are used in Blazor without assigning @keys to the contained elements/components. This leads to performance issues, and can also lead to issues with correctness (e.g. when it is important which component gets disposed). By installing this nuget package, you get warnings when you forget to set @key:

@foreach (var element in items)
 ~~~~~~~
 Warning: A key must be used when rendering loops in Blazor
{
    <div class="my-component">
        @element
    </div>
}

Experimental diagnostic: required parameters

In some situations it's an advantage to be able to mark parameters as required. With this package you have two ways to say that:

  1. explicitly required
@code {
    [Required][Parameter] public T Value { get; set; }
}
  1. all parameters are required
// either in your Component.razor or in _Imports.razor
@attribute [Excubo.Generators.Blazor.ParametersAreRequiredByDefault]