1

I have had a look at this post and the chap there seems to want to do the same thing that I am aiming for. I have inherited this application and although I am a .NET developer the MVC stuff seems magical in comparison to other MVC frameworks in other languages which I prefer. My salient code is as follows:

public class PurchaseOrderViewModel
{
    public PurchaseOrderViewModel()
    {
        this.PurchaseOrder = new PurchaseOrder();
    }
    // Some other fields here....
    public PurchaseOrder PurchaseOrder { get; set; }

    public IList<PurchaseOrderItem> OrderItems { get; set; }
 }

The controller:

    [HttpPost]
    public ActionResult Details(PurchaseOrderViewModel viewModel)
    {
       // bunch of stuff. the viewModels.OrderItems here is null. 
    }

The OrderItems in the model is a ICollection<> I am converting to an IList for the iterative nature. The relevant part of the view is as follows:

 @model PurchaseOrderViewModel
 @for (var i = 0; i < this.Model.OrderItems.Count; i++)
 {                
    @Html.EditorFor(m => m.OrderItems[i].Quantity);
 }

So why am I not getting the OrderItems coming through? Is this to do with the fact that they are IList rather than ICollection? If so, what is the workaround?

EDIT: I have changed the view code as I left something in that I was trying before.

EDIT 2: I took into consideration current comment/answers so now I have the following changes made:

viewModel:

    public ICollection<PurchaseOrderItem> PurchaseOrderItems { get; set; }

PurchaseOrderItem.cshtml: (I have left this actually as it is in code just in case):

@model Downland.Model.PurchaseOrderItem

<tr style="background-color: #fff; padding: 8px;">
<td style="text-align: left; width: 210px;">
    @if (this.Model.ProductID == null)
    {
        <img class="viewproduct" src="~/Images/GetThumbnail/-1" alt="Product Image" style="padding: 8px;" />
    }
    else
    {
        <img class="viewproduct" src="~/Images/GetProductImage?productId=@(this.Model.ProductID)" alt="Product Image" style="padding: 8px;" />
    }
</td>
<td>
    @(string.IsNullOrEmpty(this.Model.SKU) ? this.Model.NonDownlandSKU : this.Model.SKU)
</td>
<td>@this.Model.ProductName</td>
<td>
    @this.Model.Quantity
    @Html.EditorFor(m => m.Quantity)
</td>
<td>
    @string.Format("{0:C}", this.Model.PriceExVAT)
    @Html.EditorFor(m => m.PriceExVAT)
</td>
<td>@string.Format("{0:C}", this.Model.VATTotal)</td>
<td>
    @string.Format("{0:C}", this.Model.ItemTotal)
</td>
</tr>

View code:

@Html.EditorFor(m => m.PurchaseOrderItems);

Just another interesting point. On the GET I set the PurchaseOrder and then the PurchaseOrderItems in the view model based on the database data. On the Post it appears that we are setting the purchaseOrder in the view model with data from the database again. There are some other fields that are being set correctly in the view model based on what is passed to the post but the PurchaseOrderItems is still null.

Community
  • 1
  • 1
Daniel Casserly
  • 3,552
  • 2
  • 29
  • 60
  • 2
    Try to use EditorTemplate. That will solve your problem. Take a look at this post http://stackoverflow.com/questions/25365043/mvc-create-object-and-related-objects-in-one-go/25365806#25365806 or http://stackoverflow.com/questions/34028606/update-relationships-on-edit-post-with-model-bind-from-mvc-controller/34029526#34029526 – Shyju Dec 30 '15 at 12:57
  • I have updated the question. I tried the EditorTemplate but to no avail @Shyju – Daniel Casserly Dec 30 '15 at 13:28

1 Answers1

1

You should not be doing this conversion in the view because the following expression simply is too complex and not supported by the standard editor templates:

m => m.PurchaseOrder.PurchaseOrderItems.ToList()[i].Quantity

So, you should obviously use a real view model in which you don't just stuff your EF objects as properties and naming it with the ViewModel suffix (which is what this PurchaseOrderViewModel look like). So once you have a real view model you can easily access the corresponding property because it will be of type IList<T>:

