构建可以输出枚举,小数和字符串的LINQ lambda表达式

时间:2017-07-20 20:47:45

标签: c# asp.net-mvc linq razor linq-expressions

我正在为我的应用编写一个可重复使用的视图。它使用通用的ViewModel DynSingleView<T>;您插入对象类型,视图将生成属性UI组件以更新它们。它有点复杂,因为它不是实际包含要编辑的数据的ViewModel,而是包含DisplayValue个对象的列表,每个对象代表一个属性(并包含元数据)。可以使用这些(通过PropertyInfo.GetValue()方法)从通用对象中检索值。

主要障碍是使用Html.EditorFor方法。使用它将为我在阅读和写入房产方面节省大量的工作。但是,要使用它,我需要为它提供一个表示简单属性/字段访问的lambda表达式(即model => model.Item.Property)。

我在ViewModel中使用以下方法生成表达式:

    /// <summary>
    /// Given a property, returns a lambda expression that returns that property.
    /// </summary>
    /// <param name="dv">DisplayValue object, which is the property</param>
    /// <returns></returns>
    public Expression<Func<DynSingleView<T>, TType>> GetExpression<TType>(DisplayValue dv)
    {
        var param = Expression.Parameter(typeof(DynSingleView<T>));
        var instance = Expression.Property(param, nameof(DynSingleView<T>.Item));
        var propertyCall = Expression.Property(instance, dv.PropertyName);
        var lambda = Expression.Lambda<Func<DynSingleView<T>, TType>>(propertyCall, param);
        return lambda;
    }

当涉及枚举时,这会生成所需的lambda完全除外。当我检索枚举属性时,我在lambda声明行上收到以下错误:Expression of type MyProject.Model.Enums.MyEnum cannot be used for return type 'System.Enum'.

我甚至无法将其转换为lambda中的对象,因为lambda需要是一个简单的属性访问。

@Jose帮助组合了一个linq表达式,该表达式“欺骗”了lambda表达式而没有出现错误并返回类型'System.Enum'(使用该类型作为'Html.EnumDropDownListFor'的泛型参数),但是在Return type 'System.Enum' is not supported.'行生成错误:Html.EnumDropDownListFor

这是通用视图中的表格:

<table border="1">
    @{
        foreach (var dv in Model.DisplayValues)
        {
            @:<tr><td>@dv.PropertyDisplayName</td><td>
            switch (dv.Type)
            {
                case DisplayValueType.Decimal:
                    @Html.EditorFor(Model.GetExpression<decimal>(dv));
                    break;
                case DisplayValueType.String:
                case DisplayValueType.Other:
                    @Html.EditorFor(Model.GetExpression<object>(dv));
                    break;
                //this is where I'm having trouble
                case DisplayValueType.PickList:
                    @Html.EnumDropDownListFor(Model.GetExpression<object>(dv));
                    break;
            }
            @:</td></tr>
        }
    }
    <tr></tr>
</table>

似乎我能够完成这项工作的唯一方法是指定确切的枚举类型,这使得整个工作完全没用。

请帮忙!

4 个答案:

答案 0 :(得分:2)

我90%肯定你想要的是这个

foreach (var dv in Model.DisplayValues)
        {
            @:<tr>
                <td>@dv.PropertyDisplayName</td>
                <td>
                switch (dv.Type)
                {
                    case DisplayValueType.Decimal:
                    case DisplayValueType.String:
                    case DisplayValueType.Other:
                    @Html.Editor("Item."+dv.PropertyName);
                    case DisplayValueType.PickList:
                    @Html.DropDownList("Item."+dv.PropertyName,EnumHelper.GetSelectList(Model.Item.GetType().GetProperty(dv.PropertyName).PropertyType));
                                                                                  break;
                }
                @:
            </td>
        </tr>
            }

答案 1 :(得分:0)

如果您想进行静态类型检查,则必须将每个enum视为一个单独的类型(就像您执行DecimalString)并给他们自己的案例相同,除非对于类型。

答案 2 :(得分:0)

  

您插入对象类型,视图将生成属性UI组件以更新它们。

您可以使用EditorTemplates执行此操作。为要编辑的每种类型创建EditorTemplate。 MVC在运行时解析这些,因此您可以有10种不同的类型,它们将为每个类型生成不同的HTML:

动作

List<object> model = new List<object>
{
    "hello world",
    123,
    true,
};

return View(model);

View在运行时选择EditorTemplate

@model List<object>

@for (int i = 0; i < Model.Count; i++)
{
    @Html.EditorFor(m => m[i])   // textbox, numerical textbox, then checkbox
}

〜/查看/共享/ EditorTemplates / String.cshtml

@model string

@Html.TextBoxFor(m => m)

等等。这是一个极端的例子 - 通常你会使用基类而不是对象。

回发后可能会出现问题,默认情况下MVC模型活页夹可能无法识别不同的类型。您可以通过编写自定义模型绑定器来解决这个问题。

答案 3 :(得分:-1)

您可以将属性转换为TType,然后将其用作lambda的参数。

var convert = Expression.Convert(propertyCall, typeof(TType));
var lambda = Expression.Lambda<Func<DynSingleView<T>, TType>>(convert, param);