Reputation: 19664
I have a view model that looks like this:
public class EventVm
{
public int Id { get; set; }
public int GroupId { get; set; }
public string Title { get; set; }
public EventLayout EventLayout { get; set; }
}
EventLayout is a custom object that looks like this:
public class EventLayout
{
private const string SingleColumnLayoutLocalKey = "MyOrg.SingleColumnLayout";
...
//Removed for breviety
public static EventLayout SingleColumnLayout = new EventLayout(SingleColumnLayoutLocalKey);
...
//Removed for breviety
public string Value
{
get { return _layoutLocalKey; }
}
private readonly string _layoutLocalKey;
private EventLayout(string layoutLocalKey)
{
_layoutLocalKey = layoutLocalKey;
}
public static EventLayout LayoutFromLocalString(string localString)
{
...
}
public override string ToString()
{
return _layoutLocalKey;
}
public override bool Equals(object obj)
{
if (obj.GetType() != this.GetType())
{
return false;
}
if (this._layoutLocalKey == obj.ToString())
{
return true;
}
return false;
}
public override int GetHashCode()
{
return this._layoutLocalKey.GetHashCode();
}
}
Basically EventLayout is just a custom enumeration that provides string backing store. EventLayout is bound to a Select form control (T() is just a localization extension method):
<select id="eventLayoutSelect" name="EventVm.EventLayout">
@foreach (var option in Model.EventLayoutOptions)
{
<option value="@option.Value" @((Model.EventLayout != null && Model.EventLayout.Equals(option))
? "selected=selected"
: string.Empty)>@T(option.Value)</option>
}
</select>
When I POST this form to the server, EventVm.EventLayout property is null when the action attempts to bind. However I can see that an EventLayout instance is POSTed in the form data:
My action looks like this:
[HttpPost]
public ActionResult Update(EventVm eventVm)
{
_eventService.UpdateEvent(eventVm);
return RedirectToAction("Index", new { groupId = eventVm.GroupId });
}
Can someone tell me what I've done wrong please?
Upvotes: 2
Views: 2427
Reputation: 1186
Replace your foreach loop with for loop like below
@foreach (var option in Model.EventLayoutOptions)
{
<option value="@option.Value" @((Model.EventLayout != null && Model.EventLayout.Equals(option))
? "selected=selected"
: string.Empty)>@T(option.Value)</option>
}
with
for(int i=0; i<Model.EventLayoutOptions.Count; i++ )
{
<option value="@Model.EventLayoutOptions[i].Value" @((Model.EventLayout != null && Model.EventLayout.Equals(Model.EventLayoutOptions[i]))
? "selected=selected"
: string.Empty)>@T(Model.EventLayoutOptions[i].Value)</option>
}
Upvotes: 0
Reputation: 19664
A custom binding solved the problem. Thank you all for the helpful comments.
Brad Christie pointed out that because the object that my action is attempting to bind to only has a private constructor a custom binding would be needed.
Custom binding:
public class EventVmBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
HttpRequestBase request = controllerContext.HttpContext.Request;
var eventVm = new EventVm()
{
Id = Int32.Parse(request.Form.Get("Id")),
GroupId = Int32.Parse(request.Form.Get("GroupId")),
Title = request.Form.Get("Title"),
HeaderMarkup = request.Form.Get("HeaderMarkup"),
LeftNavigationMarkup = request.Form.Get("LeftNavigationMarkup"),
CenterContentMarkup = request.Form.Get("CenterContentMarkup"),
RightNavigationMarkup = request.Form.Get("RightNavigationMarkup"),
EventLayout = EventLayout.LayoutFromLocalString(request.Form.Get("EventLayout")),
DisplayOrder = Int32.Parse(request.Form.Get("DisplayOrder")),
Active = request.Form.Get("Active").As<bool>(),
CanEdit = request.Form.Get("CanEdit").As<bool>()
};
return eventVm;
}
}
Wiring it up at the action:
[HttpPost]
public ActionResult Create([ModelBinder(typeof(EventVmBinder))]EventVm eventVm)
{
_groupService.AddEventToGroup(eventVm);
return RedirectToAction("Index", new {groupId = eventVm.GroupId});
}
Upvotes: 1