Creating Bespoke Input Components for Blazor from Scratch
Tags: blazor, csharp, dotnet, webassembly
Published: 2019-12-29 00:09:50

---
title: Creating Bespoke Input Components for Blazor from Scratch
published: true
date: 2019-12-10 09:00:00 UTC
tags: Blazor,Csharp,DotNET,WebAssembly
canonical_url: https://chrissainty.com/creating-bespoke-input-components-for-blazor-from-scratch/
---

 ![Creating Bespoke Input Components for Blazor from Scratch](https://chrissainty.com/content/images/2019/12/creating-bespoke-input-components-for-blazor-from-scratch.jpg)

In my last post, we looked at how we could build custom input components on top of `InputBase`. Using `InputBase` saves us loads of extra work by managing all of the interactions with the `EditForm` component and the validation system, which is excellent.

However, sometimes, there are situations where we want or need to have a bit more control over how our input components behave. In this post, we are going to look at how we can build input components from scratch.

## Why build from scratch?

When you consider all the functionality that we get from Blazor's out-of-the-box input components. Plus, the ability to make customisations using the `InputBase` class that we looked at last time. Why would we want to build input components from scratch?

The biggest reason I've found so far is the ability to use input components outside of an `EditForm` component. Any input component which uses InputBase has to be inside of an `EditForm` component; otherwise, an exception is thrown. That's because of a check in the `OnParametersSet` method of `InputBase`. It checks for an `EditContext` which is cascaded down via the `EditForm` component.

<!--kg-card-begin: markdown-->
```
if (CascadedEditContext == null)
{
    throw new InvalidOperationException($"{GetType()} requires a cascading parameter " + $"of type {nameof(Forms.EditContext)}. For example, you can use {GetType().FullName} inside " + $"an {nameof(EditForm)}.");
}

```
<!--kg-card-end: markdown-->

Building from scratch is also beneficial if you're looking to have total control over how your component acts. By creating everything yourself, you'll be able to tailor every detail to work precisely the way you want. For example, you could create an `EditForm` replacement, and that could require you to build custom input components.

## Building from scratch

We're going to build a simple text input component which can be used both inside or outside an `EditForm` component. Text inputs are quite useful components to be able to use both inside a form for any text-based entry. But are equally useful outside of a form for things like search boxes where validation isn't necessarily a concern.

<!--kg-card-begin: markdown-->
```
<input value="@Value" @oninput="HandleInput" />

@code {
    [Parameter] public string Value { get; set; }
    [Parameter] public EventCallback<string> ValueChanged { get; set; }

    private async Task HandleInput(ChangeEventArgs args)
    {
        await ValueChanged.InvokeAsync(args.Value.ToString());
    }
}

```
<!--kg-card-end: markdown-->

This is the basic setup of our `CustomInputText` component. We have set up a couple of parameters, `Value` and `ValueChanged`. These allow us to use Blazor's `bind` directive when consuming the control. We've hooked onto the input controls `oninput` event, and every time it fires the `HandleInput` event invokes the `ValueChanged` `EventCallback` to update the value for the consumer.

## Working as part of EditForm

For an input component to work with EditForm, it has to integrate with `EditContext`. `EditContext` is the brain of a Blazor form; it holds all of the metadata regarding the state of the form. Things like whether a field has been modified. Is it valid? As well as a collection of all of the current validation messages.

`EditContext` is also responsible for raising events to signal that field values have been changed, or that an attempt has been made to submit the form. This triggers the validation aspect of the form.

### Integrating with EditContext

To integrate with EditContext, we need to add a `CascadingParameter` to our component requesting it; then we need to create a `FieldIdentifier`.

The `FieldIdentifier` class uniquely identifies a specific field or property in the form. To create an instance, we need to pass in an expression which identifies the field our component is handling. To get this expression, we can add another parameter to our component called `ValueExpression`. Blazor populates this expression for us based on a convention in a similar way to two-way binding using `Value` and `ValueChanged`.

<!--kg-card-begin: markdown-->
```
[Parameter] public Expression<Func<string>> ValueExpression { get; set; }

```
<!--kg-card-end: markdown-->

Now we have an expression we can create an instance of `FieldIdentifier`; we'll do this in the `OnInitialized` life cycle method.

<!--kg-card-begin: markdown-->
```
protected override void OnInitialized()
{
    _fieldIdentifier = FieldIdentifier.Create(ValueExpression);
}

```
<!--kg-card-end: markdown-->

We need to tell the `EditContext` when the value of our field has been updated. This will trigger any validation logic that needs to run against our field. We do this by calling the `NotifyFieldChanged` method on the `EditContext`.

<!--kg-card-begin: markdown-->
```
private async Task HandleInput(ChangeEventArgs args)
{
    await ValueChanged.InvokeAsync(args.Value.ToString());
    CascadedEditContext?.NotifyFieldChanged(_fieldIdentifier);
}

```
<!--kg-card-end: markdown-->

Once the field has been validated it will be marked as either valid or invalid. We can use this value to assign CSS classes to the component and style it appropriately. To access these values we can use the following code.

<!--kg-card-begin: markdown-->
```
private string _fieldCssClasses => _editContext?.FieldCssClass(_fieldIdentifier) ?? "";

```
<!--kg-card-end: markdown-->

This going to set the `_fieldCssClasses` field to some combination of `modified` `valid` or `invalid`, depending on the fields current state.

The final component looks like this.

<!--kg-card-begin: markdown-->
```
<input class="_fieldCssClasses" value="@Value" @oninput="HandleInput" />

@code {

    private FieldIdentifier _fieldIdentifier;
    private string _fieldCssClasses => CascadedEditContext?.FieldCssClass(_fieldIdentifier) ?? "";

    [CascadingParameter] private EditContext CascadedEditContext { get; set; }

    [Parameter] public string Value { get; set; }
    [Parameter] public EventCallback<string> ValueChanged { get; set; }
    [Parameter] public Expression<Func<string>> ValueExpression { get; set; }

    protected override void OnInitialized()
    {
        _fieldIdentifier = FieldIdentifier.Create(ValueExpression);
    }

    private async Task HandleInput(ChangeEventArgs args)
    {
        await ValueChanged.InvokeAsync(args.Value.ToString());
        CascadedEditContext?.NotifyFieldChanged(_fieldIdentifier);
    }

}

```
<!--kg-card-end: markdown-->
## Working without EditForm

Actually, we've already covered this one. You may have noticed on the last code snippet that we used the null-conditional operator (`?.`) when calling the `NotifyFieldChanged` method. The reason for this is that if the `EditContext` is `null`, then the method won't be called.

Why would the `EditContext` be `null`? If the control wasn't inside of an `EditForm` component. That simple check will allow the control to work outside of an `EditForm` component without any issue.

### What are the costs?

When we use this component without the `EditForm` component we will no longer be able to use the standard validation mechanisms. Depending on your use case this may or may not matter.

For the use cases I've had, it doesn't matter, things such as site searches or date pickers, things where I can use default values or not have to care. You could, of course, deal with this manually if you choose. You're in complete control of the component after all.

For example, we could add a `Required` parameter to the component. When this is true, we can check if there is an `EditContext`. If there isn't, we can set a private variable to show an error message if the current value is empty.

<!--kg-card-begin: markdown-->
```
<input class="_fieldCssClasses" value="@Value" @oninput="HandleInput" />

@if (_showValidation)
{
    <div class="validation-message">You must provide a name</div>
}

@code {

    private FieldIdentifier _fieldIdentifier;
    private string _fieldCssClasses => CascadedEditContext?.FieldCssClass(_fieldIdentifier) ?? "";
    private bool _showValidation;

    [CascadingParameter] private EditContext CascadedEditContext { get; set; }

    [Parameter] public string Value { get; set; }
    [Parameter] public EventCallback<string> ValueChanged { get; set; }
    [Parameter] public Expression<Func<string>> ValueExpression { get; set; }
    [Parameter] public bool Required { get; set; }

    protected override void OnInitialized()
    {
        _fieldIdentifier = FieldIdentifier.Create(ValueExpression);
    }

    private async Task HandleInput(ChangeEventArgs args)
    {
        await ValueChanged.InvokeAsync(args.Value.ToString());

        if (CascadedEditContext != null)
        {
            CascadedEditContext.NotifyFieldChanged(_fieldIdentifier);
        }
        else if (Required)
        {
            _showValidation = string.IsNullOrWhiteSpace(args.Value.ToString());
        }
    }

}

```
<!--kg-card-end: markdown-->

If we don't want the error message to be hardcoded, that's cool too; we can add a parameter for the error message so that it can be passed in. The point here is that you can customise the behaviour as much as you like based on your needs.

## Summary

In this post, we've looked at how we can build bespoke input components that work inside and outside of the `EditForm` component. We started by looking at why we would want to do this in the first place. Then we looked at how to integrate with the built in forms and validation system of Blazor. As well as how to make the component work without that system. Finally, we talked about some of the trade off of working outside of `EditForm`.