Nick
Nick

Reputation: 19664

Asp.Net MVC 4 - Binding to complex object not working in POST

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:

enter image description here

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

Answers (2)

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

Nick
Nick

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

Related Questions