Reputation: 3850
This Section of the Docs describes, how to display Validation Messages.
<ValidationMessage For="() => Parameters.PropertyA"></ValidationMessage>
Since For
is of type Expression<Func<TValue>>
, I want to pass a Func instead, but this doesn't compile:
[Parameter]
public Func<string> PropertyLocator { get; set; }
<ValidationMessage For="PropertyLocator"></ValidationMessage>
this compiles, but Validation Messages won't be resolved correctly
<ValidationMessage For="() => PropertyLocator"></ValidationMessage>
I also tried to make the Component generic, such that it knows about the Parameters
Type:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Components;
public partial class MyComponent<TParam>
{
[Parameter]
public TParam Parameters { get; set; }
[Parameter]
public Func<TReportParam, string> PropertyLocator { get; set; }
}
@using System.Linq.Expressions
@typeparam TParam
<ValidationMessage For="@((Expression<Func<string>>)(() => PropertyLocator(this.Parameters)))"></ValidationMessage>
<MyComponent TParam="MyParameters" Parameters="BindToSomeValue" PropertyLocator="(parameters) => parameters.PropertyA" />
But this leads to the following run-time exception:
Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100] Unhandled exception rendering component: The provided expression contains a InvocationExpression1 which is not supported. FieldIdentifier only supports simple member accessors (fields, properties) of an object. System.ArgumentException: The provided expression contains a InvocationExpression1 which is not supported. FieldIdentifier only supports simple member accessors (fields, properties) of an object. at Microsoft.AspNetCore.Components.Forms.FieldIdentifier.ParseAccessor[String](Expression`1 accessor, Object& model, String& fieldName) at Microsoft.AspNetCore.Components.Forms.FieldIdentifier.Create[String](Expression`1 accessor) at Microsoft.AspNetCore.Components.Forms.ValidationMessage`1[[System.String, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnParametersSet() at Microsoft.AspNetCore.Components.ComponentBase.CallOnParametersSetAsync() at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
Upvotes: 4
Views: 2617
Reputation: 3850
After some research I stumbled about the following blazor feature:
Read more about it here.
In short, if a [Parameter]
is bound with the follwoing syntax...
<MyComponent @bind-Value="My.Binding.Path" />
... it not only supports two-way bindings, but it also sets a locator expression.
[Parameter]
public string Value { get; set; }
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
[Parameter]
public Expression<Func<string>> ValueExpression { get; set; }
you may use any type, instead of string
since the value of the ValueExpression
is set automatically, you can use this behavior to display the validation message for the bound property. Simply add the ValidationMessage
Component to your component with the expression.
<ValidationMessage For="ValueExpression" />
If you're building a Component that supports Validation (which at this point, I assume you are). The following might also be interesting for you.
Not only can you use the holy trinity to display validationmessages, but also to create Components supporting validation. There are many articles covering this topic.
In short:
EditContext
whenever neededTo make the above created MyComponent
s Value
Property support validation, just follow these steps.
Define a CascadingParameter
EditContext, this gets the current EditContext, usually from the EditForm
Component. Also note that the EditContext may not be set, if there's no CascadingValue. For example if the Component isn't placed inside an EditForm
:
[CascadingParameter]
public EditContext? EditContext
Define a property to store a FieldIdentifier
and set it when parameters are set.
public FieldIdentifier? FieldIdentifier { get; private set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
await base.SetParametersAsync(parameters);
if (this.EditContext != null && this.DateExpression != null && this.FieldIdentifier?.Model != this.EditContext.Model)
{
this.FieldIdentifier = Microsoft.AspNetCore.Components.Forms.FieldIdentifier.Create(this.DateExpression);
}
}
Trigger the validation for the Field whenever you need (usually after the invocation of ValueChanged
):
this.Value = value;
this.ValueChanged.InvokeAsync(this.Value);
if (this.FieldIdentifier?.FieldName != null)
{
this.EditContext?.NotifyFieldChanged(this.FieldIdentifier!.Value);
}
Upvotes: 3
Reputation: 2601
I've created a small sample page.
The model uses DataAnnotations
as the validation mechanism.
public class DemoInputModel
{
[Required]
public String PropertyOne { get; set; }
[MinLength(2)]
public String PropertyTwo { get; set; }
[MaxLength(5)]
public String PropertyThree { get; set; }
}
On the page, the model is initialized and set as the edit context. We have three text inputs and a select box. The select box can be used to toggle the validation message. If the value is of the select box is changed, a new expression is assigned to the ValidationMessage
.
@using System.ComponentModel.DataAnnotations;
@using System.Linq.Expressions;
@page "/test"
<h1>ValidationMessageTest</h1>
<EditForm Model="_model">
<DataAnnotationsValidator />
<ValidationMessage For="ValidationResolver"></ValidationMessage>
<InputText @bind-Value="_model.PropertyOne" />
<InputText @bind-Value="_model.PropertyTwo" />
<InputText @bind-Value="_model.PropertyThree" />
<InputSelect @bind-Value="SelectedValidationProperty">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</InputSelect>
@*<ValidationSummary />*@
</EditForm>
@code {
private DemoInputModel _model = new DemoInputModel
{
PropertyOne = "Test",
PropertyTwo = "42",
PropertyThree = "Math.PI",
};
private String _selectedValidationProperty;
public String SelectedValidationProperty
{
get => _selectedValidationProperty;
set
{
_selectedValidationProperty = value;
ChangeValidator(value);
}
}
public Expression<Func<String>> ValidationResolver { get; set; }
protected override void OnInitialized()
{
SelectedValidationProperty = "1";
base.OnInitialized();
}
public void ChangeValidator(String value)
{
switch (value)
{
case "1":
ValidationResolver = () => _model.PropertyOne;
break;
case "2":
ValidationResolver = () => _model.PropertyTwo;
break;
case "3":
ValidationResolver = () => _model.PropertyThree;
break;
default:
break;
}
}
}
Did you mean something like this? It gets slightly more complicated if your model doesn't have only strings, like in the example. A "quick" workaround could be to have an Expression
for each possible type.
Under the hood, the expression is used to create a FieldIdentifier
. The FieldIdentifier
is then used to get the corresponding property/field from the EditContext
to check the validation status. Hence, you are constrained in what to choose for the expression. The error message FieldIdentifier only supports simple member accessors (fields, properties) of an object gives a good indication of this limitation.
Upvotes: 3
Reputation: 31693
I want to pass a Func instead
Why? If there isn't a specific reason why you should pass Func<TValue>
instead of Expression<Func<TValue>>
, just have the parameter
[Parameter]
public Expression<Func<string>> PropertyLocator { get; set; }
If you want only a Func<>
because you are going to reuse it for something else other than the For
parameter of ValidationMessage
, you can take a look at Extracting Func<> from Expression<> to get a Func<>
from the Expression<Func<string>> PropertyLocator
.
If you really want to pass a Func<>
, maybe you will get some problems to transform when trying to convert a .net Func to a .net Expression<Func>.
Upvotes: 1