POST Action Losing Values - asp.net-mvc

I populate my view with model values for UseCaseId and ExtensionPoint - debugging through this seems to work fine.
However, when I add a new bit of information to the ExtensionTitle field on my view and then POST, only the value for ExtensionTitle is retained and the values for UseCaseId and ExtensionPoint are lost. This means that if (ModelState.IsValid) returns false and therefore I can't save the new record to the database.
Can anyone tell me why these values are getting lost when I POST?
VIEWMODEL
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Web;
namespace JustSpecIt.ViewModels
{
public class AddExtensionStepViewModel
{
public int UseCaseId { get; set; }
[DisplayName ("Extends Step #")]
public int ExtensionPoint { get; set; }
[DisplayName ("Extends Step Description")]
public string StepTitle { get; set; }
[DisplayName("Extension Name")]
public string ExtensionTitle { get; set; }
}
}
MODEL
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace JustSpecIt.Models
{
public class Extension
{
public int ID { get; set; }
public int UseCaseID { get; set; }
public string Title { get; set; }
public int ExtensionPoint { get; set; }
}
}
CONTROLLER
// GET: Extensions/Create
public ActionResult Create(int id)
{
ViewBag.UseCaseId = id;
ViewBag.StepTitle = db.Steps.Find(id).Content;
//Create an populate the ViewModel
AddExtensionStepViewModel model = new AddExtensionStepViewModel()
{
ExtensionPoint = id,
UseCaseId = db.Steps.Find(id).UseCaseID,
};
return View();
}
// POST: Extensions/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "UseCaseId,ExtensionTitle,ExtensionPoint")] AddExtensionStepViewModel model)
{
if (ModelState.IsValid)
{
Extension e = new Extension ();
e.ExtensionPoint = model.ExtensionPoint;
e.UseCaseID = model.UseCaseId;
e.Title = model.ExtensionTitle;
db.Extensions.Add(e);
db.SaveChanges();
return RedirectToAction("ChooseExtensionStep", new { id = model.UseCaseId });
}
return View(model);
}
VIEW
#model JustSpecIt.ViewModels.AddExtensionStepViewModel
#{
ViewBag.Title = "Create";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Add Extension Step</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model.UseCaseId)
#Html.HiddenFor(model => model.ExtensionPoint)
<div class="form-horizontal">
<h4>Extension</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.ExtensionTitle, htmlAttributes: new { #class = "control-label col-md-4" })
<div class="col-md-10">
#Html.EditorFor(model => model.ExtensionTitle, new { htmlAttributes = new { #class = "form-control form-control-no-max-width" } })
#Html.ValidationMessageFor(model => model.ExtensionTitle, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}

You have not passed the model to the view in your GET method (so the values of UseCaseID and Title are 0 (the defaults for int)
public ActionResult Create(int id)
{
ViewBag.UseCaseId = id;
ViewBag.StepTitle = db.Steps.Find(id).Content;
AddExtensionStepViewModel model = new AddExtensionStepViewModel()
{
ExtensionPoint = id,
UseCaseId = db.Steps.Find(id).UseCaseID,
};
return View(model); // change this
}

Related

Asp.net core 6 get value from drop downlist

I am using the #Html.dropdownlistfor that allows the user to choose the customer that creates the deal. But the chhosed customer did not proceed to the controller which led to an unvalid model state. I got the reason that the model was not valid
but I did not know how to solve it. I have followed these links but the problem is still the same.
How do I get the Selected Value of a DropDownList in ASP.NET Core
MVC App
How to get DropDownList SelectedValue in Controller in MVC
SelectList from ViewModel from repository Asp.net core
Sorry if my question is naive I am new to ASP.net core MVC.
the code is as below:
namespace MyShop.ViewModels
{
public class CreateDealVM
{
public Deals deal { get; set; }
public IEnumerable<SelectListItem> CustomerListVM { get; set; }
}
}
The Model class:
namespace MyShop.Models
{
[Table("Deals")]
public class Deals
{
[Key]
[Display(Name = "ID")]
public int dealId { get; set; }
[ForeignKey("Customer")]
[Display(Name = "Customer")]
public Customer customer { get; set; }
[Display(Name = "CustomerName")]
public string? parentCustomerName { get; set; }
[Display(Name = "product")]
public DealTypeEnum product { get; set; }
[Display(Name = "Date")]
public DateTime saleDate { get; set; }
[Display(Name = "Quantity")]
public float quantity { get; set; }
[Display(Name = "Price")]
public float price { get; set; }
}
The Controller:
public IActionResult Create()
{
CreateDealVM vm = new CreateDealVM();
vm.CustomerListVM = _context.Customers.Select(x => new SelectListItem { Value = x.customerId.ToString(), Text = x.customerName }).ToList();
return View(vm);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind("dealId,customer,product,saleDate,quantity,price")] Deals deal, CreateDealVM vm)
{
if (ModelState.IsValid)
{
try
{
vm.deal.customer = _context.Customers.Find(vm.CustomerListVM);
vm.deal = deal;
_context.Deals.Add(vm.deal);
_context.SaveChanges();
return RedirectToAction("Index");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
return View(vm);
}
else
{
var errors = ModelState.Select(x => x.Value.Errors)
.Where(y => y.Count > 0)
.ToList();
//The Error showed here.
}
return View(vm);
}
The view:
#model MyShop.ViewModels.CreateDealVM
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Deals</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
#Html.LabelFor(model => model.deal.customer, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.CustomerListVM,Model.CustomerListVM,"Select Customer", htmlAttributes: new { #class = "form-control"})
</div>
</div>
<div class="form-group">
<label asp-for="deal.product" class="control-label"></label>
<select asp-for="deal.product" class="form-control"></select>
<span asp-validation-for="deal.product" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="deal.saleDate" class="control-label"></label>
<input asp-for="deal.saleDate" class="form-control" />
<span asp-validation-for="deal.saleDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="deal.quantity" class="control-label"></label>
<input asp-for="deal.quantity" class="form-control" />
<span asp-validation-for="deal.quantity" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="deal.price" class="control-label"></label>
<input asp-for="deal.price" class="form-control" />
<span asp-validation-for="deal.price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
I just realise your Model is wrong
[Table("Deals")]
public class Deals
{
[Key]
[Display(Name = "ID")]
public int dealId { get; set; }
[ForeignKey("Customer")]
[Display(Name = "Customer")]
public int CustomerId { get; set; }
[Display(Name = "CustomerName")]
public string? parentCustomerName { get; set; }
[Display(Name = "product")]
public DealTypeEnum product { get; set; }
[Display(Name = "Date")]
public DateTime saleDate { get; set; }
[Display(Name = "Quantity")]
public float quantity { get; set; }
[Display(Name = "Price")]
public float price { get; set; }
public virtual Customer Customer { get; set; }
Change the Dropdown
<div class="col-md-10">
#Html.DropDownListFor(model => model.Deal.CustomerId,Model.CustomerListVM,"Select Customer", htmlAttributes: new { #class = "form-control"})
</div>
one more thing since you have created viewmodel for deal. You only need to do this on your Post Controller
public IActionResult Create()
{
CreateDealVM vm = new CreateDealVM();
vm.CustomerListVM = new SelectList (_context.Customers select new { Id = x.customerId.ToString(), Text = x.customerName }),
"Id","Text");
return View(vm);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Deals deal)
{
CreateDealVM vm = new CreateDealVM();
vm.deal = deal;
vm.CustomerListVM = new SelectList (_context.Customers select new { Id = x.customerId.ToString(), Text = x.customerName }),
"Id","Text",VM.CustomerId);
if (ModelState.IsValid)
{
try
{
_context.Deals.Add(vm.deal);
_context.SaveChanges();
return RedirectToAction("Index");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
return View(vm);
}
else
{
var errors = ModelState.Select(x => x.Value.Errors)
.Where(y => y.Count > 0)
.ToList();
//The Error showed here.
}
return View(vm);
}

how i can send multivalue to create action

i have a doctor i want add doctor subspecialty to the doctor from sub specialties table many to many relationship
i need to add subspecialties from multiselect list but my controller only add first selection , i want my create controller take all passed subspecialties and create it
my model
public partial class DoctorSubSpecialty
{
public int Id { get; set; }
public Nullable<int> DoctorId { get; set; }
public Nullable<int> SubSpecialtyId { get; set; }
public virtual DoctorProfile DoctorProfile { get; set; }
public virtual SubSpecialty SubSpecialty { get; set; }
}
}
create get action
public ActionResult Create()
{
ViewBag.DoctorId = new SelectList(db.DoctorProfiles, "Id", "FullName");
ViewBag.SubSpecialtyId = new MultiSelectList(db.SubSpecialties, "id", "Name");
return View();
}
create post action
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include = "Id,DoctorId,SubSpecialtyId")] DoctorSubSpecialty doctorSubSpecialty)
{
DoctorSubSpecialty doctorSub = db.DoctorSubSpecialties.Where(d => d.DoctorId == doctorSubSpecialty.DoctorId & d.SubSpecialtyId == doctorSubSpecialty.SubSpecialtyId).FirstOrDefault();
if (doctorSub == null) {
db.DoctorSubSpecialties.Add(doctorSubSpecialty);
await db.SaveChangesAsync();
}
my view
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>DoctorSubSpecialty</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.DoctorId, "DoctorId", htmlAttributes: new { #class = "control-label col-md-2", #id = "DoctorID" })
<div class="col-md-10">
#Html.DropDownList("DoctorId", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.DoctorId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.SubSpecialtyId, "SubSpecialtyId", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("SubSpecialtyId",(MultiSelectList)ViewBag.SubSpecialtyId, htmlAttributes: new { #multiple = "multiple", #class = "form-control" })
#Html.ValidationMessageFor(model => model.SubSpecialtyId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
Create a ViewModel specific to your usecase that can actually transport more than one Id.
I.e. you will need an int[] to bind the selection to.
A ViewModel also helps you to get rid of all this ViewBag and [Bind] nonsense.
public class CreateDoctorSubSpecialtyViewModel {
// These are the selected values to be posted back
public int DoctorId { get; set; }
public int[] SubSpecialtyIds { get; set; }
// These are the possible values for the dropdowns
public IEnumerable<SelectListItem> DoctorProfiles { get; set; }
public IEnumerable<SelectListItem> SubSpecialties { get; set; }
}
GET action - initialize the ViewModel and pass it to the View:
[HttpGet]
public ActionResult Create() {
var doctorProfiles = db.DoctorProfiles.Select(d =>
new SelectListItem {
Text = d.FullName,
Value = d.Id
}
).ToArray();
var subSpecialties = db.SubSpecialties.Select(s =>
new SelectListItem {
Text = s.Name,
Value = s.id
}
).ToArray();
var viewModel = new CreateDoctorSubSpecialtyViewModel {
DoctorProfiles = doctorProfiles,
SubSpecialties = subSpecialties
};
return View("Create", viewModel);
}
View "Create.cshtml" (styling removed for clarity) - tell MVC which ViewModel we want to use with #model:
#model CreateDoctorSubSpecialtyViewModel
#using (Html.BeginForm("Create", "YourControllerName", FormMethod.Post)) {
#Html.DropDownListFor(m => m.DoctorId, Model.DoctorProfiles)
#Html.DropDownListFor(m => m.SubSpecialtyIds, Model.SubSpecialties, new { multiple = "multiple" })
<input type="submit" />
}
POST action - use Linq Contains to test against multiple submitted SubSpecialtyIds:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create(CreateDoctorSubSpecialtyViewModel postData) {
DoctorSubSpecialty[] allSelectedSubSpecialities = db.DoctorSubSpecialties
.Where(d => d.DoctorId == postData.DoctorId
&& postData.SubSpecialtyIds.Contains(d.SubSpecialtyId))
.ToArray();
// ...
}
EDIT #Html.DropDownListFor requires an IEnumerable<SelectListItem> as second parameter.

MVC MultipleModel DropdownlistFor SaveChanges()

HI i Have a MultipleModel View with CompanyName and EmployeeRange. CompanyName contains names of companies with a Relationship link to EmployeeRange (int) Field.
The Employee Range is basically
0-9
10-19
20-49
I can create and SaveChanges for a new Company Name Field.
Please help With Saving Selected value from the EmployeeRange DropDownListFor to DB.
Here is the Code
//MultipleModel.cs
public partial class MultipleModel
{
public MultipleModel()
{
CompanyEntities = new company();
EmployeeEntities = new Employee();
}
public company CompanyEntities { get; set; }
public Employee EmployeeEntities { get; set; }
}
//CompanyController.cs
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult RegisterCompany(MultipleModel model)
{
if (ModelState.IsValid)
{
//GET VARIBALE FROM DB
var addComanyName = db.companies.Add(model.CompanyEntities);
//GET VARIABLLE
addComanyName.COMPANY_NAME = model.CompanyEntities.COMPANY_NAME;
//ADD VARIBALE TO DB
db.companies.Add(addComanyName);
db.SaveChanges();
return RedirectToAction("index");
}
return View();
}
//RegisterCompany.cshtml
#model EISystem.Models.MultipleModel
#Html.DropDownListFor(m => m.CompanyEntities, new SelectList(ViewBag.products, "Employees_Range_ID", "Employees_Range"), "Select Number of Employees")
?? How Do i view the DropDownListFor so that selected Value can be POST to Controller and later be saved to DB?
You should use another class which is ViewModel for your MultipleModel class.
Like:
public class MultipleModelViewModel
{
public int SelectedProductId { get; set; }
public List<Products> ProductList{ get; set; }
public string Name { get; set; }
}
In a View
#model MultipleModelViewModel
#using (Html.BeginForm("RegisterCompany", "Company", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-group">
#Html.LabelFor(model => model.SelectedProductId , htmlAttributes: new { #class = "form-control" })
#Html.DropDownListFor(model => model.SelectedProductId, new SelectList(Model.ProductList, "Id", "Name"), "Select Product", new { #class = "form-control " })
</div>
<div class="form-group">
#Html.LabelFor(model => model.Name, new { #class = "form-control " })
#Html.TextBoxFor(model => model.Name, new { #class = "form-control" })
</div>
<button type="Submit" class="btn btn-success " id="Save-btn">
Save
</button>
}
Controller:
//Get
public ActionResult RegisterCompany()
{
var model = new MultipleModelViewModel (){
Name = model.Name,
Products = db.Products.Select(x=>new {Id=x.Id, Name=x.Name).ToList()
};
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult RegisterCompany(MultipleModelViewModel model)
{
if (ModelState.IsValid)
{
var company = new Company(){
Name = model.Name,
Product = db.Products.Find(model.SelectedProductId)
};
db.companies.Add(company);
db.SaveChanges();
return RedirectToAction("index");
}
return View();
}

Repopulating selectlist for dropdownlist on postback when default selected ASP.NET MVC

New to ASP.NET MVC and I've come across this before, however I feel I've gone about fixing it in an incorrect way. I was hoping someone could point me in the direction of the correct way of doing it.
I have a page which the user selects a dropdown value and clicks next. Now, if they don't select an item and select the default value ("Select..."), there is a validation error. The controller seems to lose information about the dropdownlist even though the model is returned to the view on a "postback." So, say I don't basically repeat code in my [HTTPPost] from my [HTTPGet]. I get the error:
There is no ViewData item of type 'IEnumerable<SelectListItem>' that has the key 'SheetIndex'.
on
#Html.DropDownListFor(model => model.SheetIndex, Model.SheetsDropdown, "Select...", new { #class = "form-control" })
So unless I repeat code, I get that error when no selection is made. What am I doing wrong?
ViewModel:
public class SelectSheetViewModel
{
public int? Id { get; set; }
public string Name { get; set; }
[Required]
public string SheetIndex { get; set; }
public string SheetName { get; set; }
public int? ChainId { get; set; }
public int? SheetId { get; set; }
public int? FileId { get; set; }
public IEnumerable<SelectListItem> SheetsDropdown { get; set; }
public HeaderViewModel Header { get; set; }
}
Controller:
[HttpGet]
public ActionResult SelectSheet(int? chainId, int? sheetId, int? fileId)
{
if (sheetId == null || fileId == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var fileDetails = db.FileDetails.Find(fileId);
SelectSheetViewModel selectSheet = new SelectSheetViewModel()
{
Id = fileDetails.FileId,
Name = fileDetails.Name,
ChainId = chainId,
SheetId = sheetId,
FileId = fileId
};
string fileName = fileDetails.UniqueName + fileDetails.Extension;
string relativeFileLocation = "~/uploads/" + fileName;
string absoluteFileLocation = HttpContext.Server.MapPath(relativeFileLocation);
if (System.IO.File.Exists(absoluteFileLocation))
{
DSDBuilder builder = new DSDBuilder();
selectSheet.SheetsDropdown = builder.GetSheets(absoluteFileLocation); // Where I get my selectlist
}
else
{
ModelState.AddModelError("SheetDropdown", "Excel workbook does not exist.");
}
selectSheet.Header = BuildHeaderViewModel(chainId, sheetId);
return View(selectSheet);
}
[HttpPost]
public ActionResult SelectSheet(SelectSheetViewModel selectSheet)
{
if (ModelState.IsValid)
{
FileDetail fileDetails = db.FileDetails.Find(selectSheet.FileId);
string[] sheetIndexAndName = selectSheet.SheetIndex.Split(':');
fileDetails.SheetIndex = Convert.ToInt32(sheetIndexAndName[0]);
fileDetails.SheetName = sheetIndexAndName[1];
db.SaveChanges();
return RedirectToAction("Build", "Sheets", new
{
ChainId = selectSheet.ChainId,
SheetId = selectSheet.SheetId,
FileId = selectSheet.FileId
});
}
// Probably not a good method vvv
var fileDetailsPostBack = db.FileDetails.Find(selectSheet.FileId);
string fileName = fileDetailsPostBack.UniqueName + fileDetailsPostBack.Extension;
string relativeFileLocation = "~/uploads/" + fileName;
string absoluteFileLocation = HttpContext.Server.MapPath(relativeFileLocation);
if (System.IO.File.Exists(absoluteFileLocation))
{
DSDBuilder builder = new DSDBuilder();
selectSheet.SheetsDropdown = builder.GetSheets(absoluteFileLocation);
}
else
{
ModelState.AddModelError("SheetDropdown", "Excel workbook does not exist.");
}
// Probably not a good method ^^^
return View(selectSheet);
}
View:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.ChainId)
#Html.HiddenFor(model => model.SheetId)
#Html.HiddenFor(model => model.FileId)
#Html.HiddenFor(model => model.Header.ChainName)
#Html.HiddenFor(model => model.Header.SheetName)
#Html.HiddenFor(model => model.Header.SheetDescription)
<div class="form-horizontal">
<div class="form-group">
#Html.LabelFor(model => model.SheetIndex, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.SheetIndex, Model.SheetsDropdown, "Select...", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.SheetIndex, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Next" class="btn btn-default" />
</div>
</div>
</div>
}
I also would not like to repeat that code because it goes to a function which uses Excel.Interop and is kind of resource heavy. Let me know if anyone has a better solution. I'm always trying to improve my code and do things the "correct" way.

The ViewData item that has the key 'ShelfId' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'

Problem
I use the following code very similarily somewhere else in my application, but it is not working. I am completely stumped.
The ViewData item that has the key 'ShelfId' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'
This is thrown during the post method. My model state is invalid.
Code
Models
Shelf
public class Shelf
{
[Key]
public int ShelfId
[Display(Name = "Shelf Id")]
[Required]
public string ShelfName
public virtual List<Book> Books {get; set;}
}
Book
public class Book
{
public int BookId
[Required]
[StrengthLength(160, MinimumLength = 8)]
public string BookName
public int ShelfId
public Shelf shelf {get; set;}
}
Controller
// GET: Units/Create
public async Task<IActionResult> Create()
{
var shelves = await _db.Shelves.OrderBy(q => q.Name).ToListAsync();
ViewBag.SelectedShelves = new SelectList(shelves, "ShelfId", "Name");
return View();
}
// POST: Units/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Book book)
{
book.CreatedBy = User.Identity.GetUserName();
book.Created = DateTime.UtcNow;
book.UpdatedBy = User.Identity.GetUserName();
book.Updated = DateTime.UtcNow;
if (ModelState.IsValid)
{
db.Units.Add(unit);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(book);
}
view
#model AgentInventory.Models.Book
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Create Unit</title>
</head>
<body>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal well bs-component" style="margin-top:20px">
<h4>Unit</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
<div class="control-label col-md-2">Room</div>
<div class="col-md-10">
#Html.DropDownListFor(model => model.ShelfId, (SelectList)ViewBag.SelectedShelves, "All", new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.BookName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.BookName, "", new { #class = "text-danger" }
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Attempts
I tried:
Adding #Html.HiddenFor(model=>model.ShelfId) in the create view, but that didn't work.
I have looked at similar issues on stackoverflow, but none of the fixes worked for me. (IE - hiddenfor, different kinds of selectlists)
Since I am new to MVC framework, I would be grateful for any assistance. I don't understand why this code works for two other kinds of models (Building and room), but not my current two models? It's weird.
PS - Is there a way to do this easily without using viewbag as well?
The reason for the error is that in the POST method when you return the view, the value of ViewBag.SelectedShelves is null because you have not set it (as you did in the get method. I recommend you refactor this in a private method that can be called from both the GET and POST methods
private void ConfigureViewModel(Book book)
{
var shelves = await _db.Shelves.OrderBy(q => q.Name).ToListAsync();
// Better to have a view model with a property for the SelectList
ViewBag.SelectedShelves = new SelectList(shelves, "ShelfId", "Name");
}
then in the controller
public async Task<IActionResult> Create()
{
// Always better to initialize a new object and pass to the view
Book model = new Book();
ConfigureViewModel(model)
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Book book)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(book)
return View(book);
}
// No point setting these if the model is invalid
book.CreatedBy = User.Identity.GetUserName();
book.Created = DateTime.UtcNow;
book.UpdatedBy = User.Identity.GetUserName();
book.Updated = DateTime.UtcNow;
// Save and redirect
db.Units.Add(unit);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
Note your Book class contains only fields, not properties (no { get; set; }) so no properties will be set and the model will always be invalid because BookName has Required and StringLength attributes.
Also you have not shown all the properties in your model (for example you have CreatedBy, Created etc. and its likely that ModelState will also be invalid because you only generate controls for only a few properties. If any other properties contain validation attributes, then ModelState will be invalid. To handle this you need to create a view model containing only the properties you want to display edit.
public class BookVM
{
public int Id { get; set; }
[Required]
[StrengthLength(160, MinimumLength = 8)]
public string Name { get; set; }
public int SelectedShelf { get; set; }
public SelectList ShelfList { get; set; }
}
Then modify the private method to assign the SelectList to the view model (not ViewBag, and in the controller methods, pass a new instance of BookVM to the view, and post back to
public async Task<IActionResult> Create(BookVM model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model)
return View(model);
}
// Initialize a new Book and set the properties from the view model
}

Resources