0

I am trying to create a simple form in ASP.MVC which shows a list of quotes, and allows the user to create work orders by selecting a person from a drop down list to assign that quote to, and then submitting the form all in one go.

My problem is that my use of DropDownListFor within a foreach loop is causing an error and I don't think I'm going about this the right way.

A (somewhat simplified) quote looks like this:

public class QuoteItem
{
    public QuoteItem()
    {
        this.IsWorkOrder = false;
    }

    [Key]
    public int QuoteItemCostID { get; set; }
    public string Item { get; set; }
    public decimal? Subtotal { get; set; }
    public bool IsWorkOrder { get; set; }

    [NotMapped]
    public string AssignedTo { get; set; }
}

I have a View Model configured as follows:

public class UnassignedWorkOrdersViewModel
{
    public IEnumerable<QuoteItemCost> QuoteItemCosts { get; set; }
    public IEnumerable<SelectListItem> Trades { get; set; }
}

And a controller action that looks like this:

public ActionResult Unassigned(int id = 0)
{

    var trades = db.UserProfiles.Where(u => u.Typpe == "Trade").ToArray().Select(x => new SelectListItem
    {
        Value = x.FirstName + " " + x.LastName,
        Text = x.FirstName + " " + x.LastName
    }).ToList();


    var quoteItems = db.QuoteItems
        .Where(x => x.IsWorkOrder == false)
        .ToList();

    UnassignedWorkOrdersViewModel viewModel = new UnassignedWorkOrdersViewModel
    {
        Trades = trades,
        QuoteItemCosts = quoteItemCosts
    };

    return View(viewModel);
}

My problem is that in my view, I'm trying to show each work order and the drop down list of trades next to it, and the way that I thought I'd do it is to map the class property "AssignedTo" to the value in the drop down list, however this doesn't work, as the DropDownList expects a LINQ expression as the first parameter:

@model Project.ViewModels.UnassignedWorkOrdersViewModel

@if (Model.QuoteItemCosts.Any())
{
    using (Html.BeginForm("Unassigned", "WorkOrder", FormMethod.Post, null))
    {
    @Html.AntiForgeryToken()
    <table>

        @foreach (var item in Model.QuoteItemCosts)
        {

            <tr>
                <td style="width:100px;">
                    <b>Item:</b> @item.Item
                </td>
                <td style="width:200px;">
                    <b>Total:</b> @item.Subtotal
                </td>
                <td>
                    <b>Assign:</b>
                    @Html.DropDownListFor(
                        item.AssignedTo,  // THIS LINE CAUSES AN ERROR 
                        Model.Trades,
                        "", new { @id = "ddlStatus", @style = "width:100%" })
                </td>
            </tr>
        }
    </table>
}

How can I resolve this and am I even going about this the right way? On form submit, I should expect to get back the ViewModel which would then return all my QuoteItems, but with an AssignedTo entry if the user has selected a person from the drop down list.

Edit:

Andrei's suggestion below has helped and led me to another solution where my DropDownListFor now looks like this:

 @Html.DropDownListFor(
                        m => m.QuoteItemCosts.First(q => q.QuoteItemCostID == item.QuoteItemCostID).AssignedTo,
    Model.Trades,
    "", new { @id = "ddlStatus", @style = "width:100%" })

but when I submit the form the viewmodel is null:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Unassigned(UnassignedWorkOrdersViewModel viewModel)
    {
        // My viewModel is null
        return RedirectToAction("Index");
    }

I've updated my razor code to include a Html.BeginForm as well.

Evonet
  • 3,600
  • 4
  • 37
  • 83

1 Answers1

3

To get everything correct, you need to make use of a regular for loop here:

@for (int i = 0; i < Model.QuoteItemCosts.Count; i++)
{
    <tr>
        <td style="width:100px;">
            <b>Item:</b> @Model.QuoteItemCosts[i].Item
        </td>
        <td style="width:200px;">
            <b>Total:</b> @Model.QuoteItemCosts[i].Subtotal
        </td>
        <td>
            <b>Assign:</b>
            @Html.DropDownListFor(
                m => m.QuoteItemCosts[i].AssignedTo,  // THIS LINE CAUSES AN ERROR 
                Model.Trades,
                "", new { @id = "ddlStatus", @style = "width:100%" })
        </td>
    </tr>
}

Now, you might wander why not keep foreach and just use m => item.AssignedTo. This is a very well known problem with foreach loop in ASP.NET MVC views. The issue here is that the expression above will generate a select tag with a wrong name attribute, and you won't receive its value when the form is posted. However name that is generated with regular for loop is correct.

Andrei
  • 55,890
  • 9
  • 87
  • 108
  • Thanks Andrei, that seems to be better, but when I submit my form my viewmodel is null, I've updated the question with the details – Evonet Jun 24 '14 at 12:41
  • Actually Andrei, I'm getting an error with your code on the lines @Model.QuoteItemCosts[i] - Cannot apply indexing with [] to an expression of type 'System.Collections.Generic.IEnumerable' – Evonet Jun 24 '14 at 12:49
  • Sorry, fixed that by changing to an IList - all good! – Evonet Jun 24 '14 at 12:53