Overview
I am writing an MVC set that is intended to provide basic search/add/edit/delete functionality for an Entity Framework object. It also does some fancy things like supporting dropdowns filled from other tables using attributes.
The main object in play is an inheritable generic controller (AdminController<T>
where T
is the EF object) containing an array of AdminField<T>
, each element of which represents a field/property in T
. AdminController<T>
can generate an AdminViewModel<T>
object. Below is a Visio chart of the relationships between those objects.
The Problem
My problem is in the Views. To display values, I'm using the Html.EditorFor
/HiddenFor
/TextboxFor
helpers. A hyper-simplified view simply loops through the field and displays their values, allowing edits if they're editable fields. This looks like:
@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" />
}
Well, that's what I want it to look like. It works just fine with fields/properties that are reference types. With value types, however, I get a System.InvalidOperationException
error on the EditorFor
line: "Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions."
This is because the ExpressionFromField
method, which returns an expression accessing an instance of T
within the ViewModel, has a return type of Expression<Func<AdminViewModel<T>, object>>
. Because it returns an object type, boxing is forced when generating the expression so that instead of (for example) t => t.Count
, my method is actually returning t => Convert(t.Count)
.
Current Workaround
My current workaround is to have a second method defined as Expression<Func<AdminViewModel<T>, int>> IntExpressionFromField(AdminField<T> field)
. Because it returns an int
, it doesn't force boxing in the expression. However, it makes my views extremely ugly:
@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" />
}
Worse, this only supports 1 value type (int
). I'd also like to support decimal, long, and whatever else--preferably without having to create custom methods for each as well as all the logic to safeguard against other types.
Help!
I'd love to find an elegant, easy way to resolve this issue. If none exists, I think my next step would be to stop using the Html
helper methods; however, this would require breaking consistency with the rest of the application and I don't even know that it would solve the issue.
Any help would be much appreciated!