I'm experimenting with custom ModelMetadataProvider. It would appear that some of the html helpers like TextBoxFor use these just fine. However, in other cases like DropDownListFor, they favor ViewData instead. For example, looking at some reflected code I see:
bool flag = false;
if (selectList == null)
{
selectList = SelectExtensions.GetSelectData(htmlHelper, name);
flag = true;
}
object defaultValue = allowMultiple ? htmlHelper.GetModelStateValue(fullHtmlFieldName, typeof (string[])) : htmlHelper.GetModelStateValue(fullHtmlFieldName, typeof (string));
if (defaultValue == null && !string.IsNullOrEmpty(name))
{
if (!flag)
defaultValue = htmlHelper.ViewData.Eval(name);
else if (metadata != null)
defaultValue = metadata.Model;
}
Note all the different attempts to get "defaultValue". Using metadata.Model is dead last. Why the separation here? If you trace that code thru you eventually end up at a call to ViewData.Eval, which as a fall back just uses reflection to get the value out of the model anyway. Is there such a thing as a custom ViewData provider to bridge that gap?
EDIT: I'm beginning to lean toward the idea that this is a bug in the framework.
Consider two pieces of code:
@Html.DropDownListFor(model => model.ErrorData.Shift, Model.ShiftOptions, new { @class = "form-control" })
The code above passes in the options "Model.ShiftOptions". Because of this it doesn't pass the condition "selectList==null" and consequently "flag" is never set and instead proceeds to try to get the default value from only the type via reflection (the Eval call).
However with this code:
@{ ViewData[Html.NameFor(m => m.ErrorData.Shift).ToString()] = Model.ShiftOptions;}
@Html.DropDownListFor(model => model.ErrorData.Shift,null, new { @class = "form-control" })
..."flag" is now satisfied and the default value is now retrieved metadata.Model. Why would different mechanisms for providing the list options change (or even influence for that matter) where the default value is retrieved from?
Edit #2
Warning: The above ViewData "fix" does not work if the DropDownListFor is called in an editor template (EditorFor) for a complex type. The NameFor call will return the name of the property INCLUDING the outer context that the EditorFor was called from, ie MyViewModel.ErrorData.Shift. However, the code for DropDownListFor in the orginal snip at the top looks for a ViewData item WITHOUT the original context, ie ErrorData.Shift. They both use
ExpressionHelper.GetExpressionText((LambdaExpression) expression)
However, NameOf uses html.Name on that result. When DDLF finally gets around to generating its name, it does something similar so it's name is correct, but it makes no sense that it doesn't include it's full context when looking for a view data option.