Binding parameters from the query string in a Blazor component
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!