@Html.EditorFor(m => m.PurchaseOrder.PurchaseOrderItems[i].Quantity);

This being said, if for some reason you have inherited some legacy crap like that there's still a workaround for you. You can use editor templates.

So you start by adding the following template in ~/Shared/EditorTemplates/PurchaseOrderItem.cshtml:

@model PurchaseOrderItem
@Html.EditorFor(x => x.Quantity)

The name of this template is very important because it all works by convention. It should be named as the type of the ICollection<> that you have and located in ~/Shared/EditorTemplates.

And then simply adapt your view like that:

@Html.EditorFor(m => m.PurchaseOrder.PurchaseOrderItems);

The EditorFor helper in this case will infer that PurchaseOrderItems is an ICollection<T> and it will automatically loop through the elements of this collection and render the corresponding ~/Shared/EditorTemplates/T.cshtml template that you could customize.


UPDATE:

It looks like I need to post a simple example to illustrate how editor templates can be used.

As always let's start with the view model(s):

public class PurchaseOrderViewModel
{
    public ICollection<PurchaseOrderItem> PurchaseOrderItems { get; set; }
}

public class PurchaseOrderItem
{
    public int Quantity { get; set; }
}

then a controller with 2 actions (one for displaying a form and one for handling the submitted data from this form):

public ActionResult Index()
{
    var model = new PurchaseOrderViewModel();
    // This information will probably come from querying some data store. 
    // That could be a SQL database for example. But for the purpose
    // of this sample we are just hardcoding some values to illustrate 
    // the concept without any dependencies
    model.PurchaseOrderItems = new[]
    {
        new PurchaseOrderItem
        {
            Quantity = 5,
        }
    };
    return View(model);
}

[HttpPost]
public ActionResult Index(PurchaseOrderViewModel model)
{
    // ... everything gets bound correctly here
}

then a strongly typed view:

@model PurchaseOrderViewModel
@using (Html.BeginForm())
{
    @Html.EditorFor(x => x.PurchaseOrderItems)
    <input type="submit" value="OK" />
}

and finally the corresponding editor template (~/Shared/EditorTemapltes/PurchaseOrderItem.cshtml):

@model PurchaseOrderItem
@Html.EditorFor(x => x.Quantity)
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • Sorry for the misleading code.. I have changed it now to something else that doesn't work which is more like the code that I would like to end up with . – Daniel Casserly Dec 30 '15 at 13:00
  • 1
    You have changed your question but unfortunately in this question you have shown some model that is called `PurchaseOrderViewModel` which doesn't have any collection property. Yet in your view you seem to be using some `@Html.EditorFor(m => m.OrderItems[i].Quantity);` call but I can't see anywhere in your view model a property called `OrderItems`. But in all cases you should really follow my advice and use editor templates. Just define an editor template for the `OrderItem` class and then you don't even need to be writing a `for` loop in your view but `@Html.EditorFor(x => x.OrderItems)`. – Darin Dimitrov Dec 30 '15 at 13:02
  • updated. Sorry I'm trying not to overload with unrelevant code but I should really double check the stuff I am putting out. Apologies – Daniel Casserly Dec 30 '15 at 13:04
  • However, it seems that the editorTemplates is the way to go. Thanks for your help I will report back as/when it works. Thanks again. – Daniel Casserly Dec 30 '15 at 13:05
  • I have tried this but if you notice my code changes under EDIT 2 I have no change in result. I have tried to go into more detail there too. – Daniel Casserly Dec 30 '15 at 13:26
  • 1
    Try narrowing down your problem. Cut all the noise and crap out. Just a plain simple ASP.NET MVC application with view models containing simple properties. – Darin Dimitrov Dec 30 '15 at 13:29
  • 1
    Please see my UPDATEd answer to understand what I mean. Once you have this very simple ASP.NET MVC application working you could add other crap to it: additional properties, CSS, database access, EF, ... – Darin Dimitrov Dec 30 '15 at 13:33
  • I feel somewhat embarrassed you admit but the problem was that I didn't have the form tags surrounding the section of code in the view. Apparently the guys who wrote it didn't put the whole thing in one form but several, as you do. You answer indirectly got me there. Thanks – Daniel Casserly Dec 30 '15 at 14:28