0

So yeah im trying to make a Create view for my "Product" and Im using a Collection model to get both Product and Customer in the same view. The only way i really know how to get the value is to do a foreach but that doesnt really work here :P The only search ive found are people saying to use model => model.product.Name etc, but that doesnt work for me maybe ive missed something?

What im going for is that i want to add a product to the customer thats selected in the dropdownlist. Will this code be enough for it or what do i need to have in mind?

EDIT Forgot to say that im trying to do like @Html.EditorFor(model => model.product.Name) but a working one and that handles Create well :s

@model Co56_Invoice_.Models.Collection
<h2>Skapa ny produkt</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
    <div class="form-horizontal">
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group">
            <div class="col-md-10">
                @Html.DropDownList("Kunder",
          Model.customer.Select(x => new SelectListItem()
          { Text = x.Name.ToString(), Value = x.CustomerID.ToString() }).ToList()
          , "Välj kund")
            </div>
        </div>





    </div>
    }
 <div>
     @Html.ActionLink("Back to List", "Index")
 </div>

@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}

public ActionResult Create()
    {
        col.customer = (from o in db.Customers select o).ToList();
        col.product = (from o in db.Products select o).ToList();
        return View(col);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create([Bind(Include = "CustomerID,ProductID,Name,StartDate,Interval,Price,YearPrice,TotalPrice,Category,Paid")] Product product)
    {
        try
        {
            if (ModelState.IsValid)
            {
                db.Products.Add(product);
                db.SaveChanges();
                return RedirectToAction("Index");
            }
        }
        catch(Exception e)
        {
            throw e.InnerException;
        }

        return View(product);
    }
  • Not clear what your asking. The view you have shown has a model `@model Collection` but the model in the POST method is `Product` –  Mar 02 '16 at 08:58
  • From a first glance, you're sending in a list of customers and a list of products, then only showing a dropdown for the customer list that is not bound to an a customer id, but then receiving a Product in the POST. Are you wanting to create a new product, but with a customer ID selected? – freedomn-m Mar 02 '16 at 08:59
  • it is a Product that i want to Create. But as i need the CustomerID in the dropdownlist I use Collection to select it. Note that im quite new on the MVC part and havent handled it for awhile – Viktor Virre Jakobsen Mar 02 '16 at 09:00
  • @freedomn-m Yes this. The product shall be created on the Customer chosen in the dropdownlist – Viktor Virre Jakobsen Mar 02 '16 at 09:02
  • 1
    Then you need the view to have `@model Product`, and pass the values for the `SelectList` via a `ViewBag` property, or better use a view model with properties for `Product` and `IEnumerable Customers` –  Mar 02 '16 at 09:02
  • Which ever way you go, the model in the view needs to match the model in the POST method. And your dropdownlist is for `"Kunder"`, but your `Product` model does not seem to have a property with that name. I assume it should be `@Html.DropDownListFOr(m => m.CustomerID, ....)` –  Mar 02 '16 at 09:04
  • @StephenMuecke abit uncertain what you mean by your first comment. I have never used ViewBag before so i dont know where ill use it and the SelectListItem Customers goes in the Create GET or? :x – Viktor Virre Jakobsen Mar 02 '16 at 09:26
  • Good, don't start :) Create a view model (say `ProductVM`) and add the properties of `Product` which you want to display/edit in the view and include `IEnumerable Customers`. Then in the GET method - `var model = new ProductVM{ Customers = new SelectList(db.Customers, "CustomerID", "Name") }; return View(model);` –  Mar 02 '16 at 09:30
  • And change the view to `@model ProductVM` and use `@Html.DropDownListFor(m => m.CustomerID, Model.Cutomers)` and finally change the POST method to `public ActionResult Create(ProductVM model)` (without that awful `[Bind]` attribute) –  Mar 02 '16 at 09:31
  • In your view you should use Using application.models.ViewModelName then in your foreach @foreach(model1 in Model) { } @foreach(model2 in Model) { } – REDEVI_ Mar 02 '16 at 09:40
  • @StephenMuecke Thank you very much with this! Just a quick question :> I guess the VM shouldnt have info like ID's and such. Which makes the m => m.CustomerID unreachable? :x – Viktor Virre Jakobsen Mar 02 '16 at 09:44
  • Its working with just m => m.Customers but does that actually make the product add to the ID? – Viktor Virre Jakobsen Mar 02 '16 at 09:46
  • @StephenMuecke Also the dbContext is getting upset as it wants a Product not a ProductVM :x – Viktor Virre Jakobsen Mar 02 '16 at 10:07
  • In the POST method you initialize a new instance of `Product` and map the view model properties to it, then save the data model. And refer also [What is ViewModel in MVC?](http://stackoverflow.com/questions/11064316/what-is-viewmodel-in-mvc) –  Mar 02 '16 at 10:30
  • 1
    To put the above comment another way - the viewmodel is for the view, the data model is for the data. You need to "map" or transfer the properties from the viewmodel that is POSTed (via the action parameters) into a new instance of the data model. This is good practice as it keeps the classes for saving data (to the db) separate from the classes used on the UI. That way you can have different classes on the UI depending on the situation (in this case it's probably a 1to1, but may not always be) – freedomn-m Mar 02 '16 at 10:41
  • Would have loved my teacher to go through stuff like this when we had MVC.. This is like news to me which sucks :D – Viktor Virre Jakobsen Mar 02 '16 at 11:27
  • @StephenMuecke You guys have any clue to why my Database drops everytime I open up Customer/Index. It worked fine till i started doing this stuff :x Also updated the code in the 'POST' anything like that i need? – Viktor Virre Jakobsen Mar 02 '16 at 11:49
  • @ViktorVirreJakobsen, You cannot just change your question completely and you need to roll back your edits (or I will). A lot of users have added answers and comments based on your original question which are now invalid and make no sense. And you need to ask a new question for the new problem. –  Mar 02 '16 at 11:53
  • @StephenMuecke Sorry! I just got so many questions and the stress with this project is killing me. I'll stick to the subject next time! – Viktor Virre Jakobsen Mar 02 '16 at 12:12

2 Answers2

0

In your view you should use Using application.models.ViewModelName then in your foreach

 @foreach(model1 in Model) { }
 @foreach(model2 in Model) { }
REDEVI_
  • 684
  • 8
  • 18
0

I will recommend a number of changes to your approach.

First and foremost, if Product is what you are creating that should be your Model in the view and both of your Create methods (including the HTTP POST). *Just realised @Stephen Muecke point that out.

To address your issue with passing lists and other required data into the view, make extensive use of the ViewBag. Also, you need to preselect all your dropdown items.

So your Action Methods should look like this:

public ActionResult Create()
    {
        ViewBag.CustomerList =
            db.Customers
            .Select(x => new SelectListItem { Text = x.Name.ToString(), Value = x.CustomerID.ToString() })
            .ToList();

        ViewBag.ProductList = db.Products
            .Select(x => new SelectListItem { Text = x.Name.ToString(), Value = x.CustomerID.ToString() })
            .ToList();

        var m = new Product();

        /*
         do some initialization on the Product object if required.
         better still, use a factory method to create new instance of Product.
        */

        return View(m);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create([Bind(Include = "CustomerID,ProductID,Name,StartDate,Interval,Price,YearPrice,TotalPrice,Category,Paid")] Product product)
    {
        try
        {
            if (ModelState.IsValid)
            {
                /*
                Use some helper method(s) to carry out extra validation on your model; unless it's all done unobtrusively with Validation Attributes
                */
                db.Products.Add(product);
                db.SaveChanges();
                return RedirectToAction("Index");
            }
        }
        catch (Exception e)
        {
            throw e.InnerException;
        }

        return View(product);
    }

Then your view can look something like this:

@model Product

<h2>Skapa ny produkt</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
    <div class="form-horizontal">
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group">
            <div class="col-md-10">
                @Html.DropDownListFor(m => m.ProductId, (ViewBag.CustomerList as IEnumerable<SelectListItem>), "Välj kund")
            </div>
        </div>



        <!--Other fields can be included-->

    </div>
    }
 <div>
     @Html.ActionLink("Back to List", "Index")
 </div>

@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
Eniola
  • 710
  • 1
  • 7
  • 23
  • Okey so now im super confused :D I've followed Stephen's example and as you may have seen i updated the code above. Should i just scrap this ProductVM thing i got going and do what you have suggested? – Viktor Virre Jakobsen Mar 02 '16 at 10:34
  • 1
    There are plenty of different ways to solve this issue. The solution in this answer uses ViewBag. Many people don't like to use viewbag to pass data like this for various reasons (which I'll let you research). The alternative is to pass all the detail in a custom class *specifically* for that view - ie a custom ViewModel: ProductVM. – freedomn-m Mar 02 '16 at 10:39
  • I obviously was a little behind on the updates. I guess this just represents another approach. – Eniola Mar 02 '16 at 11:01