概述
我正在编写一个MVC集,旨在为Entity Framework对象提供基本的搜索/添加/编辑/删除功能。它还做了一些奇特的事情,比如支持使用属性从其他表中填充的下拉列表。
正在使用的主要对象是一个可继承的通用控制器(AdminController<T>
,其中T
是EF对象),包含一个AdminField<T>
数组,其中每个元素代表一个字段/属性。 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" />
}
嗯,这就是我想要它的样子。它适用于作为引用类型的字段/属性。但是,对于值类型,我在System.InvalidOperationException
行上出现EditorFor
错误:“模板只能用于字段访问,属性访问,单维数组索引或单参数自定义索引器表达式。“
这是因为ExpressionFromField
方法返回一个访问ViewModel中T
实例的表达式,其返回类型为Expression<Func<AdminViewModel<T>, object>>
。因为它返回一个对象类型,所以在生成表达式时强制装箱,以便代替(例如)t => t.Count
,而我的方法实际上返回t => Convert(t.Count)
。
当前解决方法
我目前的解决方法是将第二种方法定义为Expression<Func<AdminViewModel<T>, int>> IntExpressionFromField(AdminField<T> field)
。因为它返回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和其他任何东西 - 最好不必为每个方法创建自定义方法以及防止其他类型的所有逻辑。
帮助!
我很想找到一种优雅,简单的方法来解决这个问题。如果不存在,我认为我的下一步将是停止使用Html
辅助方法;但是,这需要与应用程序的其余部分打破一致性,我甚至不知道它会解决问题。
非常感谢任何帮助!
答案 0 :(得分:0)
无法使用我现有的方法解决此问题,我尝试了最后提到的方法:放弃HtmlHelper
方法。这很有效,我希望我很久以前就完成了。
使用&#34;名称&#34;完成MVC ViewModel绑定。属性,所以要手动绑定到我的模型我需要获取非限定属性名称(即对于表示为cust => cust.Records.Address.State
的字段,我需要字符串SelectedItem.Records.Address.State
(其中SelectedItem
是ViewModel对象包含显示的值。)我的AdminField<T>
对象已经有MemberExpression
属性,所以我只使用了ToString()
方法,删除了参数,并添加了ViewModel对象名称。丑,我知道,但又可靠又容易。
之前,我需要表达式来提供HtmlHelper
对象,但现在我只需要该值,因此我使用更简单的ExpressionFromField()
方法替换了GetValue()
方法,该方法返回{ {1}}价值。它返回string
非常重要;此转换允许我读取和写入所有值类型(因为ViewModel绑定处理已编辑值到原始属性类型的转换)。
我的新观点如下:
string
更干净!
这种方法让我失去的一件事就是验证。从这里,我可以添加javascript验证或创建模板来复制@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" />
}
自动添加的数据验证。无论哪种方式,没什么大不了的。
希望这有助于某人。
注意:@ Ivan Stoeve建议使用HtmlHelper
/ Html.Editor
等,它通过属性名称而不是表达式接受绑定。这仍然让我没有在视图中的强类型表达式,但它让我回到验证,所以我认为值得调整它。它还将清除添加下拉选项时我必须做的一些丑陋的字符串操作。 :)