Blazor 将 ValidationMessage 传递给扩展的 InputText 组件
Blazor pass ValidationMessage to extended InputText component
我有一个 ExtendedInputText
组件继承自 InputText
@inherits InputText
<div class="flex">
<label class="w-1/2">
@Label
@if(Required){
<span class="text-red-500 ml-1">*</span>
}
</label>
<InputText
class="flex-1 border border-gray-200 bg-white p-2 rounded"
placeholder="@Label"
Value="@Value"
ValueChanged="@ValueChanged"
ValueExpression="@ValueExpression"
Required="@Required"
/>
</div>
@code
{
[Parameter]
public bool Required { get; set; }
[Parameter]
public string Label { get; set; }
}
我打算用它来代替这个
<EditForm Model="Command" OnValidSubmit="OnValidSubmit">
<FluentValidationValidator />
<ValidationSummary />
<div class="">
<label>Title <span class="text-red-500">*</span></label>
<InputText id="Title" @bind-Value="Command.Title" />
<ValidationMessage For="@(() => Command.Title)" />
</div>
<button type="submit" class="p-2 bg-positive-500 text-white rounded">Create</button>
</EditForm>
有了这个
<EditForm Model="Command" OnValidSubmit="OnValidSubmit">
<FluentValidationValidator />
<ValidationSummary />
<ExtendedInputText Label="Title" Required="true" @bind-Value="Command.Title"/>
<button type="submit" class="p-2 bg-positive-500 text-white rounded">Create</button>
</EditForm>
我如何将 <ValidationMessage For="@(() => Command.Title)" />
传递给 ExtendedInputText
组件并从内部渲染它?
我将以下代码用于我创建的组件 LabelText
但应该用于您的情况:
public partial class LabelText<T>: ComponentBase
{
[Parameter] public Expression<Func<T>> For { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
private FieldIdentifier _fieldIdentifier;
...
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "label");
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "for", _fieldIdentifier.FieldName);
builder.AddContent(3, label + GetRequired());
builder.CloseElement();
}
protected override void OnParametersSet()
{
if (CurrentEditContext == null)
{
throw new InvalidOperationException($"{GetType()} requires a cascading parameter " +
$"of type {nameof(EditContext)}. For example, you can use {GetType()} inside " +
$"an {nameof(EditForm)}.");
}
if (For == null) // Not possible except if you manually specify T
{
throw new InvalidOperationException($"{GetType()} requires a value for the " +
$"{nameof(For)} parameter.");
}
_fieldIdentifier = FieldIdentifier.Create(For);
}
更新
我无法比@MrC 的优秀代码更好地解释
private RenderFragment ValidationFragment => (builder) =>
{
if (this.ShowValidation && !this.IsValid)
{
builder.OpenElement(310, "div");
builder.AddAttribute(320, "class", MessageCss);
builder.OpenComponent<ValidationMessage<TValue>>(330);
builder.AddAttribute(340, "For", this.ValueExpression);
builder.CloseComponent();
builder.CloseElement();
}
else if (!string.IsNullOrWhiteSpace(this.HelperText))
{
builder.OpenElement(350, "div");
builder.AddAttribute(360, "class", MessageCss);
builder.AddContent(370, this.HelperText);
builder.CloseElement();
}
};
您只需要添加一个参数,例如 ValidationMessage="(() => Command.Title)"
,这个 RenderFragment 会为您完成这项工作。
类似组件的完整代码。注意组件获取的是Validation消息,不需要传递。
我想我已经包括了唯一的依赖者 class,但我可能漏掉了一些东西。
/// ============================================================
/// Author: Shaun Curtis, Cold Elm Coders
/// License: Use And Donate
/// If you use it, donate something to a charity somewhere
/// ============================================================
using Blazr.SPA.Components;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Rendering;
using System;
using System.Linq;
using System.Linq.Expressions;
#nullable enable
#pragma warning disable CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes).
#pragma warning disable CS8602 // Dereference of a possibly null reference.
namespace Blazr.UIComponents
{
public class FormEditControl<TValue> : ComponentBase
{
[Parameter]
public TValue? Value { get; set; }
[Parameter] public EventCallback<TValue> ValueChanged { get; set; }
[Parameter] public Expression<Func<TValue>>? ValueExpression { get; set; }
[Parameter] public string? Label { get; set; }
[Parameter] public string? HelperText { get; set; }
[Parameter] public string DivCssClass { get; set; } = "mb-2";
[Parameter] public string LabelCssClass { get; set; } = "form-label";
[Parameter] public string ControlCssClass { get; set; } = "form-control";
[Parameter] public Type ControlType { get; set; } = typeof(InputText);
[Parameter] public bool ShowValidation { get; set; }
[Parameter] public bool ShowLabel { get; set; } = true;
[Parameter] public bool IsRequired { get; set; }
[Parameter] public bool IsRow { get; set; }
[CascadingParameter] EditContext CurrentEditContext { get; set; } = default!;
private readonly string formId = Guid.NewGuid().ToString();
private bool IsLabel => this.ShowLabel && (!string.IsNullOrWhiteSpace(this.Label) || !string.IsNullOrWhiteSpace(this.FieldName));
private bool IsValid;
private FieldIdentifier _fieldIdentifier;
private ValidationMessageStore? _messageStore;
private string? DisplayLabel => this.Label ?? this.FieldName;
private string? FieldName
{
get
{
string? fieldName = null;
if (this.ValueExpression != null)
ParseAccessor(this.ValueExpression, out var model, out fieldName);
return fieldName;
}
}
private string MessageCss => CSSBuilder.Class()
.AddClass("invalid-feedback", !this.IsValid)
.AddClass("valid-feedback", this.IsValid)
.Build();
private string ControlCss => CSSBuilder.Class(this.ControlCssClass)
.AddClass("is-valid", this.IsValid)
.AddClass("is-invalid", !this.IsValid)
.Build();
protected override void OnInitialized()
{
if (CurrentEditContext is null)
throw new InvalidOperationException($"No Cascading Edit Context Found!");
if (ValueExpression is null)
throw new InvalidOperationException($"No ValueExpression defined for the Control! Define a Bind-Value.");
if (!ValueChanged.HasDelegate)
throw new InvalidOperationException($"No ValueChanged defined for the Control! Define a Bind-Value.");
CurrentEditContext.OnFieldChanged += FieldChanged;
CurrentEditContext.OnValidationStateChanged += ValidationStateChanged;
_messageStore = new ValidationMessageStore(this.CurrentEditContext);
_fieldIdentifier = FieldIdentifier.Create(ValueExpression);
if (_messageStore is null)
throw new InvalidOperationException($"Cannot set the Validation Message Store!");
var messages = CurrentEditContext.GetValidationMessages(_fieldIdentifier).ToList();
var showHelpText = (messages.Count == 0) && this.IsRequired && this.Value is null;
if (showHelpText && !string.IsNullOrWhiteSpace(this.HelperText))
_messageStore.Add(_fieldIdentifier, this.HelperText);
}
protected void ValidationStateChanged(object sender, ValidationStateChangedEventArgs e)
{
var messages = CurrentEditContext.GetValidationMessages(_fieldIdentifier).ToList();
if (messages != null || messages.Count > 1)
{
_messageStore.Clear();
}
}
protected void FieldChanged(object sender, FieldChangedEventArgs e)
{
if (e.FieldIdentifier.Equals(_fieldIdentifier))
_messageStore.Clear();
}
protected override void OnParametersSet()
{
this.IsValid = true;
{
if (this.IsRequired)
{
this.IsValid = false;
var messages = CurrentEditContext.GetValidationMessages(_fieldIdentifier).ToList();
if (messages is null || messages.Count == 0)
this.IsValid = true;
}
}
}
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
if (IsRow)
builder.AddContent(1, RowFragment);
else
builder.AddContent(2, BaseFragment);
}
private RenderFragment BaseFragment => (builder) =>
{
builder.OpenElement(0, "div");
builder.AddAttribute(10, "class", this.DivCssClass);
builder.AddContent(40, this.LabelFragment);
builder.AddContent(60, this.ControlFragment);
builder.AddContent(70, this.ValidationFragment);
builder.CloseElement();
};
private RenderFragment RowFragment => (builder) =>
{
builder.OpenElement(0, "div");
builder.AddAttribute(10, "class", "row form-group");
builder.OpenElement(20, "div");
builder.AddAttribute(30, "class", "col-12 col-md-3");
builder.AddContent(40, this.LabelFragment);
builder.CloseElement();
builder.OpenElement(40, "div");
builder.AddAttribute(50, "class", "col-12 col-md-9");
builder.AddContent(60, this.ControlFragment);
builder.AddContent(70, this.ValidationFragment);
builder.CloseElement();
builder.CloseElement();
};
private RenderFragment LabelFragment => (builder) =>
{
if (this.IsLabel)
{
builder.OpenElement(110, "label");
builder.AddAttribute(120, "for", this.formId);
builder.AddAttribute(130, "class", this.LabelCssClass);
builder.AddContent(140, this.DisplayLabel);
builder.CloseElement();
}
};
private RenderFragment ControlFragment => (builder) =>
{
builder.OpenComponent(210, this.ControlType);
builder.AddAttribute(220, "class", this.ControlCss);
builder.AddAttribute(230, "Value", this.Value);
builder.AddAttribute(240, "ValueChanged", EventCallback.Factory.Create(this, this.ValueChanged));
builder.AddAttribute(250, "ValueExpression", this.ValueExpression);
builder.CloseComponent();
};
private RenderFragment ValidationFragment => (builder) =>
{
if (this.ShowValidation && !this.IsValid)
{
builder.OpenElement(310, "div");
builder.AddAttribute(320, "class", MessageCss);
builder.OpenComponent<ValidationMessage<TValue>>(330);
builder.AddAttribute(340, "For", this.ValueExpression);
builder.CloseComponent();
builder.CloseElement();
}
else if (!string.IsNullOrWhiteSpace(this.HelperText))
{
builder.OpenElement(350, "div");
builder.AddAttribute(360, "class", MessageCss);
builder.AddContent(370, this.HelperText);
builder.CloseElement();
}
};
// Code lifted from FieldIdentifier.cs
private static void ParseAccessor<T>(Expression<Func<T>> accessor, out object model, out string fieldName)
{
var accessorBody = accessor.Body;
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.");
fieldName = memberExpression.Member.Name;
if (memberExpression.Expression is ConstantExpression constantExpression)
{
if (constantExpression.Value is null)
throw new ArgumentException("The provided expression must evaluate to a non-null value.");
model = constantExpression.Value;
}
else if (memberExpression.Expression != null)
{
var modelLambda = Expression.Lambda(memberExpression.Expression);
var modelLambdaCompiled = (Func<object?>)modelLambda.Compile();
var result = modelLambdaCompiled();
if (result is null)
throw new ArgumentException("The provided expression must evaluate to a non-null value.");
model = result;
}
else
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.");
}
}
}
#pragma warning restore CS8622
#pragma warning restore CS8602
#nullable disable
和CSSBuilder
/// ============================================================
/// Author: Shaun Curtis, Cold Elm Coders
/// License: Use And Donate
/// If you use it, donate something to a charity somewhere
/// ============================================================
using System.Collections.Generic;
using System.Text;
using System.Linq;
namespace Blazr.SPA.Components
{
public class CSSBuilder
{
private Queue<string> _cssQueue = new Queue<string>();
public static CSSBuilder Class(string cssFragment = null)
{
var builder = new CSSBuilder(cssFragment);
return builder.AddClass(cssFragment);
}
public CSSBuilder()
{
}
public CSSBuilder (string cssFragment)
{
AddClass(cssFragment);
}
public CSSBuilder AddClass(string cssFragment)
{
if (!string.IsNullOrWhiteSpace(cssFragment)) _cssQueue.Enqueue(cssFragment);
return this;
}
public CSSBuilder AddClass(IEnumerable<string> cssFragments)
{
if (cssFragments != null)
cssFragments.ToList().ForEach(item => _cssQueue.Enqueue(item));
return this;
}
public CSSBuilder AddClass(string cssFragment, bool WhenTrue)
{
if (WhenTrue) return this.AddClass(cssFragment);
return this;
}
public CSSBuilder AddClassFromAttributes(IReadOnlyDictionary<string, object> additionalAttributes)
{
if (additionalAttributes != null && additionalAttributes.TryGetValue("class", out var val))
_cssQueue.Enqueue(val.ToString());
return this;
}
public CSSBuilder AddClassFromAttributes(IDictionary<string, object> additionalAttributes)
{
if (additionalAttributes != null && additionalAttributes.TryGetValue("class", out var val))
_cssQueue.Enqueue(val.ToString());
return this;
}
public string Build(string CssFragment = null)
{
if (!string.IsNullOrWhiteSpace(CssFragment)) _cssQueue.Enqueue(CssFragment);
if (_cssQueue.Count == 0)
return string.Empty;
var sb = new StringBuilder();
foreach(var str in _cssQueue)
{
if (!string.IsNullOrWhiteSpace(str)) sb.Append($" {str}");
}
return sb.ToString().Trim();
}
}
}
实际效果如下:
在 Nicola 和 Shaun 的帮助下,这是对我有用的解决方案。
@inherits InputText
<div class="flex">
<label class="w-1/2 text-right font-semibold mr-1 py-2">
@Label
@if (Required)
{
<span class="text-red-500 ml-1">*</span>
}
</label>
<div class="flex-1">
<InputText class="w-full border border-gray-200 bg-white p-2 rounded"
placeholder="@Label"
Value="@Value"
ValueChanged="@ValueChanged"
ValueExpression="@ValueExpression"
Required="@Required"/>
@ValidationFragment
</div>
</div>
@code
{
[Parameter]
public bool Required { get; set; }
[Parameter]
public string Label { get; set; }
private RenderFragment ValidationFragment => (builder) =>
{
var messages = EditContext.GetValidationMessages(FieldIdentifier).ToList();
if(messages is not null && messages.Count > 0)
{
builder.OpenElement(310, "div");
builder.AddAttribute(320, "class", "text-red-500 p-2 w-full");
builder.OpenComponent<ValidationMessage<string>>(330);
builder.AddAttribute(340, "For", ValueExpression);
builder.CloseComponent();
builder.CloseElement();
}
};
}
它们的关键部分是私有 RenderFragment ValidationFragment
,它以编程方式构建以显示存储在级联 EditContext
中的相关错误
我有一个 ExtendedInputText
组件继承自 InputText
@inherits InputText
<div class="flex">
<label class="w-1/2">
@Label
@if(Required){
<span class="text-red-500 ml-1">*</span>
}
</label>
<InputText
class="flex-1 border border-gray-200 bg-white p-2 rounded"
placeholder="@Label"
Value="@Value"
ValueChanged="@ValueChanged"
ValueExpression="@ValueExpression"
Required="@Required"
/>
</div>
@code
{
[Parameter]
public bool Required { get; set; }
[Parameter]
public string Label { get; set; }
}
我打算用它来代替这个
<EditForm Model="Command" OnValidSubmit="OnValidSubmit">
<FluentValidationValidator />
<ValidationSummary />
<div class="">
<label>Title <span class="text-red-500">*</span></label>
<InputText id="Title" @bind-Value="Command.Title" />
<ValidationMessage For="@(() => Command.Title)" />
</div>
<button type="submit" class="p-2 bg-positive-500 text-white rounded">Create</button>
</EditForm>
有了这个
<EditForm Model="Command" OnValidSubmit="OnValidSubmit">
<FluentValidationValidator />
<ValidationSummary />
<ExtendedInputText Label="Title" Required="true" @bind-Value="Command.Title"/>
<button type="submit" class="p-2 bg-positive-500 text-white rounded">Create</button>
</EditForm>
我如何将 <ValidationMessage For="@(() => Command.Title)" />
传递给 ExtendedInputText
组件并从内部渲染它?
我将以下代码用于我创建的组件 LabelText
但应该用于您的情况:
public partial class LabelText<T>: ComponentBase
{
[Parameter] public Expression<Func<T>> For { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
private FieldIdentifier _fieldIdentifier;
...
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "label");
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "for", _fieldIdentifier.FieldName);
builder.AddContent(3, label + GetRequired());
builder.CloseElement();
}
protected override void OnParametersSet()
{
if (CurrentEditContext == null)
{
throw new InvalidOperationException($"{GetType()} requires a cascading parameter " +
$"of type {nameof(EditContext)}. For example, you can use {GetType()} inside " +
$"an {nameof(EditForm)}.");
}
if (For == null) // Not possible except if you manually specify T
{
throw new InvalidOperationException($"{GetType()} requires a value for the " +
$"{nameof(For)} parameter.");
}
_fieldIdentifier = FieldIdentifier.Create(For);
}
更新
我无法比@MrC 的优秀代码更好地解释
private RenderFragment ValidationFragment => (builder) =>
{
if (this.ShowValidation && !this.IsValid)
{
builder.OpenElement(310, "div");
builder.AddAttribute(320, "class", MessageCss);
builder.OpenComponent<ValidationMessage<TValue>>(330);
builder.AddAttribute(340, "For", this.ValueExpression);
builder.CloseComponent();
builder.CloseElement();
}
else if (!string.IsNullOrWhiteSpace(this.HelperText))
{
builder.OpenElement(350, "div");
builder.AddAttribute(360, "class", MessageCss);
builder.AddContent(370, this.HelperText);
builder.CloseElement();
}
};
您只需要添加一个参数,例如 ValidationMessage="(() => Command.Title)"
,这个 RenderFragment 会为您完成这项工作。
类似组件的完整代码。注意组件获取的是Validation消息,不需要传递。
我想我已经包括了唯一的依赖者 class,但我可能漏掉了一些东西。
/// ============================================================
/// Author: Shaun Curtis, Cold Elm Coders
/// License: Use And Donate
/// If you use it, donate something to a charity somewhere
/// ============================================================
using Blazr.SPA.Components;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Rendering;
using System;
using System.Linq;
using System.Linq.Expressions;
#nullable enable
#pragma warning disable CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes).
#pragma warning disable CS8602 // Dereference of a possibly null reference.
namespace Blazr.UIComponents
{
public class FormEditControl<TValue> : ComponentBase
{
[Parameter]
public TValue? Value { get; set; }
[Parameter] public EventCallback<TValue> ValueChanged { get; set; }
[Parameter] public Expression<Func<TValue>>? ValueExpression { get; set; }
[Parameter] public string? Label { get; set; }
[Parameter] public string? HelperText { get; set; }
[Parameter] public string DivCssClass { get; set; } = "mb-2";
[Parameter] public string LabelCssClass { get; set; } = "form-label";
[Parameter] public string ControlCssClass { get; set; } = "form-control";
[Parameter] public Type ControlType { get; set; } = typeof(InputText);
[Parameter] public bool ShowValidation { get; set; }
[Parameter] public bool ShowLabel { get; set; } = true;
[Parameter] public bool IsRequired { get; set; }
[Parameter] public bool IsRow { get; set; }
[CascadingParameter] EditContext CurrentEditContext { get; set; } = default!;
private readonly string formId = Guid.NewGuid().ToString();
private bool IsLabel => this.ShowLabel && (!string.IsNullOrWhiteSpace(this.Label) || !string.IsNullOrWhiteSpace(this.FieldName));
private bool IsValid;
private FieldIdentifier _fieldIdentifier;
private ValidationMessageStore? _messageStore;
private string? DisplayLabel => this.Label ?? this.FieldName;
private string? FieldName
{
get
{
string? fieldName = null;
if (this.ValueExpression != null)
ParseAccessor(this.ValueExpression, out var model, out fieldName);
return fieldName;
}
}
private string MessageCss => CSSBuilder.Class()
.AddClass("invalid-feedback", !this.IsValid)
.AddClass("valid-feedback", this.IsValid)
.Build();
private string ControlCss => CSSBuilder.Class(this.ControlCssClass)
.AddClass("is-valid", this.IsValid)
.AddClass("is-invalid", !this.IsValid)
.Build();
protected override void OnInitialized()
{
if (CurrentEditContext is null)
throw new InvalidOperationException($"No Cascading Edit Context Found!");
if (ValueExpression is null)
throw new InvalidOperationException($"No ValueExpression defined for the Control! Define a Bind-Value.");
if (!ValueChanged.HasDelegate)
throw new InvalidOperationException($"No ValueChanged defined for the Control! Define a Bind-Value.");
CurrentEditContext.OnFieldChanged += FieldChanged;
CurrentEditContext.OnValidationStateChanged += ValidationStateChanged;
_messageStore = new ValidationMessageStore(this.CurrentEditContext);
_fieldIdentifier = FieldIdentifier.Create(ValueExpression);
if (_messageStore is null)
throw new InvalidOperationException($"Cannot set the Validation Message Store!");
var messages = CurrentEditContext.GetValidationMessages(_fieldIdentifier).ToList();
var showHelpText = (messages.Count == 0) && this.IsRequired && this.Value is null;
if (showHelpText && !string.IsNullOrWhiteSpace(this.HelperText))
_messageStore.Add(_fieldIdentifier, this.HelperText);
}
protected void ValidationStateChanged(object sender, ValidationStateChangedEventArgs e)
{
var messages = CurrentEditContext.GetValidationMessages(_fieldIdentifier).ToList();
if (messages != null || messages.Count > 1)
{
_messageStore.Clear();
}
}
protected void FieldChanged(object sender, FieldChangedEventArgs e)
{
if (e.FieldIdentifier.Equals(_fieldIdentifier))
_messageStore.Clear();
}
protected override void OnParametersSet()
{
this.IsValid = true;
{
if (this.IsRequired)
{
this.IsValid = false;
var messages = CurrentEditContext.GetValidationMessages(_fieldIdentifier).ToList();
if (messages is null || messages.Count == 0)
this.IsValid = true;
}
}
}
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
if (IsRow)
builder.AddContent(1, RowFragment);
else
builder.AddContent(2, BaseFragment);
}
private RenderFragment BaseFragment => (builder) =>
{
builder.OpenElement(0, "div");
builder.AddAttribute(10, "class", this.DivCssClass);
builder.AddContent(40, this.LabelFragment);
builder.AddContent(60, this.ControlFragment);
builder.AddContent(70, this.ValidationFragment);
builder.CloseElement();
};
private RenderFragment RowFragment => (builder) =>
{
builder.OpenElement(0, "div");
builder.AddAttribute(10, "class", "row form-group");
builder.OpenElement(20, "div");
builder.AddAttribute(30, "class", "col-12 col-md-3");
builder.AddContent(40, this.LabelFragment);
builder.CloseElement();
builder.OpenElement(40, "div");
builder.AddAttribute(50, "class", "col-12 col-md-9");
builder.AddContent(60, this.ControlFragment);
builder.AddContent(70, this.ValidationFragment);
builder.CloseElement();
builder.CloseElement();
};
private RenderFragment LabelFragment => (builder) =>
{
if (this.IsLabel)
{
builder.OpenElement(110, "label");
builder.AddAttribute(120, "for", this.formId);
builder.AddAttribute(130, "class", this.LabelCssClass);
builder.AddContent(140, this.DisplayLabel);
builder.CloseElement();
}
};
private RenderFragment ControlFragment => (builder) =>
{
builder.OpenComponent(210, this.ControlType);
builder.AddAttribute(220, "class", this.ControlCss);
builder.AddAttribute(230, "Value", this.Value);
builder.AddAttribute(240, "ValueChanged", EventCallback.Factory.Create(this, this.ValueChanged));
builder.AddAttribute(250, "ValueExpression", this.ValueExpression);
builder.CloseComponent();
};
private RenderFragment ValidationFragment => (builder) =>
{
if (this.ShowValidation && !this.IsValid)
{
builder.OpenElement(310, "div");
builder.AddAttribute(320, "class", MessageCss);
builder.OpenComponent<ValidationMessage<TValue>>(330);
builder.AddAttribute(340, "For", this.ValueExpression);
builder.CloseComponent();
builder.CloseElement();
}
else if (!string.IsNullOrWhiteSpace(this.HelperText))
{
builder.OpenElement(350, "div");
builder.AddAttribute(360, "class", MessageCss);
builder.AddContent(370, this.HelperText);
builder.CloseElement();
}
};
// Code lifted from FieldIdentifier.cs
private static void ParseAccessor<T>(Expression<Func<T>> accessor, out object model, out string fieldName)
{
var accessorBody = accessor.Body;
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.");
fieldName = memberExpression.Member.Name;
if (memberExpression.Expression is ConstantExpression constantExpression)
{
if (constantExpression.Value is null)
throw new ArgumentException("The provided expression must evaluate to a non-null value.");
model = constantExpression.Value;
}
else if (memberExpression.Expression != null)
{
var modelLambda = Expression.Lambda(memberExpression.Expression);
var modelLambdaCompiled = (Func<object?>)modelLambda.Compile();
var result = modelLambdaCompiled();
if (result is null)
throw new ArgumentException("The provided expression must evaluate to a non-null value.");
model = result;
}
else
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.");
}
}
}
#pragma warning restore CS8622
#pragma warning restore CS8602
#nullable disable
和CSSBuilder
/// ============================================================
/// Author: Shaun Curtis, Cold Elm Coders
/// License: Use And Donate
/// If you use it, donate something to a charity somewhere
/// ============================================================
using System.Collections.Generic;
using System.Text;
using System.Linq;
namespace Blazr.SPA.Components
{
public class CSSBuilder
{
private Queue<string> _cssQueue = new Queue<string>();
public static CSSBuilder Class(string cssFragment = null)
{
var builder = new CSSBuilder(cssFragment);
return builder.AddClass(cssFragment);
}
public CSSBuilder()
{
}
public CSSBuilder (string cssFragment)
{
AddClass(cssFragment);
}
public CSSBuilder AddClass(string cssFragment)
{
if (!string.IsNullOrWhiteSpace(cssFragment)) _cssQueue.Enqueue(cssFragment);
return this;
}
public CSSBuilder AddClass(IEnumerable<string> cssFragments)
{
if (cssFragments != null)
cssFragments.ToList().ForEach(item => _cssQueue.Enqueue(item));
return this;
}
public CSSBuilder AddClass(string cssFragment, bool WhenTrue)
{
if (WhenTrue) return this.AddClass(cssFragment);
return this;
}
public CSSBuilder AddClassFromAttributes(IReadOnlyDictionary<string, object> additionalAttributes)
{
if (additionalAttributes != null && additionalAttributes.TryGetValue("class", out var val))
_cssQueue.Enqueue(val.ToString());
return this;
}
public CSSBuilder AddClassFromAttributes(IDictionary<string, object> additionalAttributes)
{
if (additionalAttributes != null && additionalAttributes.TryGetValue("class", out var val))
_cssQueue.Enqueue(val.ToString());
return this;
}
public string Build(string CssFragment = null)
{
if (!string.IsNullOrWhiteSpace(CssFragment)) _cssQueue.Enqueue(CssFragment);
if (_cssQueue.Count == 0)
return string.Empty;
var sb = new StringBuilder();
foreach(var str in _cssQueue)
{
if (!string.IsNullOrWhiteSpace(str)) sb.Append($" {str}");
}
return sb.ToString().Trim();
}
}
}
实际效果如下:
在 Nicola 和 Shaun 的帮助下,这是对我有用的解决方案。
@inherits InputText
<div class="flex">
<label class="w-1/2 text-right font-semibold mr-1 py-2">
@Label
@if (Required)
{
<span class="text-red-500 ml-1">*</span>
}
</label>
<div class="flex-1">
<InputText class="w-full border border-gray-200 bg-white p-2 rounded"
placeholder="@Label"
Value="@Value"
ValueChanged="@ValueChanged"
ValueExpression="@ValueExpression"
Required="@Required"/>
@ValidationFragment
</div>
</div>
@code
{
[Parameter]
public bool Required { get; set; }
[Parameter]
public string Label { get; set; }
private RenderFragment ValidationFragment => (builder) =>
{
var messages = EditContext.GetValidationMessages(FieldIdentifier).ToList();
if(messages is not null && messages.Count > 0)
{
builder.OpenElement(310, "div");
builder.AddAttribute(320, "class", "text-red-500 p-2 w-full");
builder.OpenComponent<ValidationMessage<string>>(330);
builder.AddAttribute(340, "For", ValueExpression);
builder.CloseComponent();
builder.CloseElement();
}
};
}
它们的关键部分是私有 RenderFragment ValidationFragment
,它以编程方式构建以显示存储在级联 EditContext