views:

34

answers:

2

Hi,

I trying to return the same model back to the view that edited the model. Making it sort of like Word or something with ctrl+s functionality for saving the mode. This works fine though the model that is returned to the view contains a bunch of nulls for some stupid reason. Is it because things were not serialized properly when the controller got the view model back or am I handling MVC the wrong way?

This is the Model

public class EditInvoiceModel
{
    private readonly IEnumerable<Customer> _customers;

    public EditInvoiceModel()
    {
        CreateProduct = new Product { Invoice = Invoice };
        CreateWorkday = new Workday { Invoice = Invoice };
    }

    public EditInvoiceModel(Invoice invoice, IEnumerable<Customer> customers)
    {
        Invoice = invoice;
        _customers = customers;

        Customers = _customers.Select(x =>
                                      new SelectListItem
                                        {
                                            Selected = x.Id == Invoice.CustomerID,
                                            Text = x.Name,
                                            Value = x.Id.ToString()
                                        });

        Products = Invoice.Products;
        Workdays = Invoice.Workdays;

        CreateProduct = new Product {Invoice = Invoice};
        CreateWorkday = new Workday { Invoice = Invoice };
    }

    public IEnumerable<SelectListItem> Customers { get; set; }
    public IEnumerable<Product> Products { get; set; }
    public IEnumerable<Workday> Workdays { get; set; }
    public Product CreateProduct { get; set; }
    public Workday CreateWorkday { get; set; }
    public Invoice Invoice { get; set; }
}

And this is the controller action that returns the model back to the same view.

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(EditInvoiceModel invoiceModel)
{
    try
    {
        _repository.UpdateInvoice(invoiceModel.Invoice);
    }
    catch (Exception ex)
    {
        Log.Error(ex);
    }

    return View(invoiceModel);
}

All properties except the Invoice is null when this is returned to the view. I have no idea why this happens. Hope someone can help.

The problem that occurs (in the view) is the following: This is not because of a typo since it is working fine the first time the view is run. This must be to a problem with the modelbinder or my usage of the model binder.

The ViewData item that has the key 'Invoice.CustomerID' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. 
Exception Details: System.InvalidOperationException: The ViewData item that has the key 'Invoice.CustomerID' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'.

Source Error: 

Line 28:        <div class="editor-field">
Line 29:            <%: Html.DropDownListFor(x => x.Invoice.CustomerID, Model.Customers)%>
Line 30:            <%: Html.ValidationMessageFor(x => x.Invoice.CustomerID)%>
Line 31:        </div>

Lastly part of the view that displays the view model.

<%@ page language="C#" masterpagefile="~/Views/Shared/Site.Master" 
    inherits="System.Web.Mvc.ViewPage<FakturaLight.WebClient.Models.EditInvoiceModel>" %>
<asp:content id="Content2" contentplaceholderid="MainContent" runat="server">
    <%= Html.ValidationSummary() %>
    <% using (Html.BeginForm())
    { %>
    <%= Html.AntiForgeryToken() %>
    <div class="content-left">
        <%: Html.EditorFor(x => x.Invoice) %>
        <div class="editor-label">
            <%: Html.LabelFor(x => x.Invoice.CustomerID)%>
        </div>
        <div class="editor-field">
            <%: Html.DropDownListFor(x => x.Invoice.CustomerID, Model.Customers)%>
            <%: Html.ValidationMessageFor(x => x.Invoice.CustomerID)%>
        </div>
    <% } %>
    </div>
    <div class="content-right" id="details" style=" clear:both;">
        <div id="workdays">
            <%: Html.DisplayFor(x => x.Workdays) %>
        </div>
        <div id="products">
            <%: Html.DisplayFor(x => x.Products) %>
        </div>
    </div>
</asp:content>
+1  A: 

The reason why only the Invoice property is populated is because in your form you are only having input fields and dropdown lists for it. The model binder populates properties from what's sent in the request. You are posting a form which contains values only for the Invoice. As far as the Workdays and Products properties are concerned you are only displaying them (Html.DisplayFor) and they are never sent to the server. Also the model binder invokes the default constructor of your model which doesn't initialize those properties neither, so they are null at postback.

Darin Dimitrov
Ok, that answers my question. Back to the drawing board I guess I have to create a new modelbinder for this. I can return the Edit(int id) action result no problem but then the user might have to retype changes if server side validation fails or I just retrieve the needed data again. Thanks!
mhenrixon
+1  A: 

Darin is correct. Remember that you are still working with a disconnected client. This is just HTML and HTTP under the covers. The model binder is only able to bind values that are pushed to the server in the HTTP POST. All other properties for the class will receive no assignment, so if you want a more complete model pushed back to the browser in response in the

return View(invoiceModel);

you will need to complete those property assignments on the server side within your controller or with your repository's update method perhaps.

Tyler Jensen