user1104203
user1104203

Reputation: 167

Passing Model Through POST

I'm having some problems with one specific part of my ASP.NET site. This page is essentially where a user edits an invoice and then posts the changes.

At this point I'm doing a re-write of an existing page, which works. I cannot figure out what the difference is other than the other one is older MVC 3.

When I first go to the EditInvoice Action:

public ActionResult EditInvoice()
{
    SalesDocument invoice = null;

    try
    {
        invoice = SomeService.GetTheInvoice();
    }
    catch (Exception ex)
    {
        return HandleControllerException(ex);
    }

    return View("InvoiceEdit", invoice.LineItems);
}

The model of the "InvoiceEdit" view is the List

Now the view loads fine, and displays all the document line items in a form:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<List<MyApp.SalesDocumentLineItem>>" %>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <% using (Html.BeginForm()) {%>

    <fieldset>
        <legend>Line Items</legend>
            <div id="lineItems" class="table">
                <div class="row">
                    <div class="colHButton">X</div>
                    <div class="colH">Item</div>
                    <div class="colHPrice">Price</div>
                    <div class="colHQty">Qty</div>
                </div>
              <% foreach (var item in Model.Where(m => m.LineItemType.Id == NexTow.Client.Framework.UnitsService.LineItemTypes.SaleItem || m.LineItemType.Id == NexTow.Client.Framework.UnitsService.LineItemTypes.NonSaleItem))
              { %>    
                <div class="row">
                    <%=Html.Hidden("Model.LineItemNameId", item.LineItemNameId)%>
                    <div class="cellButton"><button onclick="DeleteContainer(event);">X</button></div>
                    <div class="cell"><span class="formData"><%=item.LineItemDescription %></span></div>                
                    <div class="cellPrice">
                        <span class="formData">$</span>
                        <%= Html.TextBox("Model.Price", item.Price, new { style="width:75px;" })%>
                    </div>
                    <div class="cellQty">
                        <%= Html.TextBox("Model.Quantity", item.Quantity, new { style = "width:60px;" })%>
                    </div>
                </div>  
            <%} %>
        </div>
    </fieldset>
    <p>
        <input type="submit" value="Update Invoice" onclick="SequenceFormElementsNames('salesItems');" />
    </p>

    <% } %>
</asp:Content>

This then provides the user the ability to edit the entries, and then the user clicks the "Update Invoice" submit button. This posts to the same view and action using a POST:

[HttpPost]
public ActionResult EditInvoice(List<SalesDocumentLineItem> salesItems)
{
    if (salesItems == null || salesItems.Count == 0)
    {
        return View("ClientError", new ErrorModel() { Description = "Line items required." });
    }

    SalesDocument invoice = null;

    try
    {
        invoice = SomeService.UpdateInvoice();
    }
    catch (Exception ex)
    {
        return HandleControllerException(ex);
    }

    InvoiceModel model = new InvoiceModel();
    model.Document = invoice;

    return View("InvoicePreview", model);
}

However, eventhough this worked in the old application. In the new one, this does not work. When we breakpoint at the final EditInvoice POST action method, the collection of salesItems is NULL. What is happening!?

Upvotes: 0

Views: 713

Answers (1)

Brant
Brant

Reputation: 15324

When you use Html.TextBox(string, object), the first argument is used as the name of the form field. When you post this form back to the server, MVC looks at your action method's argument list and uses the names of the form fields to try and build those arguments. In your case, it tries to build a List<SalesDocumentLineItem>.

It looks like you're using "Model.Something" as the names of your form fields. This probably worked in the past, but I'm guessing something changed in MVC4 such that the framework doesn't know what you're talking about anymore. Fortunately, there's a better way.

Instead of setting the name of the form field using a string, use the strongly-typed HTML helpers like this:

<% for (var i = 0; i < Model.Count; i++) { %>
  <div class="row">
    <%= Html.HiddenFor(model => model[i].LineItemNameId) %>
    <!-- etc... -->
  </div>
<% } %>

These versions of the HTML helpers use a lambda expression to point to a property in your model and say "See that property? That one right there? Generate an appropriate form field name for it." (Bonus: Since the expression is pointing at the property anyway, you don't have to specify a value anymore -- MVC will just use the value of the property the expression represents)

Since lambda expressions are C# code, you will get a compile-time error if you make a typo, and tools like Visual Studio's "Rename" feature will work if you ever change the property's name.

(This answer goes into more detail on exactly how this works.)

Upvotes: 2

Related Questions