C# 如何使用Blazor动态构建Enum select?

C# 如何使用Blazor动态构建Enum select?,c#,.net-core,reflection,blazor,blazor-server-side,C#,.net Core,Reflection,Blazor,Blazor Server Side,我正试图用Blazor构建一个通用表单组件。目前,除枚举选择外,所有其他输入类型都在工作。我认为这是因为编译器在尝试添加表达式和回调函数时不知道具体的枚举类型: public partial class GenericForm<ViewModel> : ComponentBase where ViewModel : new() { [Parameter] public ViewModel Model { get; set; } public readonly

我正试图用Blazor构建一个通用表单组件。目前,除枚举选择外,所有其他输入类型都在工作。我认为这是因为编译器在尝试添加表达式和回调函数时不知道具体的枚举类型:

public partial class GenericForm<ViewModel> : ComponentBase where ViewModel : new()
{
    [Parameter]
    public ViewModel Model { get; set; }
    public readonly PropertyInfo[] Properties = typeof(ViewModel).GetProperties();
    [Parameter] public EventCallback<ViewModel> OnValidSubmit { get; set; }

    protected override async Task OnInitializedAsync()
    {
        if (Model == null)
        {
            Model = new ViewModel();
        }
        await base.OnInitializedAsync();
    }
    public RenderFragment CreateComponent(PropertyInfo property) => builder =>
    {
        var typeCode = Type.GetTypeCode(property.PropertyType);
        if (property.PropertyType.IsEnum)
        {
            BuildEnumComponent(builder,property);
        }
        else
        {
            switch (typeCode)
            {
                case TypeCode.Int32:
                    BuildComponent<double>(property, builder, typeof(InputNumber<double>));
                    break;
                case TypeCode.Int64:
                    BuildComponent<long>(property, builder, typeof(InputNumber<long>));
                    break;
                case TypeCode.Int16:
                    BuildComponent<int>(property, builder, typeof(InputNumber<int>));
                    break;
                case TypeCode.Decimal:
                    BuildComponent<decimal>(property, builder, typeof(InputNumber<decimal>));
                    break;
                case TypeCode.String:
                    BuildComponent<string>(property, builder, typeof(InputText));
                    break;
                case TypeCode.Boolean:
                    BuildComponent<bool>(property, builder, typeof(InputCheckbox));
                    break;
                case TypeCode.DateTime:
                    BuildComponent<DateTime>(property, builder, typeof(InputDate<DateTime>));
                    break;
                default:
                    Console.WriteLine("Unknown property type");
                    break;
            }
        }
        
    };

    private void BuildEnumComponent(RenderTreeBuilder builder,PropertyInfo property)
    {
        Guid id = Guid.NewGuid();
        builder.AddMarkupContent(1, $"<label for=\"{id}\">{property.Name}</label>");
        builder.OpenElement(2, "select");
        builder.AddAttribute(3, "id", id.ToString());
        builder.AddAttribute(4, "Value", Enum.GetValues(property.PropertyType).GetValue(0));
        builder.AddAttribute(5, "ValueChanged", CreateCallback<Enum>(property));
        builder.AddAttribute(6, "ValueExpression", CreateExpression<Enum>(property));

        foreach (var value in Enum.GetValues(property.PropertyType))
        {
            builder.OpenElement(1, "option");
            builder.AddAttribute(2, "value", value.ToString());
            builder.CloseElement();
        }
        builder.CloseElement();
    }


    private void BuildComponent<PropertyType>(PropertyInfo property, RenderTreeBuilder builder, Type inputType)
    {
        var propertyValue = property.GetValue(Model);
        var id = Guid.NewGuid();
        builder.AddMarkupContent(0, $"<label for=\"{id}\">{property.Name}</label>");
        builder.OpenComponent(1, inputType);
        builder.AddAttribute(2, "id", id.ToString());
        builder.AddAttribute(3, "Value", propertyValue);
        builder.AddAttribute(5, "ValueChanged", CreateCallback<PropertyType>(property));
        builder.AddAttribute(6, "ValueExpression", CreateExpression<PropertyType>(property));
        builder.CloseComponent();
    }

    private EventCallback<PropertyType> CreateCallback<PropertyType>(PropertyInfo property)
    {
        return RuntimeHelpers.TypeCheck(EventCallback.Factory.Create(this, EventCallback.Factory.CreateInferred(this, __value => property.SetValue(Model, __value), (PropertyType)property.GetValue(Model))));
    }
  

