可重用的 MVC 代码:处理 linq 表达式中的值类型
Reusable MVC code: handling value types in linq expressions
概览
我正在编写一个 MVC 集,旨在为 Entity Framework 对象提供基本的 search/add/edit/delete 功能。它还做了一些奇特的事情,比如支持使用属性从其他表中填充的下拉菜单。
游戏中的主要对象是一个可继承的通用控制器(AdminController<T>
,其中 T
是 EF 对象)包含一个 AdminField<T>
数组,每个元素代表一个 field/property 在 T
。 AdminController<T>
可以生成一个 AdminViewModel<T>
对象。下面是这些对象之间关系的 Visio 图表。
问题
我的问题出在视图中。为了显示值,我使用了 Html.EditorFor
/HiddenFor
/TextboxFor
助手。一个超级简化的视图简单地循环遍历字段并显示它们的值,如果它们是可编辑字段则允许编辑。这看起来像:
@model AdminViewModel<ExampleEFObject>
@using (Html.BeginForm())
{
foreach (var f in Model.Fields)
{
var expression = Model.ExpressionFromField(f);
<label class="control-label col-md-3">@f.DisplayName</label>
@if (!f.Editable)
{
@Html.TextBoxFor(expression, new { @class = "form-control", @disabled = "disabled" })
}
else
{
@Html.EditorFor(expression, new { htmlAttributes = new { @class = "form-control" } })
}
}
<input type="submit" value="Save" class="btn btn-primary" />
}
好吧,这就是我想要它的样子。它与作为引用类型的 fields/properties 一起工作得很好。但是,对于值类型,我在 EditorFor
行收到 System.InvalidOperationException
错误:“Templates can be used only with field access, 属性 access, single-dimensional array索引,或单参数自定义索引器表达式。"
这是因为 ExpressionFromField
方法 return 是一个在 ViewModel 中访问 T
实例的表达式,其 return 类型为 Expression<Func<AdminViewModel<T>, object>>
.因为它 return 是一个对象类型,所以在生成表达式时强制装箱,所以我的方法实际上是 returning t => Convert(t.Count)
而不是(例如)t => t.Count
。 =33=]
当前解决方法
我目前的解决方法是将第二种方法定义为 Expression<Func<AdminViewModel<T>, int>> IntExpressionFromField(AdminField<T> field)
。因为它 return 是一个 int
,所以它不会在表达式中强制装箱。然而,这让我的观点变得极其丑陋:
@model AdminViewModel<ExampleEFObject>
@using (Html.BeginForm())
{
foreach (var f in Model.Fields)
{
var expression = Model.ExpressionFromField(f);
var intExpression = Model.IntExpressionFromField(f);
<label class="control-label col-md-3">@f.DisplayName</label>
@if (!f.Editable)
{
if (intExpression == null)
{
@Html.TextBoxFor(expression, new { @class = "form-control", @disabled = "disabled" })
}
else
{
@Html.TextBoxFor(intExpression, new { @class = "form-control", @disabled = "disabled" })
}
}
else
{
if (intExpression == null)
{
@Html.EditorFor(expression, new { htmlAttributes = new { @class = "form-control" } })
}
else
{
@Html.EditorFor(intExpression, new { htmlAttributes = new { @class = "form-control" } })
}
}
}
<input type="submit" value="Save" class="btn btn-primary" />
}
更糟糕的是,这只支持 1 种值类型 (int
)。我还想支持 decimal、long 和其他任何东西——最好不必为每个创建自定义方法以及所有逻辑来防止其他类型。
求助!
我很想找到一种优雅、简单的方法来解决这个问题。如果 none 存在,我想我的下一步是停止使用 Html
辅助方法;但是,这需要打破与应用程序其余部分的一致性,我什至不知道它能解决问题。
如有任何帮助,我们将不胜感激!
无法使用我现有的方法解决此问题,我尝试了最后提到的方法:放弃 HtmlHelper
方法。这很有效,我希望我很久以前就这样做了。
MVC ViewModel 绑定是使用 "name" 属性 完成的,因此要手动绑定到我的模型,我需要获取不合格的 属性 名称(即表示为 cust => cust.Records.Address.State
,我需要字符串 SelectedItem.Records.Address.State
(其中 SelectedItem
是包含显示值的 ViewModel 对象)。我的 AdminField<T>
对象已经有一个 MemberExpression
属性,所以我只是使用了 ToString()
方法,删除了参数,并在 ViewModel 对象名称前添加了前缀。丑陋,我知道,但可靠且简单。
之前,我需要表达式来提供 HtmlHelper
对象,但现在我只需要值,所以我用更简单的 GetValue()
方法替换了我的 ExpressionFromField()
方法 returns string
值。重要的是 returns a string
;此转换允许我读取和写入所有值类型(因为 ViewModel 绑定处理将编辑后的值转换回原始 属性 类型)。
我的新视图是这样的:
@model AdminViewModel<ExampleEFObject>
@using (Html.BeginForm())
{
foreach (var f in Model.Fields)
{
var propertyName = f.PropertyName(f); //unqualified, can contain multiple parts
var value = Model.GetValue(f); //uses the field expression to retrieve the value
//of a "T SelectedItem" object in the ViewModel
<label class="control-label col-md-3">@f.DisplayName</label>
@if (!f.Editable)
{
<input class="form-control" disabled="disabled" name="@propertyName" type="text" value="@value" />
}
else
{
<input class="form-control" name="@propertyName" type="text" value="@value" />
}
}
<input type="submit" value="Save" class="btn btn-primary" />
}
干净多了!
我用这种方法失去的一件事是验证。从这里,我可以添加 javascript 验证或创建模板来复制 HtmlHelper
自动添加的数据验证。不管怎样,没什么大不了的。
希望这对某人有所帮助。
注意:@Ivan Stoeve 推荐使用 Html.Editor
/Html.Textbox
等,它们通过 属性 名称而不是表达式接受绑定。这仍然让我没有视图中的强类型表达式,但它给了我返回验证,所以我认为值得调整它。它还将清除我在添加下拉选项时必须做的一些丑陋的字符串操作。 :)
概览
我正在编写一个 MVC 集,旨在为 Entity Framework 对象提供基本的 search/add/edit/delete 功能。它还做了一些奇特的事情,比如支持使用属性从其他表中填充的下拉菜单。
游戏中的主要对象是一个可继承的通用控制器(AdminController<T>
,其中 T
是 EF 对象)包含一个 AdminField<T>
数组,每个元素代表一个 field/property 在 T
。 AdminController<T>
可以生成一个 AdminViewModel<T>
对象。下面是这些对象之间关系的 Visio 图表。
问题
我的问题出在视图中。为了显示值,我使用了 Html.EditorFor
/HiddenFor
/TextboxFor
助手。一个超级简化的视图简单地循环遍历字段并显示它们的值,如果它们是可编辑字段则允许编辑。这看起来像:
@model AdminViewModel<ExampleEFObject>
@using (Html.BeginForm())
{
foreach (var f in Model.Fields)
{
var expression = Model.ExpressionFromField(f);
<label class="control-label col-md-3">@f.DisplayName</label>
@if (!f.Editable)
{
@Html.TextBoxFor(expression, new { @class = "form-control", @disabled = "disabled" })
}
else
{
@Html.EditorFor(expression, new { htmlAttributes = new { @class = "form-control" } })
}
}
<input type="submit" value="Save" class="btn btn-primary" />
}
好吧,这就是我想要它的样子。它与作为引用类型的 fields/properties 一起工作得很好。但是,对于值类型,我在 EditorFor
行收到 System.InvalidOperationException
错误:“Templates can be used only with field access, 属性 access, single-dimensional array索引,或单参数自定义索引器表达式。"
这是因为 ExpressionFromField
方法 return 是一个在 ViewModel 中访问 T
实例的表达式,其 return 类型为 Expression<Func<AdminViewModel<T>, object>>
.因为它 return 是一个对象类型,所以在生成表达式时强制装箱,所以我的方法实际上是 returning t => Convert(t.Count)
而不是(例如)t => t.Count
。 =33=]
当前解决方法
我目前的解决方法是将第二种方法定义为 Expression<Func<AdminViewModel<T>, int>> IntExpressionFromField(AdminField<T> field)
。因为它 return 是一个 int
,所以它不会在表达式中强制装箱。然而,这让我的观点变得极其丑陋:
@model AdminViewModel<ExampleEFObject>
@using (Html.BeginForm())
{
foreach (var f in Model.Fields)
{
var expression = Model.ExpressionFromField(f);
var intExpression = Model.IntExpressionFromField(f);
<label class="control-label col-md-3">@f.DisplayName</label>
@if (!f.Editable)
{
if (intExpression == null)
{
@Html.TextBoxFor(expression, new { @class = "form-control", @disabled = "disabled" })
}
else
{
@Html.TextBoxFor(intExpression, new { @class = "form-control", @disabled = "disabled" })
}
}
else
{
if (intExpression == null)
{
@Html.EditorFor(expression, new { htmlAttributes = new { @class = "form-control" } })
}
else
{
@Html.EditorFor(intExpression, new { htmlAttributes = new { @class = "form-control" } })
}
}
}
<input type="submit" value="Save" class="btn btn-primary" />
}
更糟糕的是,这只支持 1 种值类型 (int
)。我还想支持 decimal、long 和其他任何东西——最好不必为每个创建自定义方法以及所有逻辑来防止其他类型。
求助!
我很想找到一种优雅、简单的方法来解决这个问题。如果 none 存在,我想我的下一步是停止使用 Html
辅助方法;但是,这需要打破与应用程序其余部分的一致性,我什至不知道它能解决问题。
如有任何帮助,我们将不胜感激!
无法使用我现有的方法解决此问题,我尝试了最后提到的方法:放弃 HtmlHelper
方法。这很有效,我希望我很久以前就这样做了。
MVC ViewModel 绑定是使用 "name" 属性 完成的,因此要手动绑定到我的模型,我需要获取不合格的 属性 名称(即表示为 cust => cust.Records.Address.State
,我需要字符串 SelectedItem.Records.Address.State
(其中 SelectedItem
是包含显示值的 ViewModel 对象)。我的 AdminField<T>
对象已经有一个 MemberExpression
属性,所以我只是使用了 ToString()
方法,删除了参数,并在 ViewModel 对象名称前添加了前缀。丑陋,我知道,但可靠且简单。
之前,我需要表达式来提供 HtmlHelper
对象,但现在我只需要值,所以我用更简单的 GetValue()
方法替换了我的 ExpressionFromField()
方法 returns string
值。重要的是 returns a string
;此转换允许我读取和写入所有值类型(因为 ViewModel 绑定处理将编辑后的值转换回原始 属性 类型)。
我的新视图是这样的:
@model AdminViewModel<ExampleEFObject>
@using (Html.BeginForm())
{
foreach (var f in Model.Fields)
{
var propertyName = f.PropertyName(f); //unqualified, can contain multiple parts
var value = Model.GetValue(f); //uses the field expression to retrieve the value
//of a "T SelectedItem" object in the ViewModel
<label class="control-label col-md-3">@f.DisplayName</label>
@if (!f.Editable)
{
<input class="form-control" disabled="disabled" name="@propertyName" type="text" value="@value" />
}
else
{
<input class="form-control" name="@propertyName" type="text" value="@value" />
}
}
<input type="submit" value="Save" class="btn btn-primary" />
}
干净多了!
我用这种方法失去的一件事是验证。从这里,我可以添加 javascript 验证或创建模板来复制 HtmlHelper
自动添加的数据验证。不管怎样,没什么大不了的。
希望这对某人有所帮助。
注意:@Ivan Stoeve 推荐使用 Html.Editor
/Html.Textbox
等,它们通过 属性 名称而不是表达式接受绑定。这仍然让我没有视图中的强类型表达式,但它给了我返回验证,所以我认为值得调整它。它还将清除我在添加下拉选项时必须做的一些丑陋的字符串操作。 :)