ListBoxFor is not fully updating the viewmodel on submit - asp.net-mvc-4

I have been able to create viewmodel like this (please ignore that it includes another viewmodel, that will be fixed after I solve my current problem :) ):
public class UserViewModel
{
#region Variables
private SecUserViewModel user;
private string[] assignedRolesIds;
private List<SecRoleViewModel> availableRoles;
#endregion
#region Properties
public SecUserViewModel User
{
get { return this.user; }
set { this.user = value; }
}
public string Guid
{
get { return this.user.Guid.ToString(); }
set { this.user.Guid = value; }
}
public string UserName
{
get { return this.user.UserName; }
set { this.user.UserName = value; }
}
public string Email
{
get { return this.user.Email; }
set { this.user.Email = value; }
}
public byte[] AuthDigest
{
get { return this.user.AuthDigest; }
set { this.user.AuthDigest = value; }
}
public bool IsUsingTempPasswd
{
get { return this.user.IsUsingTempPasswd; }
set { this.user.IsUsingTempPasswd = value; }
}
public DateTime? LastLogin
{
get { return this.user.LastLogin; }
set { this.user.LastLogin = value; }
}
public DateTime? PasswordChanged
{
get { return this.user.PasswordChanged; }
set { this.user.PasswordChanged = value; }
}
public string[] AssignedRolesIds
{
get { return this.assignedRolesIds; }
set { this.assignedRolesIds = value; }
}
public List<SecRoleViewModel> AvailableRoles
{
get { return this.availableRoles; }
set { this.availableRoles = value; }
}
#endregion
#region Constructor
public UserViewModel()
{
User = new SecUserViewModel();
AssignedRolesIds = null;
AvailableRoles = new List<SecRoleViewModel>(0);
}
public UserViewModel(SecUserViewModel secUser, List<SecRoleViewModel> roleList, List<SecRoleViewModel> availableList)
{
User = secUser;
AssignedRolesIds = roleList.Select(r => r.Role.Guid.ToString()).ToArray();
AvailableRoles = availableList;
}
#endregion
}
My controller has an edit action. On "GET" I pass viewmodel and it is displayed properly including the multiselect list and preselected values. But when I "POST" the "Edit", UserViewModel that is passed back has "AssignedRolesIds" and "AvailableRoles" Empty, although everything else is filled. When I check FormCollection object, there are "AssignedRolesIds" present as a key.
My view looks like this:
#model DocuLive.ViewModels.UserViewModel
#{
ViewBag.Title = "Edit";
Layout = "~/Views/Shared/_AdminPage.cshtml";
}
<h2>Edit</h2>
<div class="error-message">#TempData["Fail"]</div>
<div class="success-message">#TempData["Success"]</div>
#using (Html.BeginForm("Edit", "User", FormMethod.Post))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>SecUser</legend>
<div class="editor-label">
#Html.LabelFor(model => model.User.Guid)
</div>
<div class="editor-field">
#Html.DisplayFor(model => model.User.Guid)
#Html.HiddenFor(model => model.User.Guid)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.User.UserName)
</div>
<div class="editor-field">
#Html.DisplayFor(model => model.User.UserName)
#Html.HiddenFor(model => model.User.UserName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.User.Email)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.User.Email)
#Html.ValidationMessageFor(model => model.User.Email)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.User.IsUsingTempPasswd)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.User.IsUsingTempPasswd)
#Html.ValidationMessageFor(model => model.User.IsUsingTempPasswd)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.User.LastLogin)
</div>
<div class="editor-field">
#Html.DisplayFor(model => model.User.LastLogin)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.User.PasswordChanged)
</div>
<div class="editor-field">
#Html.DisplayFor(model => model.User.PasswordChanged)
</div>
<div class="hidden-field">
#Html.HiddenFor(model => model.User.AuthDigest)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.AssignedRolesIds)
</div>
<div class="editor-field">
#Html.ListBoxFor(x => x.AssignedRolesIds, new MultiSelectList(Model.AvailableRoles, "Guid", "RoleName"), new { Multiple = "multiple", #class = "multi-select-list" })
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Users")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Can anyone advise why only values related to ListBoxFor are not passed back (there is a null value)?
Thanks in advance