    private Expression<Func<PropertyType>> CreateExpression<PropertyType>(PropertyInfo property)
    {
        var constant = Expression.Constant(Model, Model.GetType());
        var exp = Expression.Property(constant, property.Name);
        return Expression.Lambda<Func<PropertyType>>(exp);
    }

}
public分部类GenericForm:ComponentBase其中ViewModel:new()
{
[参数]
公共视图模型{get;set;}
public readonly PropertyInfo[]Properties=typeof(ViewModel).GetProperties();
[参数]公共事件回调OnValidSubmit{get;set;}
受保护的重写异步任务OnInitializedAsync()
{
if(Model==null)
{
模型=新的ViewModel();
}
wait base.OnInitializedAsync();
}
公共RenderFragment CreateComponent(PropertyInfo属性)=>builder=>
{
var typeCode=Type.GetTypeCode(property.PropertyType);
if(property.PropertyType.IsEnum)
{
BuildEnumComponent(生成器、属性);
}
其他的
{
开关(类型代码)
{
case TypeCode.Int32:
BuildComponent(属性、生成器、类型(输入编号));
打破
case TypeCode.Int64:
BuildComponent(属性、生成器、类型(输入编号));
打破
case TypeCode.Int16:
BuildComponent(属性、生成器、类型(输入编号));
打破
大小写类型代码。十进制:
BuildComponent(属性、生成器、类型(输入编号));
打破
大小写类型代码。字符串:
BuildComponent(属性、生成器、类型(输入文本));
打破
大小写类型代码。布尔值:
BuildComponent(属性、生成器、类型(InputCheckbox));
打破
案例类型代码.DateTime:
BuildComponent(属性、生成器、类型(输入日期));
打破
违约:
Console.WriteLine(“未知属性类型”);
打破
}
}
};
私有void BuildEnumComponent(RenderTreeBuilder、PropertyInfo属性)
{
Guid id=Guid.NewGuid();
AddMarkupContent(1,$“{property.Name}”);
建筑商。开放元素(2,“选择”);
builder.AddAttribute(3,“id”,id.ToString());
builder.AddAttribute(4,“值”,Enum.GetValues(property.PropertyType.GetValue(0));
AddAttribute(5,“ValueChanged”,CreateCallback(property));
AddAttribute(6,“ValueExpression”,CreateExpression(property));
foreach(Enum.GetValues(property.PropertyType)中的var值)
{
建造商开证条款(1,“期权”);
builder.AddAttribute(2,“value”,value.ToString());
builder.CloseElement();
}
builder.CloseElement();
}
私有void BuildComponent(PropertyInfo属性,RenderTreeBuilder,输入类型)
{
var propertyValue=property.GetValue(模型);
var id=Guid.NewGuid();
AddMarkupContent(0,$“{property.Name}”);
builder.OpenComponent(1,输入类型);
builder.AddAttribute(2,“id”,id.ToString());
builder.AddAttribute(3,“值”,propertyValue);
AddAttribute(5,“ValueChanged”,CreateCallback(property));
AddAttribute(6,“ValueExpression”,CreateExpression(property));
builder.CloseComponent();
}
私有EventCallback CreateCallback(PropertyInfo属性)
{
return RuntimeHelpers.TypeCheck(EventCallback.Factory.Create(this,EventCallback.Factory.Create推断(this,_值=>property.SetValue(Model,_值),(PropertyType)property.GetValue(Model));
}
私有表达式CreateExpression(PropertyInfo属性)
{
var constant=Expression.constant(Model,Model.GetType());
var exp=Expression.Property(常量,Property.Name);
返回表达式.Lambda(exp);
}
}
它在这一行崩溃:
returnexpression.Lambda(exp)出现此错误:
System.ArgumentException:“Backender.Core.Common.Enums.EntityFieldType”类型的表达式不能用于返回类型“System.Enum”
。EntityFieldType也是一个枚举。
有什么提示吗?

类型不匹配。在构建表达式时,使用作为所有枚举的父类型的
Enum
类型。但是,当使用组件时,它将接收从
Enum
派生的特定
Enum
。您应该为传递的枚举创建特定的表达式和回调,而不是为常规
enum
类型创建特定的表达式和回调


通常,不能用子类型替换泛型参数。只有当泛型参数是协变的,也就是说,当它是用类似….的东西定义的时候,才可以这样做。。。。。这是定义为
IEnumerable
IEnumerable
的情况,但对于
表达式

而言,通过使用更多反射来实现此功能的情况并非如此:

private void BuildEnumSelectComponent(PropertyInfo property, RenderTreeBuilder builder)
{
    // When the elementType that is rendered is a generic Set the propertyType as the generic type
    var elementType = typeof(InputSelectWithOptions<>);
        Type[] typeArgs = { property.PropertyType };
        elementType = elementType.MakeGenericType(typeArgs);
   
    // Activate the the Type so that the methods can be called
    var instance = Activator.CreateInstance(elementType);
    var Value = property.GetValue(Model);
    var id = Guid.NewGuid();
    builder.AddMarkupContent(0, $"<div><label for=\"{id}\">{property.Name}</label></div>");
    builder.OpenComponent(1, instance.GetType());
    builder.AddAttribute(2, "id", id.ToString());
    builder.AddAttribute(3, "Value", Value);


    var method = this.GetType().GetMethod("CreateCallback");
    method= method.MakeGenericMethod(typeArgs);
    var callback = method.Invoke(this, new object[] { property });

    builder.AddAttribute(4, "ValueChanged", callback);
    

    // Create an expression to set the ValueExpression-attribute.
    var constant = Expression.Constant(Model, Model.GetType());
    var exp = Expression.Property(constant, property.Name);
    var lamb = Expression.Lambda(exp);
    builder.AddAttribute(5, "ValueExpression", lamb);
    builder.AddAttribute(6, "ChildContent",
       new RenderFragment(builder =>
       {
                // when type is a enum present them as an <option> element 
                // by leveraging the component InputSelectOption
                var values = property.PropertyType.GetEnumValues();
               foreach (var val in values)
               {
                    //  Open the InputSelectOption component
                    builder.OpenComponent(0, typeof(InputSelectOption<string>));

                    // Set the value of the enum as a value and key parameter
                    builder.AddAttribute(1, nameof(InputSelectOption<string>.Value), val.ToString());
                   builder.AddAttribute(2, nameof(InputSelectOption<string>.Key), val.ToString());

                    // Close the component
                    builder.CloseComponent();
               }

       }));
    builder.CloseComponent();
}
现在很有魅力

var method = this.GetType().GetMethod("CreateCallback");
method= method.MakeGenericMethod(typeArgs);
var callback = method.Invoke(this, new object[] { property });