1

I have a simple select with an option selected from the model that is loaded.

To start here is my simple model

public class invoice
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ID { get; set; }

    public invoice_state invoice_state { get; set; }

}


public class invoice_state
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ID { get; set; }

    public string alias { get; set; }

    public List<invoice> invoices { get; set; }

    [Display(Name = "title")]
    public string title { get; set; }
}

Here is what I have that works in one view.

in controller:

    public IActionResult Create()
    {
        string state = "start_finish";
        ViewBag.State = state;
        var states = _context.invoice_state.Select(c => new {
            id = c.ID,
            title = c.title
        }).ToList();
        ViewBag.States = new SelectList(states, "id", "title", _context.invoice_state.Where(e => e.alias == state).FirstOrDefault().ID);

        return View();
    }

in the view

@Html.DropDownList("invoice_state", (SelectList)ViewBag.States, "--Select One--")

That works fine, the option is selected... but on my edit view which is set up mostly the same is not working.

in controller:

    public async Task<IActionResult> Edit(int? id)
    {
        var invoice = await _context.invoice
            .Include(_item => _item.invoice_state).SingleOrDefaultAsync(m => m.ID == id);
        if (invoice == null)
        {
            return NotFound();
        }
        string state = "start_finish"; // as a default
        var states = _context.invoice_state.Select(c => new { id = c.ID, title = c.title }).ToList();
        if (null != invoice.invoice_state)
        {
            ViewBag.State = invoice.invoice_state.alias;
        }
        else
        {
            ViewBag.State = state;
        }
        ViewBag.States = new SelectList(states, "id", "title", _context.invoice_state.Where(e => e.alias == state).FirstOrDefault());

        return View(invoice);
    }

and in the edit view

  @Html.DropDownList("invoice_state", (SelectList)ViewBag.States, "--Select One--")

I have read all over the place and can't find a simple answer that isn't wire up more files, and even those haven't helped get me to the need. I have tried to force it too and it is not working, but figured it was worth placing here too.

        ViewBag.States = _context
                            .invoice_state
                            .Select(c => new SelectListItem {
                                Value = c.ID.ToString(),
                                Text = c.title
                            })
                            .Select(l => new SelectListItem {
                                Selected = (l.Value == invoice.invoice_state.ID.ToString()),
                                Text = l.Text,
                                Value = l.Value
                            });

but only get 'Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable<Microsoft.AspNetCore.Mvc.Rendering.SelectListItem>' to 'Microsoft.AspNetCore.Mvc.Rendering.SelectList' or the list version of the error if i add .ToList() on the select like before.

Some people have suggested to set the selected value, and as i read it it would be like,

if (null != invoice.invoice_state)
{
    ViewBag.invoice_stateID = invoice.invoice_state.ID;
}
else
{
    ViewBag.invoice_stateID = _context.invoice_state.Where(e => e.alias == "start_finish").FirstOrDefault().ID;
}

if i use

<select asp-for="invoice_state" asp-items="@ViewBag.States" >
    <option>Please select one</option>
</select>

It doesn't work either, see the list but nothing selected. Last note, if I select it and submit it, it does set the value in the database, just when I get back to the edit it again fails to again select anything.

Also to be clear there is the data

enter image description here

which came from

    <dt>
        @Html.DisplayNameFor(model => model.invoice_state)
    </dt>
    <dd>
        @Html.DisplayFor(model => model.invoice_state)
    </dd>
