Michael Bradvica joined Saxony Partners in 2020 as a Software Engineer on our app dev team. Previously, he has worked as a Full-Stack .NET Developer for technology services firms. You can read more of his thought leadership at MichaelBradvica.com – from which the blog post below has been adapted.

Refactoring Form Inputs in Blazor: Step-by-Step

A common scenario in forms with web applications is dealing with tedious repetition when creating input fields.

Many individual inputs have a common pattern of surrounding it with a label, validation, and some styling. Developers continuously find themselves in copy-paste scenarios with only a few small alterations for each input. The line count can easily explode into the hundreds with larger forms. Most of this markup falls under the category of line noise or boilerplate that must be there.

Every form field generally follows the same pattern:

– Input Field

– Validation text when the input is invalid

– Label for the input

– Styling for the input in form of CSS classes

The following example shows two input fields in a Blazor form. The first is a text input for a name, while the other is a number input for a person’s age. For the sake of simplicity, only the input fields are displayed. The CSS classes come from the Bulma library.

“`csharp

<div class=”field”>

    <label for=”name-text-input” class=”label”>Name</label>

    <div class=”control”>

        <InputText class=”input” id=”name-text-input” @bind-Value=”InputModel.Name”></InputText>

    </div>

    <p class=”help is-danger”>

        <ValidationMessage For=”() => InputModel.Name”></ValidationMessage>

    </p>

</div>

<div class=”field”>

    <label for=”age-number-input” class=”label”>Age</label>

    <div class=”control”>

        <InputNumber class=”input” id=”age-number-input” @bind-Value=”InputModel.Age”></InputNumber>

    </div>

    <p class=”help is-danger”>

        <ValidationMessage For=”() => InputModel.Age”></ValidationMessage>

    </p>

</div>

“`

Although nothing is technically wrong with this markup, it is rather repetitive. As engineers, we should acknowledge the time-sinking and sanity-draining exercise of repeating. Forms can grow with more and more fields, drastically increasing the line count in a component. As developers, the infrastructure around the input is secondary. We should only have to declare a single element while all the secondary elements are added automatically.

If we sift through the HTML and Razor markup, we can see the real differences:

– Type of input

– Model passed to the input and validation

– Label name and content

From this, we can create a common interface that will accept these parameters and render the same result. Our desired solution should look like this:

“`csharp

<TextInput @bind-Value=”InputModel.Name” Label=”Name” Validation=”() => InputModel.Name” />

<NumberInput @bind-Value=”InputModel.Age” Label=”Age” Validation=”() => InputModel.Age” />

“`

This ideal implementation is a single line solution that will that contain all the markup and Razor necessary to render a complete input field. We will cut the numbers of lines required for each input from around nine to just one. This will simplify our application and line count, ultimately saving time.

Reviewing the current implementation for the name field:

“`csharp

<div class=”field”>

    <label for=”name-text-input” class=”label”>Name</label>

    <div class=”control”>

        <InputText class=”input” id=”name-text-input” @bind-Value=”InputModel.Name”></InputText>

    </div>

    <p class=”help is-danger”>

        <ValidationMessage For=”() => InputModel.Name”></ValidationMessage>

    </p>

</div>

“`

What we need are two components for each field:

– An input component that is specific to the input type (text, number, email)

– A container around the component that has the styling, label, and validation

We begin with our container component. If we extract everything around the component and leave a RenderFragment for our input, we should get the following:

“`csharp

@using System.Linq.Expressions

@typeparam TValue

<div class=”field”>

    <label for=”@DisplayName” class=”label”>@DisplayName</label>

    <div class=”control”>

        @ChildContent

    </div>

    <p class=”help is-danger”>

        <ValidationMessage For=”@ValidationFor”></ValidationMessage>

    </p>

</div>

@code {

    [Parameter]

    public RenderFragment ChildContent { get; set; }

    [Parameter]

    public string DisplayName { get; set; }

    [Parameter]

    public Expression<Func<TValue>> ValidationFor { get; set; }

}

“`

This is called our “InputContainer” component. All we have done here is replaced the specific input with a RenderFragment, allowing us to reuse the container for every specific input. Therefore, we have a parameter for our label and the validation expression, both of which will be used to validate the contents of the input.

With our InputContainer in place, we need to tackle our individual input components by creating one component per input type. While this may seem like a hassle at first, it will pay dividends later. Creating each input component is trickier than our container. Each input needs to inherit the generic “InputBase” class and implement the “TryParseValueFromString” method. This method is responsible for taking the value from the input and parsing it to the specific type for the model we are trying to populate.

Our TextInput component:

“`csharp

@using System.Linq.Expressions

@inherits InputBase<string>

<InputContainer ValidationFor=”Validation” DisplayName=”@Label”>

    <input class=”input @CssClass” id=”@Label” type=”text” @bind=”@CurrentValue” />

</InputContainer>

@code {

    [Parameter]

    public string Label { get; set; }

    [Parameter]

    public Expression<Func<string>> Validation { get; set; }

    protected override bool TryParseValueFromString(string value, out string result, out string validationErrorMessage)

    {

        result = value;

        validationErrorMessage = null;

        return true;

    }

}

“`

A quick review of what this component is accomplishing:

1) Inherits from InputBase of type string because this is a text input

2) Declares an input of type text and passes the label as an id

3) Adds the “CssClass” property, which is part of InputBase class, allowing Blazor to add the “valid” and “invalid” classes during validation

4) Adds two-way binding to the “CurrentValue” property inherited from the InputBase class

5) Passes the input to the “InputContainer” as a child, the label name, and validation expression

6) Implements the TryParseValueFromString method

For reference, our NumberInput becomes:

“`csharp

@using System.Linq.Expressions

@inherits InputBase<int>

<InputContainer DisplayName=”@Label” ValidationFor=”@Validation”>

    <input class=”input” id=”@Label” type=”number” @bind=”@CurrentValue” />

</InputContainer>

@code {

    [Parameter]

    public string Label { get; set; }

    [Parameter]

    public Expression<Func<int>> Validation { get; set; }

    protected override bool TryParseValueFromString(string value, out int result, out string validationErrorMessage)

    {

        if (int.TryParse(value, out var parsed))

        {

            result = parsed;

            validationErrorMessage = null;

            return true;

        }

        throw new InvalidOperationException(“Can’t parse to int.”);

    }

}

“`

The main difference is that our TryParseValueFromString is forced to do extra logic when dealing with non-numeric strings.

But with our new compact components, our form content becomes:

“`csharp

<TextInput @bind-Value=”InputModel.Name” Label=”Name” Validation=”() => InputModel.Name” />

<NumberInput @bind-Value=”InputModel.Age” Label=”Age” Validation=”() => InputModel.Age” />

“`

The amount of markup you will be able to remove in your application ultimately comes down to how many forms you have and the size of those forms. For applications with minimal form usage, this technique may not make a huge impact. Yet, for applications with many forms, the opportunity to reduce inputs by up to 90 percent cannot be overstated or ignored.

Making Digital Practical with Saxony Partners

Technology and digital strategy can be complicated, but our goal at Saxony Partners is simple: Make Digital Practical.

Saxony Partners will meet you where you are and help you get where you want to go. Our pragmatic approach helps ensure early success by leveraging proven technologies and practical solutions in a cost-effective way.

Our team of app dev experts, full-stack developers, and software engineers work with clients to optimize their technology investments and leverage that technology to meet and solve business needs and challenges.

If you or your company is looking for help with software development, reach out to us here.