On my parent component I have this:
<TestComponent @bind-Value="testString" />
@code {
[MaxLength(10)]
private string testString;
}
And on my TestComponent
this:
<input type="text" @bind="Value" @bind:event="oninput" />
@code {
[Parameter] public string Value { get; set; }
[Parameter] public EventCallback<string> ValueChanged { get; set; }
protected override void OnInitialized() {
//Get MaxLength here
}
}
How could I check in my TestComponent
if Value
have a MaxLength
and get his value if it does?
I already have this need to check for attributes to use DisplayName
to render a label and Required
to add a red *
to the label, and I didn't know how to do this, I tried the normal way of doing it, but now sucess.
But then I remembered that Blazor already does this with ValidationMessage
because it gets the attributes from a property and validates it... so I decided to check the source code of it.
Digging very deep, I found this function which explains how to do what we need.
First of all, it have a Expression<Func<T>>
parameter, which in blazor is the For
property of ValidationMessage
, so we can see here that probably isn't possible to do it with a binded value or just passing it like Foo="@Foo"
(if it was possible, they would probably have done that way), so we need to have another property that will pass that type
eg
<TestComponent @bind-Value="testString" Field=@"(() => testString)" />
Now continuing with the code from that function, it will get the Body
of the expression and do some checkings to get and make sure you are passing a property.
And then there is this line.
fieldName = memberExpression.Member.Name;
And if you take a look at memberExpression.Member
and call GetCustomAttributes
you will have exactly what we need, all the custom attributes of the proprety.
So now all we need is just loop the custom attributes and do what ever you want.
Here is a simplified version to get the CustomAttribute
of the property returned in the Expression<Func<T>>
private IEnumerable<CustomAttributeData> GetExpressionCustomAttributes<T>(Expression<Func<T>> accessor)
{
var accessorBody = accessor.Body;
// Unwrap casts to object
if (accessorBody is UnaryExpression unaryExpression
&& unaryExpression.NodeType == ExpressionType.Convert
&& unaryExpression.Type == typeof(object))
{
accessorBody = unaryExpression.Operand;
}
if (!(accessorBody is MemberExpression memberExpression))
{
throw new ArgumentException($"The provided expression contains a {accessorBody.GetType().Name} which is not supported. {nameof(FieldIdentifier)} only supports simple member accessors (fields, properties) of an object.");
}
return memberExpression.Member.GetCustomAttributes();
}
For you example, here is how to solve it
.razor
<TestComponent @bind-Value="testString" Field="(() => testString)" />
@code {
[MaxLength(10)]
private string testString;
}
TestComponent.razor
<input type="text" @bind="Value" @bind:event="oninput" />
@code {
[Parameter] public Expression<Func<string>>Field { get; set; }
[Parameter] public string Value { get; set; }
[Parameter] public EventCallback<string> ValueChanged { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
if (Field != null)
{
var attrs = GetExpressionCustomAttributes(Field);
foreach (var attr in attrs)
{
if(attr is MaxLengthAttribute maxLengthAttribute)
{
// Do what you want with maxLengthAttribute
}
}
}
}
private IEnumerable<CustomAttributeData> GetExpressionCustomAttributes<T>(Expression<Func<T>> accessor)
{
var accessorBody = accessor.Body;
// Unwrap casts to object
if (accessorBody is UnaryExpression unaryExpression
&& unaryExpression.NodeType == ExpressionType.Convert
&& unaryExpression.Type == typeof(object))
{
accessorBody = unaryExpression.Operand;
}
if (!(accessorBody is MemberExpression memberExpression))
{
throw new ArgumentException($"The provided expression contains a {accessorBody.GetType().Name} which is not supported. {nameof(FieldIdentifier)} only supports simple member accessors (fields, properties) of an object.");
}
return memberExpression.Member.GetCustomAttributes();
}
}
If you only have one attribute, you can also call memberExpression.Member.GetCustomAttributes<Attribute>()
to get a list of that attribute type.
TL;DR
Add a new property to the component
[Parameter] public Expression<Func<T>>Field { get; set; }
Use this gist helper functions to get the attribute(s) you want.
You can do this:
<TestComponent @bind-Value="@testString" />
@code {
private string str;
// Define testString as a parameter property
[Parameter]
public string testString
{
get {return str;}
set
{
if(str.Length <= 10)
{
str = value;
}
}
}
}
Note: What you needed here, is to define a public parameter property into which the value passed by ValueChanged.InvokeAsync is received. This is actually a true two-way component data binding...
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.