It seems that the problem is binding the string array values to string id value of complex object (in my case Role) - but I figured out a way around it eventually. The trick is to have this signature of the method:
[HttpPost]
public ActionResult Edit(UserViewModel user, string [] assignedRolesIds)
And then you have this in view:
#Html.ListBox("AssignedRolesIds",new MultiSelectList(Model.AvailableRoles, "Guid", "RoleName"),new { Multiple = "multiple", #class = "multi-select-list"})
With this solution, you have to reassign "assignedRolesIds" back to "AssignedRolesIds" property on UserViewModel, but that is only two lines (including check that the array is not emppty or null).

Related

How fix not working ActionResult Update in NET Core?

I'm learning about .NET Core and I'm using code from this tutorial. But my update sql is not working.
Here is the index view code:
public ActionResult Index(int? id)
{
ViewBag.Operation = id;
ViewBag.Name = db.Chars.ToList();
Chars Chars = db.Chars.Find(id);
return View(Chars);
}
As for now it work I see results from sql and here is the updated part:
public ActionResult Update(Chars Chars)
{
if (ModelState.IsValid)
{
db.Entry(Chars).State = EntityState.Modified;
db.SaveChanges();
}
return RedirectToAction("Index", new { id = 0 });
}
Here is index.cshtml part:
#using (Html.BeginForm()
{
#foreach (var item in (IEnumerable<MVC__test_2.Chars>)ViewBag.Name)
{
<div class="form-group">
<div class="col-md-10">
#Html.EditorFor(modelItem => item.CharName, new { htmlAttributes = new { #class = "form-control" } })
#Html.HiddenFor(modelItem => item.CharID, new { id = item.CharID })
</div>
</div>
#Html.ActionLink("Index", "Index", new { id = item.CharID })
<input type="submit" value="Update" name="Update"
style=#((ViewBag.Operation != null && Convert.ToInt32(ViewBag.Operation) > 0) ? "display:block" : "display:none") />
}
}
According to the tutorial you provided , I made a demo to test and it updated the data well. The following is the working example , you could refer to and make the modification as per your need .
Model
public class Description
{
public int Id { get; set; }
public string Display { get; set; }
}
Controller
public IActionResult Index(int? id)
{
ViewBag.Operation = id;
ViewBag.Name = _context.Description.ToList();
Description description= _context.Description.Find(id);
return View(description);
}
public ActionResult Update(Description description)
{
if (ModelState.IsValid)
{
_context.Entry(description).State = EntityState.Modified;
_context.SaveChanges();
}
return RedirectToAction("Index", new { id = 0 });
}
Index.cshtml , you should hide the id of the modified data in the modification section.
#model WebApplication1.Models.Description
#using (Html.BeginForm("Update", "Home", FormMethod.Post))
{
#foreach (var item in (IEnumerable<WebApplication1.Models.Description >)ViewBag.Name)
{
<div class="form-group">
<div class="col-md-10">
#Html.EditorFor(modelItem => item.Display, new { htmlAttributes = new { #class = "form-control" } })
#Html.HiddenFor(modelItem => item.Id, new { id = item.Id })
</div>
</div>
#Html.ActionLink("Edit", "Index", new { id = item.Id })
}
// Create or Update data
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true)
<fieldset>
<legend> <b>Entry Screen</b></legend>
<div class="form-group">
#Html.LabelFor(model => model.Display, new { #class = "control-label col-md-2" })
#Html.HiddenFor(model => model.Id)
<div class="col-md-10">
#Html.EditorFor(model => model.Display)
#Html.ValidationMessageFor(model => model.Display)
</div>
</div>
<div class="form-group">
<p>
<input type="submit" value="Create" name="Create"
style=#((ViewBag.Operation != null && Convert.ToInt32(ViewBag.Operation) > 0) ? "display:none" : "display:block") />
<input type="submit" value="Update" name="Update"
style=#((ViewBag.Operation != null && Convert.ToInt32(ViewBag.Operation) > 0) ? "display:block" : "display:none") />
</p>
</div>
</fieldset>
</div>
}

why delete failed to removed the record?

i have the following action for the delete operation:
[HttpPost]
public ActionResult Delete(int id, EmployeeDeleteViewModel collection)
{
try
{
if (ModelState.IsValid)
{
Employee e = new Employee
{
EmpId = collection.EmpID,
FirstName = collection.FirstName,
LastName = collection.LastName,
DepartmentId = collection.DepartmentID
};
db.Employees.Remove(e);
db.SaveChanges();
return RedirectToAction("Index", new { id = id });
}
// TODO: Add update logic here
return View(collection);
}
catch
{
return View();
}
}
the delete view is:
#model VirtualCampus2.Models.EmployeeDeleteViewModel
#{
ViewBag.Title = "Delete";
}
<h2>Delete</h2>
<h3>Are you sure you want to delete this?</h3>
<fieldset>
<legend>EmployeeDeleteViewModel</legend>
<div class="display-label">
#Html.DisplayNameFor(model => model.EmpID)
</div>
<div class="display-field">
#Html.DisplayFor(model => model.EmpID)
</div>
<div class="display-label">
#Html.DisplayNameFor(model => model.FirstName)
</div>
<div class="display-field">
#Html.DisplayFor(model => model.FirstName)
</div>
<div class="display-label">
#Html.DisplayNameFor(model => model.LastName)
</div>
<div class="display-field">
#Html.DisplayFor(model => model.LastName)
</div>
<div class="display-label">
#Html.DisplayNameFor(model => model.DepartmentName)
</div>
<div class="display-field">
#Html.DisplayFor(model => model.DepartmentName)
</div>
<div class="display-label">
#Html.DisplayNameFor(model => model.DepartmentID)
</div>
<div class="display-field">
#Html.DisplayFor(model => model.DepartmentID)
</div>
</fieldset>
#using (Html.BeginForm())
{
<p>
<input type="submit" value="Delete" /> |
#Html.ActionLink("Back to List", "Index")
</p>
}
When i click delete on the delete view the following error occurs:
to action method's second parameter "collection", it sends a collection with zeros and null in their properties
does not delete the record
Here is video that shows the problem
why this happens and how do i fix this?
To Steve:
I have made the changes by creating a separate view model and a delete action:
ViewModel:
public class EmpDeleteCommitViewModel
{
public int EmpID { get; set; }
}
the actions Delete methods:
[HttpGet]//this works fine, gets the record to show on view
public ActionResult Delete(int id)
{
var empList = db.Employees.ToList();
var employee = empList.Where(e => e.EmpId == id).Select(e => new EmployeeDeleteViewModel
{
EmpID=e.EmpId,
FirstName= e.FirstName,
LastName=e.LastName,
DepartmentID=e.DepartmentId,
DepartmentName=e.Department.Name
}).FirstOrDefault();
return View(employee);
}
[HttpPost] //BUT THIS DOES NOT WORK!, evm EmpID does not contain id value
public ActionResult Delete(EmpDeleteCommitViewModel evm)
{
try
{
var employee = db.Employees.Where(e => e.EmpId == evm.EmpID).FirstOrDefault();
db.Employees.Remove(employee);
db.SaveChanges();
return RedirectToAction("Index", new { id = evm.EmpID });
}
catch
{
return View();
}
}
You have no form controls (<input>) between form tags so there is nothing to post back when you submit the form. All you doing is generating some text to display the property values.
There is no need to include the EmployeeDeleteViewModel collection parameter in you method. Your int id parameter will be bound with the id of the employee assuming your using the correct routing, so all you need to to get the original data model from the database based on the id and then delete it.

How to retrieve Posted file in create action of another entity

