优化用于 MVC InputFor 的 lambda 表达式时出现模板错误

Template error when refining a lambda expression for use in MVC InputFor

在我的应用程序中,我有几个(>10)个地方有这样的模型:

public interface IOptionList
{
    string Name;
    bool Checked;
}

public class Option : IOptionList { }

public class MyModel
{
    public Option[] Options = 
    {
        new Option { Name = "Option 1", Checked = false }
    };

    // etc... for many more implementations of IOptionList
}

这些用于在视图中生成复选框列表,如下所示:

@for (int i = 0; i < Model.Options.Length; i++)
{
    <div>
        @Html.CheckBoxFor(x => x.Options[i].Checked)
        @Html.LabelFor(x => x.Options[i].Checked, Model.Options[i].Name)
    </div>
}

因为它被使用了很多次,所以我想通过编写一个 HtmlHelper 扩展来生成这样的列表来简化我的模型:

@Html.CheckBoxFormGroupFor(x => x.Options, Model.Options)

我目前的尝试是这样的:

public static CheckBoxFormGroupFor<TModel, TItem>(this HtmlHelper<TModel>html,
    Expression<Func<TModel, TItem[]>> expression, TItem[] values)
{
    var sb = new StringBuilder();

    for (int i = 0; i < values.Length; i++)
    {
        var indexExpression = Expression.ArrayIndex(expression.Body, Expression.Constant(i));
        var checkedAccessExpression = Expression.Property(indexExpression, typeof(ICheckboxList), "Checked");
        var lambda = Expression.Lambda<Func<TModel, bool>>(checkedAccessExpression, Expression.Parameter(typeof(TModel)));
        var cbx = html.CheckBoxFor(x => lambda.Compile()(x));
        var lbl = html.LabelFor(x => lambda.Compile()(x), values[i].Name);

        var div = new TagBuilder("div");
        div.InnerHtml = $"{cbx}{lbl}";
        sb.Append(div);
    }
    return new HtmlString(sb.ToString());
}

据我所知,它扩展了初始 lambda 表达式 x => x.Options 以访问正确数组索引处对象的正确 属性,但是这给了我一个模板错误消息

Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.

据我所知,我只是在做一个单维数组索引,然后是 属性 访问,所以我不确定为什么会看到这个。我之前尝试过 var cbx = html.CheckBoxFor(lambda); 但这不起作用,因为参数 x 仅在视图范围内定义。

假设我想要的是可能的,谁能帮助我实现它?我是用这种方式操作 Expressions 的新手。

你很接近。直接使用 lambda 变量

var cbx = html.CheckBoxFor(lambda);
var lbl = html.LabelFor(lambda, values[i].Name);

确实是正确的方法。只需确保您正在编写的 lambda 表达式使用与 expression 参数相同的参数(因为它绑定到新表达式中使用的正文),即此处

var lambda = Expression.Lambda<Func<TModel, bool>>(
    checkedAccessExpression,
    Expression.Parameter(typeof(TModel)));

Expression.Parameter(typeof(TModel)替换为expression.Parameters[0]:

var lambda = Expression.Lambda<Func<TModel, bool>>(
    checkedAccessExpression,
    expression.Parameters[0]));