Parameters In Blazor Components (Deep Dive)

Introduction

A Blazor Component can exist as either a standalone component or an entire page with its own route. When annotating properties in a Blazor Component with [Parameter], it allows us to handle incoming parameters with ease. For instance, when we use the [Parameter] attribute, we can accept custom parameters in our component like this.

<MyComponent MyCustomParameter="value" />

Or like this,

https://<host>/MyComponent/value

The Anatomy of a Blazor Component

When we look at the source code for a Blazor Component, we can immediately tell whether or not the component was intended to be used as a standalone page. If it is, then we know right away the pattern for the URL, so we can access this component at run-time.

@page "/myroute"

As previously mentioned, the @page directive can also be used to specify the pattern for accepting parameters in the route.

@page "/counter/{MyParameter}"

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

Because of the use of the [Parameter] attribute here, Blazor will automatically initialize the property MyParameter with the corresponding value from the route parameters. For instance, if we run our app and navigate to.

"/counter/myValue"

The value of MyParameter will be the string “myValue” when the component has finished loading. Here is a way we can visualize a simple Parameter.

MyParameter

A Quick Primer On Attributes in C#

If you’re unfamiliar with attributes in C#, it will be hard to understand how this [Parameter] functionality actually works in Blazor. In a previous section, I stated “Blazor will automatically initialize the property …” and this is true. Attributes do not have inherent behaviors in C# or .NET, and they generally do not influence any external data on their own. Attributes are just metadata on a property, and this metadata needs to be consumed by some other code.

But how are attributes implemented?

Attributes are simple classes that follow a particular naming convention, ClassNameAttribute —signaling to the C# compiler that this class will be used as an annotation in another class.

Say, for example, I want a custom attribute called [ValidationApplied]. I would create a new class called ValidationAppliedAttribute which will inherit from Attribute.

class ValidationAppliedAttribute : Attribute
{
}

Now this class can be used as an attribute in another class.

class SomeClass  
{  
    [ValidationApplied] // custom attribute  
    public string MyProperty { get; set; }  
}  

What’s interesting about this is that on its own, the attribute doesn’t do anything. It is merely metadata on a property and doesn’t encapsulate any behaviors. Attributes can have their own properties, too.

class ValidationAppliedAttribute : Attribute
{
    public string MyProperty { get; set; }
}

And now the following syntax is possible.

[ValidationApplied(MyProperty = "some value")]
public string SomeProperty { get; set; }

Given the fact that attributes don’t inherently encapsulate any behaviors, we need to write the behaviors ourselves. This is done using reflection elsewhere in the codebase.

var attributes = Attribute.GetCustomAttributes(myObject.GetType()).Dump();

foreach (var attribute in attributes)
{
    if (attribute is ValidationAppliedAttribute)
    {
        // property has [ValidationApplied]. test validation on property
    }
}

We can be certain this type of reflection is being performed when we use the [Parameter] annotation in a Blazor Component. Using reflection does carry some performance overhead, but over time this overhead has gotten smaller and smaller. I'm only mentioning all of this because it’s a good idea, in my opinion, to be aware of what our code is actually doing when we use these very abstract frameworks—especially when we’re paying for compute time on the Cloud. But, we also need not jump to any conclusions about performance just by looking at an implementation detail alone. For instance, many frameworks that do this type of reflection may implement caching. They’ll do the reflection work once and cache the results. After all, attributes are applied to Types, not Instances. Therefore, the work done using reflection only really needs to be done once per type.

Note. I’m not certain whether or not Blazor implements this caching functionality. If anyone has knowledge about this, feel free to share in the comments and I’ll update this article.

Optional Parameters

In C#, we can create optional values using nullable types. Nullable types are first-class citizen in Blazor, which means we can take advantage of this syntax to allow for optional parameters in our Blazor Components. Here’s how we can specify an optional parameter in a component,

@page "/counter/{InitialValue:int?}"
@code {
    private int _count = 0;
    [Parameter]
    public int? InitialValue { get; set; }
    protected override void OnParametersSet()
    {
        if (InitialValue.HasValue)
        {
            _count = InitialValue.Value;
        }
    }
}

In the @page directive at the top of the component, we’re using a type annotation to tell Blazor this will be an optional int. Below that in the @code section, we are creating a corresponding nullable int property called InitialValue. Once the parameters are set during initialization, we are then checking to see if a value has been provided. If so, we update our count value with InitialValue.

Consequently, because Blazor Components don’t care how they are incorporated into your project, all of this logic will work when our component is used as a standalone component on another page! This means we can set InitialValue like this.

/counter/4

or like this,

<Counter InitialValue="4" />

As previously mentioned, Blazor Components don’t care whether they’re a page, a component in another page, or a component in another component.

Two-Way Data Binding

Two-Way Data Binding is a powerful mechanism that allows us to bind to data inside the component (from outside the component,) sending and receiving changes to the component state in real time. In other words, a parent component can bind a variable to a Parameter within our child component—Any changes made to that parent’s variable will update the bound Parameter in the child component. Likewise, any changes made to the child Parameter will also change the bound variable within the parent component.

This is why it’s known as two-way binding. Luckily, adding the capability of two-way binding in a Blazor Component is easy!

There are only a couple of additions we need to make to our code in order to enable two-way binding. First, we need to add a corresponding EventCallback for the parameter in question. Take notice of the name given to this event callback in the following image; this naming convention is required for Blazor to wire everything up correctly.

Blazor

When using this component in a page, we can bind to the parameter via the @bind- attribute. Now, any changes to the bound data will propagate automatically. There is one thing missing in the above example, however. If we change the value of MyParameter from within MyComponent, we need to actually invoke MyParameterChanged to ensure the proper events are fired.

private async Task ChangeValueAsync()
{
    MyParameter = "greetings, world!";
    await MyParameterChanged.InvokeAsync(MyParameter);
}

Example

For this example, we’re going to create a component called TwoWayBinding.razor. This is a basic counter component with two-way binding enabled.

<h3>Value: @Value</h3>
<button class="btn btn-success" @onclick="ChangeValue">Increment Value</button>
@code {
    [Parameter]
    public int Value { get; set; }

    [Parameter]
    public EventCallback<int> ValueChanged { get; set; }

    private async Task ChangeValue()
    {
        ++Value;
        await ValueChanged.InvokeAsync(Value);
    }
}

Next, in Index.razor we can add this code.

<p>Data From Page: @count</p>
<TwoWayBinding @bind-Value="count" />
<input type="number" @bind-value="count" />
@code {
    private int count;
}

Running this example will yield several text displays and controls, all bound to the same data. Any changes made will automatically propagate to every other control that binds to the data.

Several text

Conclusion

In this article we took a deeper look at what Parameters are in Blazor, how they work, how we can use them, and finally how we can add Two-Way Binding capabilities to a Parameter. I hope after reading this material you’re able to fully understand and use these powerful mechanisms given to us by ASP.NET Core and Blazor.

Happy Coding! Stay safe everyone.


Similar Articles