Binding parameters from the query string in a Blazor component

  • Gérald Barré

It's common to set values using the query string. For instance, it can contain search parameters and default values for a form. It also very useful for sharing a URL to specific resources.

Blazor doesn't provide a way to get the values of the query string and bind them to properties. However, you can get the full URL using the NavigationManager and the right event in the Blazor component lifecycle to set the properties when needed.

The final code looks like:

@page "/"
@inject NavigationManager NavigationManager

<div>@Value</div>
<button type="button" @onclick="UpdateValue">Update value</button>

@code
{
    // 👇 The value is set from the query string by the SetParametersAsync method
    [QueryStringParameter]
    public int Value { get; set; }

    public override Task SetParametersAsync(ParameterView parameters)
    {
        // 👇 Read the value of each property decorated by [QueryStringParameter] from the query string
        this.SetParametersFromQueryString(NavigationManager);

        return base.SetParametersAsync(parameters);
    }

    private void UpdateValue()
    {
        Value = new Random().Next();

        // 👇 Update the URL to set the new value in the query string
        this.UpdateQueryString(NavigationManager);
    }
}

First, you need to create the QueryStringParameterAttribute attribute to decorate the properties to bind from the query string:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class QueryStringParameterAttribute : Attribute
{
    public QueryStringParameterAttribute()
    {
    }

    public QueryStringParameterAttribute(string name)
    {
        Name = name;
    }

    /// <summary>Name of the query string parameter. It uses the property name by default.</summary>
    public string Name { get; }
}

Then, you can write the extension methods to parse the query string and assign each parameter to the associated property. The parsing and editing of the query string are easy thanks to the QueryHelpers from the NuGet package Microsoft.AspNetCore.WebUtilities.

// Requires Microsoft.AspNetCore.WebUtilities to edit the query string
// <PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
public static class QueryStringParameterExtensions
{
    // Apply the values from the query string to the current component
    public static void SetParametersFromQueryString<T>(this T component, NavigationManager navigationManager)
        where T : ComponentBase
    {
        if (!Uri.TryCreate(navigationManager.Uri, UriKind.RelativeOrAbsolute, out var uri))
            throw new InvalidOperationException("The current url is not a valid URI. Url: " + navigationManager.Uri);

        // Parse the query string
        Dictionary<string, StringValues> queryString = QueryHelpers.ParseQuery(uri.Query);

        // Enumerate all properties of the component
        foreach (var property in GetProperties<T>())
        {
            // Get the name of the parameter to read from the query string
            var parameterName = GetQueryStringParameterName(property);
            if (parameterName == null)
                continue; // The property is not decorated by [QueryStringParameterAttribute]

            if (queryString.TryGetValue(parameterName, out var value))
            {
                // Convert the value from string to the actual property type
                var convertedValue = ConvertValue(value, property.PropertyType);
                property.SetValue(component, convertedValue);
            }
        }
    }

    // Apply the values from the component to the query string
    public static void UpdateQueryString<T>(this T component, NavigationManager navigationManager)
        where T : ComponentBase
    {
        if (!Uri.TryCreate(navigationManager.Uri, UriKind.RelativeOrAbsolute, out var uri))
            throw new InvalidOperationException("The current url is not a valid URI. Url: " + navigationManager.Uri);

        // Fill the dictionary with the parameters of the component
        Dictionary<string, StringValues> parameters = QueryHelpers.ParseQuery(uri.Query);
        foreach (var property in GetProperties<T>())
        {
            var parameterName = GetQueryStringParameterName(property);
            if (parameterName == null)
                continue;

            var value = property.GetValue(component);
            if (value is null)
            {
                parameters.Remove(parameterName);
            }
            else
            {
                var convertedValue = ConvertToString(value);
                parameters[parameterName] = convertedValue;
            }
        }

        // Compute the new URL
        var newUri = uri.GetComponents(UriComponents.Scheme | UriComponents.Host | UriComponents.Port | UriComponents.Path, UriFormat.UriEscaped);
        foreach (var parameter in parameters)
        {
            foreach (var value in parameter.Value)
            {
                newUri = QueryHelpers.AddQueryString(newUri, parameter.Key, value);
            }
        }

        navigationManager.NavigateTo(newUri);
    }

    private static object ConvertValue(StringValues value, Type type)
    {
        return Convert.ChangeType(value[0], type, CultureInfo.InvariantCulture);
    }

    private static string ConvertToString(object value)
    {
        return Convert.ToString(value, CultureInfo.InvariantCulture);
    }

    private static PropertyInfo[] GetProperties<T>()
    {
        return typeof(T).GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
    }

    private static string GetQueryStringParameterName(PropertyInfo property)
    {
        var attribute = property.GetCustomAttribute<QueryStringParameterAttribute>();
        if (attribute == null)
            return null;

        return attribute.Name ?? property.Name;
    }
}

When clicking on the update button, you can see that the URL is updated:

Note that NavigationManager.NavigateTo triggers the navigation handler of Blazor. This means it will rebind parameters and re-render the component. If you only want to change the URL without triggering anything on Blazor you can use the history API in JavaScript:

// @inject IJSRuntime jsRuntime
await jsRuntime.InvokeVoidAsync("window.history.replaceState", null, "", newUri);

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee