blazor如何传递某种类型的列表到EditorAttribute



我正在构建一些通用的表单生成器所以我现在可以

public class  Model
{
[Editor(typeof(CustomIntEditor), typeof(InputBase<>))]
public int? testInt{ get; set; }
}

所以CustomIntEditor.razor

@using System.Diagnostics.CodeAnalysis
@using Microsoft.AspNetCore.Components.Forms
@inherits InputBase<int?>
<select @attributes="AdditionalAttributes"
type="number"
class="@CssClass"
value="@CurrentValueAsString"
@onchange="e => CurrentValueAsString = (string?)e.Value">
<option value =1>Choice 1</option>
<option value =2>Choice 2</option>
<option value =3 >Choice 3</option>
@code {
protected override string FormatValueAsString(int? value)
{
if (value != null) return value.ToString()!; else return string.Empty;
}
protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out int? result, [NotNullWhen(false)] out string? validationErrorMessage)
{
validationErrorMessage = null;
if (value != null) result = int.Parse(value!); else result = null;
return true;
}
}

所以问题是这个编辑器如何传递一些List<KeyValuePair<int,string>>

所以我可以像

那样创建选项/值
List<KeyValuePair<int,string>> L = new List<KeyValuePair<int,string>>(){
{1,"asd"},
{2,"bsd"}
}
.
.
.
[Editor(typeof(CustomIntEditor), typeof(InputBase<>),L)]
public int? testInt{ get; set; }

在上循环构建类似

的列表
<option value =@key>@value</option>

---------- 更新 -----------

我现在有这样的

private RenderFragment CreateOptionsListComponent() => builder =>
{
var optionsListAttribute = (OptionsListAttribute?)property.GetCustomAttributes(typeof(OptionsListAttribute), false).FirstOrDefault();
if (optionsListAttribute is not null)
{
var   optionsList = (SortedDictionary<int, string>?)typeof(TModel).GetProperty(optionsListAttribute.List, typeof(SortedDictionary<int, string>))?.GetValue(model!);
}           
builder.OpenComponent(0,typeof(InputOptionsListSelect<>).MakeGenericType(property!.PropertyType));
builder.AddAttribute(1, "Value", Value);
builder.AddAttribute(2, "ValueChanged", changeHandler);
builder.AddAttribute(3, "ValueExpression", lambdaExpression);
builder.AddAttribute(4, "id", FieldId());
builder.AddAttribute(5, "class", "form-control");

builder.CloseComponent();
}

但是如何将这个optionsList传递给这个InputOptionsListSelect<>? 我不能自己实例化这个组件?据我所知,它需要无参数的元素。你知道吗?

i did not try like this

builder.AddContent(6,ListOptions); 

private RenderFragment ListOptions => (__builder) =>
{
foreach(var option in this.optionsList!)
{
__builder.OpenElement(7, "option");
__builder.AddAttribute(8, "value", option.Key);
__builder.AddContent(9, option.Value);
__builder.CloseElement();
}
};

但它什么也没做。不知道它把它放在哪里---------------- 更新2 ----------------是的,我明白了,但这仍然不能解决我的问题你是对的,但这仍然不能解决问题。我没有地方

请检查https://github.com/meziantou/Meziantou.Framework/blob/main/src/Meziantou.AspNetCore.Components/GenericForm.razor

https://github.com/meziantou/Meziantou.Framework/blob/main/src/Meziantou.AspNetCore.Components/GenericFormField.cs

检查他如何使用InputEnumSelect -这是我需要做的,但这不是一个enum;P

https://github.com/meziantou/Meziantou.Framework/blob/main/src/Meziantou.AspNetCore.Components/InputEnumSelect.cs

这是我采取的概念,并在此基础上,我做了一些修改/添加一些功能等所以在任何组件中都没有。razor -所有来自代码

的内容和初始组件使用

<DynamicFormComponent 
TModel=DataX 
Model=d 
OnValidSubmitCallback="OnValidSubmit"
ShowValidationSummary=true
ShowValidationUnderField=true>

我发现我可能可以通过additionalattributes做到这一点,因为它是字符串/对象对-不确定它是否是存储列表的最佳位置,但它可以这样做,我相信

谢谢你!

我不知道你是如何生成你的表单控件或做你的绑定,但这里是如何做属性位。我已经将它连接到一个标准表单中,这样您就可以看到实际运行的代码。

@page "/"
<h3>GenericForm</h3>
<EditForm EditContext=this.editContext>
<InputSelect class="form-select" @bind-Value=modelData.Id>
@this.ListOptions
</InputSelect>
</EditForm>
@code {
public TestModel modelData = new TestModel() { Id = 2 };
private EditContext? editContext;
protected override Task OnInitializedAsync()
{
this.editContext = new EditContext(modelData);
return base.OnInitializedAsync();
}
private SortedDictionary<int, string> GetFieldList(string fieldName)
{
var list = new SortedDictionary<int, string>();
var typeInfo = this.modelData.GetType();
var prop = typeInfo.GetProperty(fieldName);
var editorAttr = prop?.GetCustomAttributes(true).ToList().SingleOrDefault(item => item is OptionListAttribute);
if (editorAttr is not null)
{
OptionListAttribute attr = (OptionListAttribute)editorAttr;
var obj = typeInfo.GetProperty(attr.List)?.GetValue(modelData);
if (obj is not null)
list = (SortedDictionary<int, string>)obj;
}
return list;
}
private RenderFragment ListOptions => (__builder) =>
{
@foreach (var option in this.GetFieldList("Id"))
{
<option value="@option.Key">@option.Value</option>
}
};
public class TestModel
{
[OptionList("LookupList")]
public int Id { get; set; }
public SortedDictionary<int, string> LookupList { get; set; } = new SortedDictionary<int, string>()
{
{ 1, "UK" },
{ 2, "France" },
{ 3, "Spain" },
};
}
[AttributeUsage(AttributeTargets.Property)]
public class OptionListAttribute : Attribute
{
public string List { get; set; }
public OptionListAttribute(string list)
{
List = list;
}
}
}

更新基于更新的Question,这里是一个自定义组件,它展示了如何从ValueExpression获取模型信息和属性值,并构建选项列表。

@using System.Linq.Expressions
@typeparam TValue
<InputSelect @attributes=UserAttributes Value="@this.Value" ValueChanged=this.ValueChanged ValueExpression=this.ValueExpression!>
@this.ListOptions
</InputSelect>
@code {
[Parameter] public TValue? Value { get; set; }
[Parameter] public EventCallback<TValue> ValueChanged { get; set; }
[Parameter] public Expression<Func<TValue>>? ValueExpression { get; set; }
[Parameter(CaptureUnmatchedValues = true)] public IDictionary<string, object> UserAttributes { get; set; } = new Dictionary<string, object>();
private string? fieldName;
private object? model;
private SortedDictionary<TValue, string>? optionList;
protected override void OnInitialized()
{
base.OnInitialized();
if (this.ValueExpression is null)
throw new NullReferenceException("You must set a ValueExpression for the component");
// As we get the ValueExpression we can use it to get the property name and the model
ParseAccessor<TValue>(this.ValueExpression, out model, out fieldName);
// And then get the OptionList
GetOptionList();
}
private void GetOptionList()
{
optionList = new SortedDictionary<TValue, string>();
var typeInfo = this.model?.GetType();
if (typeInfo is not null)
{
var prop = typeInfo.GetProperty(fieldName!);
var editorAttr = prop?.GetCustomAttributes(true).ToList().SingleOrDefault(item => item is OptionListAttribute);
if (editorAttr is not null)
{
OptionListAttribute attr = (OptionListAttribute)editorAttr;
var obj = typeInfo.GetProperty(attr.List)?.GetValue(model);
if (obj is null)
throw new ArgumentException("The provided field must implement the OptionList Attribute.");
optionList = (SortedDictionary<TValue, string>)obj;
}
}
}
// This method takes the Expression provided in ValueExpression and gets the model object and the name of the field referenced
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.");
}
private RenderFragment ListOptions => (__builder) =>
{
@if (this.optionList is not null)
{
@foreach (var option in this.optionList)
{
<option value="@option.Key.ToString()">@option.Value</option>
}
}
};
}

