优化用于 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]));
在我的应用程序中,我有几个(>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]));