【问题标题】:How can I dynamically build an Enum select with Blazor?如何使用 Blazor 动态构建枚举选择?
【发布时间】:2020-11-20 11:15:18
【问题描述】:

我正在尝试使用 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);
    }

}

它在这一行崩溃:return Expression.Lambda&lt;Func&lt;PropertyType&gt;&gt;(exp); 并出现此错误:System.ArgumentException: 'Expression of type 'Backender.Core.Common.Enums.EntityFieldType' cannot be used for return type 'System.Enum''。 EntityFieldType 也是一个枚举。 有什么建议吗?

【问题讨论】:

    标签: c# .net-core reflection blazor blazor-server-side


    【解决方案1】:

    存在类型不匹配。在构建表达式时,您使用 Enum 类型,它是所有枚举的父类型。然而,当你的组件被使用时,它会收到一个特定的Enum,它是Enum 的后代。您应该创建特定于传递的枚举的表达式和回调,而不是一般的 Enum 类型。

    一般来说,您不能将泛型参数替换为子类型。仅当泛型参数是协变时才可以做到这一点,也就是说,当它用类似的东西定义时......这是IEnumerable&lt;T&gt;的情况,它被定义为IEnumerable&lt;out T&gt;,但不会发生@ 987654327@.

    【讨论】:

      【解决方案2】:

      设法通过使用更多反射来完成这项工作:

      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();
      }
      

      前面代码的问题是我们不知道 Enum 的具体类型(就像 Francesco 说的那样)。但是我们可以通过在运行时定义泛型参数来使用反射来利用现有的 CreateCallback 方法:

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

      现在就像一个魅力。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-09-21
        • 1970-01-01
        • 1970-01-01
        • 2020-06-11
        • 1970-01-01
        • 2020-09-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多