Quantum
  • 1,456
  • 3
  • 26
  • 54
  • You need to set the value of property `invoice_state` in the GET method and pass the model to the view - you have not shown that code!. (and setting the `Selected` property of `SelectListItem` is pointless when binding to a property) –  Feb 28 '17 at 08:02
  • Sorry in the edit i need to reset the property? I have the value set in the database, and it's in the model, and just to be sure `invoice_state` wasn't null when i called the `invoice` model I set `.Include(_item => _item.invoice_state)` which is show above. – Quantum Feb 28 '17 at 08:09
  • You have not even show the `return view(yourmodel);` code. And `invoice_state` is a complex object and you cannot bind to a complex object - you need a property which is `int SelectedInvoice` to bind to - `@Html.DropDownListFor(m => m.SelectedInvoice, (SelectList)ViewBag.States, "--Select One--")` - and ALWAYS use the strongly typed methods –  Feb 28 '17 at 08:13
  • I showed the whole edit method now with the return. The data is there, i can get to it. If i put `int SelectedInvoice` isn't it going to try to add it to the database? seem super redundant to have to tell it it has a selected value when it is there already. lol at the moment I just want to get it to work :D – Quantum Feb 28 '17 at 08:22
  • You editing data - Step 1. ALWAYS use a view model - [What is ViewModel in MVC?](http://stackoverflow.com/questions/11064316/what-is-viewmodel-in-mvc). And suggest you study the code [here](http://stackoverflow.com/questions/34366305/the-viewdata-item-that-has-the-key-xxx-is-of-type-system-int32-but-must-be-o) (but you could use `@Html.DropDownListFor(m => m.invoice_state.ID, (SelectList)ViewBag.States, "--Select One--")` assuming that is the property you want to bind to) –  Feb 28 '17 at 08:25
  • `@Html.DropDownListFor(m => m.invoice_state.ID, (SelectList)ViewBag.States, "--Select One--")` was it. I'll look at the view model but idk, just haven't been digging the MVVM thing. It just seems like i have to wire so much up to get things right.. 100% thank you for getting it to work. There is a new error `...'invoice_state' because it has a null primary ..` but I bet i can fix that.. – Quantum Feb 28 '17 at 08:41
  • question is here https://stackoverflow.com/questions/50518407/mvc-core-selectlist-dropdown-in-controller-error-microsoft-aspnetcore-mvc-rende –  May 24 '18 at 21:28

1 Answers1

2

@Stephen Muecke is right, the ticket is the ViewModel. Not a fan fully here but I'll simmer. The solution is not just getting it to show, you need to save is too. Here is the whole of it.

ViewModel

namespace project_name.Models.InvoiceViewModel
{
    public class EditInvoiceViewModel
    {
        public int invoice_stateID { get; set; }
        public invoice invoice { get; set; }
        public List<SelectListItem> States { set; get; }
    }
}

edit in action on controller

    public async Task<IActionResult> Edit(int? id)
    {


        var invoice = await _context.invoice
            .Include(_item => _item.invoice_state).SingleOrDefaultAsync(m => m.ID == id);

        string state = "start_finish"; // as a default
        var states = _context.invoice_state.Select(c => new { id = c.ID, title = c.title }).ToList();
        if (null != invoice.invoice_state)
        {
            ViewBag.State = invoice.invoice_state.alias;
        }
        else
        {
            ViewBag.State = state;
        }

        var vm = new EditInvoiceViewModel();
        vm.invoice = invoice;
        vm.States = _context
                            .invoice_state
                            .Select(c => new SelectListItem
                            {
                                Value = c.ID.ToString(),
                                Text = c.title
                            })
                            .ToList();

        if (null != invoice.invoice_state)
        {
            vm.invoice_stateID = invoice.invoice_state.ID;
        }  else {
            vm.invoice_stateID = _context.invoice_state.Where(e => e.alias == "start_finish").FirstOrDefault().ID;
        }
        return View(vm);
    }

to save in action on controller

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Edit(EditInvoiceViewModel model)
    {
        if (ModelState.IsValid)
        {
            try
            {
                model.invoice.creator = await GetCurrentUserAsync();
                model.invoice.invoice_state = await _context.invoice_state.SingleOrDefaultAsync(m => m.ID == model.invoice_stateID);
                _context.Update(model.invoice);
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!invoiceExists(model.invoice.ID))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }
            return RedirectToAction("Index");
        }
        return View(model);
    }

And in the view

    <div class="form-group">
        <label asp-for="@Model.invoice_stateID" class="col-md-2 control-label"></label>
        <select asp-for="@Model.invoice_stateID" asp-items="@Model.States" >
            <option>Please select one</option>
        </select>
    </div>

The thing I guess I was having a hard time with is all supporting questions that are like this, and all the blogs focus on the display, but i still need to save it, and that was not clear. I had to skip the [bind()] but I'll get back to that.

Quantum
  • 1,456
  • 3
  • 26
  • 54