I am in trouble with a posted file related to an entity, I can see the posted file in the request, but if I add the httppostedfile as action parameter it will be null... what should I do to manage this case?
NewsArticle model:
public class NewsArticle
{
public string Title{get; set;}
public int ID{get; set;}
[AllowHtml]
public string Body { get; set; }
public Image Image { get; set; }
//other stuff
}
where the field Image is:
the related entity will be created in the newarticlecontroller
public class Image
{
public int ID { get; set; }
public string URL { get; set; }
public string Title { get; set; }
public string Subtitle { get; set; }
}
detail of view
#model GatorsWebSite.NewsArticle
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm("Create", "NewsArticles", FormMethod.Post, new { enctype = "multipart/forma-data" }))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>NewsArticle</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Title, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Title, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Title, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Body, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextAreaFor(model => model.Body, new { htmlAttributes = new { #class = "form-control ckHolder" } })
#Html.ValidationMessageFor(model => model.Body, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.SubTitle, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.SubTitle, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.SubTitle, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Image, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.Image, new { type = "file" })
#Html.ValidationMessageFor(m => m.Image)
</div>
</div>
#*<div class="form-group">
#Html.LabelFor(model => model.AuthorID, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.AuthorID, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.AuthorID, "", new { #class = "text-danger" })
</div>
</div>*#
#*<div class="form-group">
#Html.LabelFor(model => model.Date, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Date, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Date, "", new { #class = "text-danger" })
</div>
</div>*#
<div class="form-group">
#Html.LabelFor(model => model.Published, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<div class="checkbox">
#Html.EditorFor(model => model.Published)
#Html.ValidationMessageFor(model => model.Published, "", new { #class = "text-danger" })
</div>
</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>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
#Scripts.Render("~/bundles/ckEditor")
<script type="text/javascript">
CKEDITOR.replace("#Html.IdFor(m => m.Body)", {});
</script>
}
Controller action:
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,Title,Body,SubTitle,Published,Image")]NewsArticle newsArticle )// here I don't know how to manage the posted file
{
if (ModelState.IsValid)
{
newsArticle.Date = DateTime.Now;
newsArticle.AuthorID = User.Identity.GetUserId();
newsArticle.Body = newsArticle.Body;
_repository.Add(newsArticle);
return RedirectToAction("Index");
}
return View(newsArticle);
}
Any help will be really appreciated
And what if you put input file in your form like:
<div class="form-group">
<label for="newsimage">Select news image:</label>
<div class="col-md-10">
<div class="checkbox">
<input id="newsimage" type="file" name="newsimage"/>
</div>
</div>
</div>
and post action will be:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,Title,Body,SubTitle,Published,Image")]NewsArticle newsArticle )// here I don't know how to manage the posted file
{
if (ModelState.IsValid)
{
string ImageUrl="";
for (int i = 0; i < Request.Files.Count; i++)
{
if (Request.Files[i].FileName!="")
{
string UrlFile = Server.MapPath("~/newsimagefolderpath/") + FileName;
Request.Files[i].SaveAs(UrlFile);
ImageUrl = UrlFile;
}
}
newsArticle.Date = DateTime.Now;
newsArticle.URL = string.IsNullOrEmpty(ImageUrl) ? "defaultimagepath" : ImageUrl;
newsArticle.AuthorID = User.Identity.GetUserId();
newsArticle.Body = newsArticle.Body;
_repository.Add(newsArticle);
return RedirectToAction("Index");
}
return View(newsArticle);
}
Ok, I am new to MVC so obviously I have to pay the tax, btw this is what I have done to solve this problem:
I have created a ViewModel to host the aggregated data of the view:
public class NewsArticleViewModel
{
public string Title { get; set; }
public int ID { get; set; }
[AllowHtml]
public string Body { get; set; }
public string SubTitle { get; set; }
public string AuthorID { get; set; }
public DateTime Date { get; set; }
public bool Published { get; set; }
public HttpPostedFileBase ImageUpload { get; set; }
}
then I have changed the signature of the control action:
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Title,Body,SubTitle,Published,ImageUpload")]GatorsWebSite.ViewModels.NewsArticleViewModel newsArticle)
then I have corrected a typo in the form declaration :)
#using (Html.BeginForm("Create", "NewsArticles", FormMethod.Post, new { enctype = "multipart/form-data" }))
...and then I have changed the view
#Html.LabelFor(model => model.ImageUpload, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.ImageUpload, new { type = "file" })
#Html.ValidationMessageFor(m => m.ImageUpload)
</div>
Result:

Can you tell how to make a update in DB, with fileupload (File is updated ok)?

Can you tell how to make a update in DB, using fileupload (File is updated ok) ?
I dont get any errors message, but the edit controller make a new row in DB, instead of updating the old row.
I have tried to remove insertOnsubmit, but only result is no update at all, in DB
I'am using LINQ to SQL MVC4
Table id - CompanyNameCon - PdfCon
Controller:
public ActionResult Edit(int id = 0)
{
DAT_SupplyCon SupplyCon = db.DAT_SupplyCons.Where(x => x.ID == id).FirstOrDefault();
if (SupplyCon == null)
{
return HttpNotFound();
}
return View(SupplyCon);
}
//
// POST: /Books/Edit/5
[HttpPost]
public ActionResult Edit(DAT_SupplyCon DAT_SupplyCon, HttpPostedFileBase file)
{
if (ModelState.IsValid)
{
// Delete old file
FileUpload.DeleteFile(DAT_SupplyCon.PdfCon);
// Upload our file
DAT_SupplyCon.PdfCon = FileUpload.UploadFile(file);
???? db.DAT_SupplyCons.InsertOnSubmit(DAT_SupplyCon);
db.SubmitChanges();
return RedirectToAction("Index");
}
return View(DAT_SupplyCon);
}
view
#model CFire2.Models.DAT_SupplyCon
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
<script src="~/Scripts/jquery-1.8.2.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
#using (Html.BeginForm("Edit", "SupplyCon", FormMethod.Post, new { enctype = "multipart/form- data" }))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>DAT_SupplyCon</legend>
<div class="editor-label">
#Html.LabelFor(model => model.SupplierCon)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.SupplierCon)
#Html.ValidationMessageFor(model => model.SupplierCon)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.CompanyNameCon)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.CompanyNameCon)
#Html.ValidationMessageFor(model => model.CompanyNameCon)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.PdfCon)
</div>
<div class="editor-field">
<input type="file" name="file" />
#Html.HiddenFor(model => model.PdfCon)
#Html.ValidationMessageFor(model => model.PdfCon)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Utils:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IO;
namespace CFire2.Utils
{
public static class FileUpload
{
public static char DirSeparator =
System.IO.Path.DirectorySeparatorChar;
public static string FilesPath = "Content" +
DirSeparator + "Uploads" + DirSeparator;
public static string UploadFile(HttpPostedFileBase file)
{
if (null == file) return "";
if (!(file.ContentLength > 0)) return "";
string fileName = file.FileName;
string fileExt = Path.GetExtension(file.FileName);
if (null == fileExt) return "";
if (!Directory.Exists(FilesPath))
{
Directory.CreateDirectory(FilesPath);
}
var path = Path.Combine(HttpContext.Current.Server.MapPath("~/Content/SupplyCon"),fileName);
file.SaveAs(Path);
return fileName;
}
public static void DeleteFile(string fileName)
{
if (fileName.Length == 0) return;
var path = Path.Combine(HttpContext.Current.Server.MapPath("~/Content/SupplyCon"), fileName);
if (File.Exists(Path.GetFullPath(path)))
{
File.Delete(Path.GetFullPath(path));
}
}
}
}
Add a hidden field for your primary key property to your form.
#Html.HiddenFor(model => model.ID)
Otherwise it'll be treated as a new record since no primary key is posted.
InsertOnSubmit method work on the basis of primary key .
if primary key value is not exists in DB it will create a new record otherwise it will update that record .
In your case you can use hidden field to store the primary key id into the model .
#Html.HiddenFor(model => model.PrimaryKeyID)

asp.net mvc 4 dropdownlist does not return a value

I have 2 models - Question and Category -
public class Question
{
[ScaffoldColumn(false)]
public int QuestionId { get; set; }
[Required]
public string QuestionText { get; set; }
[Required]
public string AnswerA { get; set; }
[Required]
public string AnswerB { get; set; }
[Required]
public string AnswerC { get; set; }
[Required]
public string AnswerD { get; set; }
[Required]
public int Correct { get; set; }
[ForeignKey("Category")]
[Display(Name = "Category")]
[Required]
public int categoryId;
//Navigation property
public virtual Category Category { get; set; }
}
public class Category
{
[ScaffoldColumn(false)]
public int CategoryId { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Question> Question { get; set; }
}
In my QuestionController, I have added code to be able to access the available categories for a dropdownlist in the view -
private void PopulateCategoryDropDownList(object selectedCategory = null)
{
var categoryQuery = from c in db.Categories
orderby c.Name
select c;
ViewBag.categoryId = new SelectList(categoryQuery, "CategoryId", "Name", selectedCategory);
}
And I have the following methods for create -
// GET: /Question/Create
public ActionResult Create()
{
PopulateCategoryDropDownList();
return View();
}
//
// POST: /Question/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Question question)
{
try
{
var errors = ModelState.Values.SelectMany(v => v.Errors);
if (ModelState.IsValid)
{
db.Questions.Add(question);
db.SaveChanges();
return RedirectToAction("Index");
}
}
catch (DataException dex)
{
ModelState.AddModelError("",dex.Message);
}
PopulateCategoryDropDownList(question.Category.CategoryId);
return View(question);
}
My view for creating a new question is as follows -
#model Quiz.Models.Question
#{
ViewBag.Title = "Create";
}
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Question</legend>
<div class="editor-label">
#Html.LabelFor(model => model.QuestionText)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.QuestionText)
#Html.ValidationMessageFor(model => model.QuestionText)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.AnswerA)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.AnswerA)
#Html.ValidationMessageFor(model => model.AnswerA)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.AnswerB)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.AnswerB)
#Html.ValidationMessageFor(model => model.AnswerB)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.AnswerC)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.AnswerC)
#Html.ValidationMessageFor(model => model.AnswerC)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.AnswerD)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.AnswerD)
#Html.ValidationMessageFor(model => model.AnswerD)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Correct)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Correct)
#Html.ValidationMessageFor(model => model.Correct)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.categoryId)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.categoryId,(SelectList)ViewBag.categoryId)
#Html.ValidationMessageFor(model => model.categoryId)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
So, the issue is that although the question can be created, the categoryId from the dropdownlist is always null.
I have tried a bunch of things, ranging from attempting to access the dropdownlist directly to creating a different viewmodel. However, none of them work as required. Also, my code follows the tutorials available online. I'm not able to figure out what is different.
Please do help me find the mistake in my code.
We might have to narrow things down, I have a feeling something is going wrong with the models being read correctly from ViewBag. Try replacing your Create actions for a moment with the following, where your custom ViewBag filling function has been removed:
public ActionResult Create() {
ViewBag.categoryId = new SelectList(db.Categories, "CategoryId", "Name");
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Question question) {
try {
var errors = ModelState.Values.SelectMany(v => v.Errors);
if (ModelState.IsValid) {
db.Questions.Add(question);
db.SaveChanges();
return RedirectToAction("Index");
}
} catch (DataException dex) {
ModelState.AddModelError("",dex.Message);
}
ViewBag.categoryId = new SelectList(db.Categories, "CategoryId", "Name", question.Category.CategoryId);
return View(question);
}
Does this run correctly?
If this doesn't work it must be a model-binding issue. The last thing I can think of trying is change the ViewBag calls to effect the field Category instead of CategoryId. Also update your view when making the DropDownList.