和修改后的演示页面:

@page "/"
<h3>GenericForm</h3>
<EditForm EditContext=this.editContext>
<div class="p-2">
<MySelect class="form-select" @bind-Value=modelData.Id />
</div>
</EditForm>
@code {
public TestModel modelData = new TestModel() { Id = 2 };
private EditContext? editContext;
protected override Task OnInitializedAsync()
{
this.editContext = new EditContext(modelData);
return base.OnInitializedAsync();
}
public class TestModel
{
[OptionList("LookupList")]
public int Id { get; set; }
public SortedDictionary<int, string> LookupList { get; set; } = new SortedDictionary<int, string>()
{
{ 1, "UK" },
{ 2, "France" },
{ 3, "Spain" },
};
}
}

第二更新

以下是上述代码在InputEnumSelect的一个版本中实现的

public sealed class InputListSelect<TValue> : InputBase<TValue>
{
private string? fieldName;
private object? model;
#pragma warning disable CS8714 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint.
private SortedDictionary<TValue, string>? optionList;
#pragma warning restore CS8714 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint.
protected override void OnInitialized()
{
base.OnInitialized();
if (this.ValueExpression is null)
throw new NullReferenceException("You must set a ValueExpression for the component");
// As we get the ValueExpression we can use it to get the property name and the model
ParseAccessor<TValue>(this.ValueExpression, out model, out fieldName);
// And then get the OptionList
GetOptionList();
}
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "select");
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "class", CssClass);
builder.AddAttribute(3, "value", BindConverter.FormatValue(CurrentValueAsString));
builder.AddAttribute(4, "onchange", EventCallback.Factory.CreateBinder<string?>(this, value => CurrentValueAsString = value, CurrentValueAsString, culture: null));
if (optionList is not null)
{
foreach (var option in optionList)
{
builder.OpenElement(5, "option");
builder.AddAttribute(6, "value", option.Key?.ToString());
builder.AddContent(7, option.Value);
builder.CloseElement();
}
}
builder.CloseElement(); // close the select element
}
protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage)
{
// Let's Blazor convert the value for us 😊
if (BindConverter.TryConvertTo(value, CultureInfo.CurrentCulture, out TValue? parsedValue))
{
result = parsedValue!;
validationErrorMessage = "";
return true;
}
// Map null/empty value to null if the bound object is nullable
if (string.IsNullOrEmpty(value))
{
var nullableType = Nullable.GetUnderlyingType(typeof(TValue));
if (nullableType != null)
{
result = default!;
validationErrorMessage = "";
return true;
}
}
// The value is invalid => set the error message
result = default;
validationErrorMessage = $"The {FieldIdentifier.FieldName} field is not valid.";
return false;
}
private void GetOptionList()
{
#pragma warning disable CS8714 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint.
optionList = new SortedDictionary<TValue, string>();
var typeInfo = this.model?.GetType();
if (typeInfo is not null)
{
var prop = typeInfo.GetProperty(fieldName!);
var editorAttr = prop?.GetCustomAttributes(true).ToList().SingleOrDefault(item => item is OptionListAttribute);
if (editorAttr is not null)
{
OptionListAttribute attr = (OptionListAttribute)editorAttr;
var obj = typeInfo.GetProperty(attr.List)?.GetValue(model);
if (obj is null)
throw new ArgumentException("The provided field must implement the OptionList Attribute.");
optionList = (SortedDictionary<TValue, string>)obj;
}
}
#pragma warning restore CS8714 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint.
}
// This method takes the Expression provided in ValueExpression and gets the model object and the name of the field referenced
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.");
}
}

相关内容

  • 没有找到相关文章

最新更新