Can't figure out what I'm doing wrong. When the form in the view is posted the model properties turn out to be null.
Model
public class RegistrationModel
{
public RegistrationModel()
{
Registration = new REGISTRATION();
AddPayment = true;
}
public REGISTRATION Registration { get; set; }
public bool AddPayment { get; set; }
}
View
#model Client.Models.RegistrationModel
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(excludePropertyErrors: false)
<div class="form-group">
#Html.DropDownList("SECTION_ID", null, string.Empty, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.DropDownList("STUDENT_ID", null, string.Empty, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.DropDownList("STATUS_ID", null, string.Empty, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.CheckBoxFor(model => model.AddPayment)
</div>
<p>
<input type="submit" class="btn btn-success" value="Create" />
</p>
}
Controller
public ActionResult Create()
{
//code to populate view dropdowns
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(RegistrationModel model)
{
WriteFileLog(_logPath, Newtonsoft.Json.JsonConvert.SerializeObject(model));
}
In the controller's Create action that handles the post model properties are null.
Registration Class (autogenerated by EF from database):
public partial class REGISTRATION
{
public REGISTRATION()
{
this.REGISTRATION_AUDIT = new HashSet<REGISTRATION_AUDIT>();
}
public int ID { get; set; }
public int SECTION_ID { get; set; }
public int STUDENT_ID { get; set; }
public int STATUS_ID { get; set; }
public virtual ICollection<REGISTRATION_AUDIT> REGISTRATION_AUDIT { get; set; }
public virtual SECTION SECTION { get; set; }
public virtual V_REGISTRATION_STATUS V_REGISTRATION_STATUS { get; set; }
public virtual PERSON PERSON { get; set; }
}
I would recommend using the strongly-typed helpers, like so:
#Html.DropDownListFor(m => m.Registration.SECTION_ID, null, string.Empty, new { #class = "form-control" })
Otherwise, you need to adjust the names you're using to
#Html.DropDownList("Registration.SECTION_ID", null, string.Empty, new { #class = "form-control" })
You can probably simplify what you're doing by duplicating the Registration class's members into your view model, replacing the Registration property.
As #StephenMuecke points out, you're missing a few parts from your model/markup. The template for the DropDownList helper you're using is
DropDownListFor(
[model property to bind],
[collection of possible values to bind],
[option label],
[HTML attributes])
Passing null for that second parameter means you have no values to populate the generated <select> element with, and should normally generate an exception.
I'm not a fan of using ViewBag to pass collections into the view, so I'd recommend something like
public class RegistrationModel
{
public RegistrationModel()
{
Registration = new REGISTRATION();
AddPayment = true;
}
public REGISTRATION Registration { get; set; }
public bool AddPayment { get; set; }
public SelectList Sections { get; set; }
public SelectList Students { get; set; }
public SelectList Statuses { get; set; }
}
and then adjust the markup accordingly:
#Html.DropDownListFor(m => m.Registration.SECTION_ID, Model.Sections, string.Empty, new { #class = "form-control" })
Related
During the next time, I could create some posts because I'm learning C# and ASP.NET MVC. I'm coming from Pythonic world, so some things are not clear for me.
I would like to generate a List of strings, then I would like to display this list in my form as a DropDownList.
This is my model:
public class Joueur
{
public int ID { get; set; }
[Required, Display(Name = "Nom"), StringLength(30)]
public string Lastname { get; set; }
[Required, Display(Name = "Prénom"), StringLength(30)]
public string Firstname { get; set; }
[Required, StringLength(15)]
public string Poste { get; set; }
public string Image { get; set; }
}
This is my controller according to Create Method:
// GET: Joueurs/Create
public ActionResult Create()
{
List<Strings> posteList = new List<SelectListItem>{ "Gardien", "Défenseur", "Milieu", "Attaquant" };
ViewBag.PosteList = posteList;
return View();
}
And this is my view:
<div class="col-md-10">
#*ViewBag.PosteList is holding all the postes values*#
#Html.DropDownListFor(model => model.Poste, ViewBag.PosteList as SelectList, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Poste, "", new { #class = "text-danger" })
</div>
But I get this issue:
#Html.DropDownListFor(model => model.Poste, ViewBag.PosteList as SelectList, new { #class = "form-control" })
There is no ViewData element of type « IEnumerable » with the key « Poste ».
How I could do that ?
With Django, it's pretty easy, in my model I create a dictionary and I pass this dict in the property, but with C# ASP.NET? I don't find a way to do that.
I assume your view will display a form to represent the Joueur object that you want the user to fill out, and your ViewBag.PosteList will have the values that the user can select from for the Joueur.Poste property. In order to accomplish this, you should create a new/empty Joueur object in your Create controller method and pass it to the view like so:
public ActionResult Create()
{
var model = new Joueur();
List<Strings> posteList = new List<SelectListItem>{ "Gardien", "Défenseur", "Milieu", "Attaquant" };
ViewBag.PosteList = posteList;
return View(model);
}
Then the rest of your original code should work.
I found a solution, hopefully it's a good way:
In my model I created an Enum:
public class Joueur
{
public int ID { get; set; }
[Required, Display(Name = "Nom"), StringLength(30)]
public string Lastname { get; set; }
[Required, Display(Name = "Prénom"), StringLength(30)]
public string Firstname { get; set; }
[Required, StringLength(15)]
public Position Poste { get; set; }
public string Image { get; set; }
}
public enum Position
{
Gardien,
Défenseur,
Milieu,
Attaquant
}
And in my view I added:
<div class="form-group">
#Html.LabelFor(model => model.Poste, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(m => m.Poste, new SelectList(Enum.GetValues(typeof(FCSL.Models.Joueur.Position))), "Sélectionner le poste", new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Poste, "", new { #class = "text-danger" })
</div>
</div>
And I applied migration commands. It seems to work now.
#Html.DropDownList("Poste", new SelectList( ViewBag.PosteList, "id", "Poste"))
and in controller
public ActionResult Create()
{
List<Strings> posteList = new List<SelectListItem>{ "Gardien", "Défenseur", "Milieu", "Attaquant" };
ViewBag.PosteList = posteList;
return View(ViewBag.PosteList); // return viewbag
}
I have these models and a view model. When form posting to create the entries, the selected value for WardID and DoctorID becomes zero in the controllers POST method.
View code
#model NewClient.HospitalMgt.ViewModel.PatientAdmissionViewModel
....
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<div class="editor-label">
#Html.LabelFor(model => model.Admission.WardID, "Ward")
</div>
<div class="editor-field">
#Html.DropDownList("WardID", String.Empty)
#Html.ValidationMessageFor(model => model.Admission.WardID)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Admission.DoctorID, "Doctor")
</div>
<div class="editor-field">
#Html.DropDownList("DoctorID", String.Empty)
#Html.ValidationMessageFor(model => model.Admission.DoctorID)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Patient.PatientName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Patient.PatientName)
#Html.ValidationMessageFor(model => model.Patient.PatientName)
</div>
.... //more controls for properties of Patient
<div class="editor-label">
#Html.LabelFor(model => model.Admission.AdmissionNumber)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Admission.AdmissionNumber)
#Html.ValidationMessageFor(model => model.Admission.AdmissionNumber)
</div>
.... //more controls for properties of Admission
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
Controller code
public ActionResult Create()
{
ViewBag.WardID = new SelectList(db.Wards, "WardID", "WardNumber");
ViewBag.DoctorID = new SelectList(db.Doctors, "DoctorID", "DoctorName");
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(PatientAdmissionViewModel Id)
{
if (ModelState.IsValid)
{
var c = new Patient()
{
Address = Id.Patient.Address,
ContactNumber = Id.Patient.ContactNumber,
EmergencyContact = Id.Patient.EmergencyContact,
PatientName = Id.Patient.PatientName,
};
var a = new Admission()
{
AdmissionNumber = Id.Admission.AdmissionNumber,
AdmissionDate = Id.Admission.AdmissionDate,
**WardID = Id.Admission.WardID, //becomes zero
DoctorID = Id.Admission.DoctorID //becomes zero**
};
db.Patients.Add(c);
db.Admissions.Add(a);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.WardID = new SelectList(db.Wards, "WardID", "WardNumber", Id.Admission.WardID);
ViewBag.DoctorID = new SelectList(db.Doctors, "DoctorID", "DoctorName", Id.Admission.DoctorID);
return View(Id);
}
Models and View models
//[NotMapped]
public class PatientAdmissionViewModel
{
public Patient Patient { get; set; }
public Admission Admission { get; set; }
}
public class Patient
{
public int PatientID { get; set; }
[Required(ErrorMessage = "A Patient Name is required")]
public string PatientName { get; set; }
public string Address { get; set; }
[Required(ErrorMessage = "A Contact Number is required")]
public string ContactNumber { get; set; }
public string EmergencyContact { get; set; }
public virtual Ward Ward { get; set; }
}
public class Admission
{
public int AdmissionID { get; set; }
public string AdmissionNumber { get; set; }
public Nullable<int> PatientID { get; set; }
public int WardID { get; set; }
public int DoctorID { get; set; }
public System.DateTime AdmissionDate { get; set; }
public virtual Ward Ward { get; set; }
public virtual ICollection<PatientPayment> PatientPayments { get; set; }
}
You use of #Html.DropDownList("WardID", String.Empty) and #Html.DropDownList("DoctorID", String.Empty) are generating inputs with name="WardID" and name="DoctorID" respectively, but you model does not contain properties with those names (it does however contain a property named Admission which contains those properties).
You 'view model' is not really a view model (a view model should not contain properties which are data models when editing). Your view model should also contain properties for the SelectList's so that you can strongly bind to your model. Your view model needs to be
public class PatientAdmissionViewModel
{
[Display(Name = "Ward"]
[Required(ErrorMessage = "Please select a ward")]
public int? SelectedWard { get; set; }
[Display(Name = "Doctor"]
[Required(ErrorMessage = "Please select a doctor")]
public IEnumerable<SelectListItem> WardList { get; set; }
public int? SelectedDoctor { get; set; }
public IEnumerable<SelectListItem> DoctorList { get; set; }
public string PatientName { get; set; }
... // other properties of Patient that you need in the view
public string AdmissionNumber{ get; set; }
... // other properties of Admission that you need in the view
}
and in the GET method, initialize an instance of the view model, set its properties and return it to the view
public ActionResult Create()
{
var model = new PatientAdmissionViewModel()
{
WardList = new SelectList(db.Wards, "WardID", "WardNumber"),
DoctorList = new SelectList(db.Doctors, "DoctorID")
};
return View(model);
}
and in the view
#Html.DropDownListFor(m => m.SelectedWard, Model.WardList, "-Please select-")
....
#Html.DropDownListFor(m => m.SelectedDoctor, Model.DoctorList, "-Please select-")
and finally, in the POST method, you initialize new instances of your data models, set their properties based on the values in the view models and save the data models.
I also recommend you read the answers to What is ViewModel in MVC?.
instead of this
#Html.DropDownList("WardID", String.Empty)
#Html.DropDownList("DoctorID", String.Empty)
use this on view
#Html.DropDownListFor(model => model.Admission.WardID, (SelectList)ViewBag.WardID, "--Select One--")
#Html.DropDownListFor(model => model.Admission.DoctorID, (SelectList)ViewBag.DoctorID, "--Select One--")
use this on controller
WardID = Id.Admission.WardID,
DoctorID = Id.Admission.DoctorID
I have the following ViewModel:
public class ActivityReportViewModel
{
public Dictionary<int, List<string>> Periods { get; set; }
public List<Project> Projects { get; set; }
public List<Templates> Templates { get; set; }
public DateTime TimePeriod { get; set; }
}
public class Project
{
public string Customer { get; set; }
public string ProjectNumber { get; set; }
public string ProjectDescription { get; set; }
public bool IsSelected { get; set; }
public int TemplateId { get; set; }
public bool XLSX { get; set; }
public bool PDF { get; set; }
}
I fill this ViewModel in my controller and then send it to my Create view, which works fine and the values of the Projects property are all there. However, when I postback the data to the server, the values are gone. I tried supplying HiddenFields to all properties of each Project to no avail. Here's my relevant view markup:
<div>
#Html.LabelFor(model => model.Projects, htmlAttributes: new { #class = "ms-Label" })
<ul class="ms-List" style="list-style:none;">
#for (int x = 0; x < Model.Projects.Count; x++)
{
<li class="ms-ListItem">
<span class="ms-ListItem-primaryText">#Model.Projects[x].ProjectDescription</span>
<span class="ms-ListItem-secondaryText">#Model.Projects[x].Customer</span>
<span class="ms-ListItem-tertiaryText">#Model.Projects[x].ProjectNumber</span>
#*<div class="ms-ListItem-selectionTarget js-toggleSelection"></div>*#
#Html.HiddenFor(m => Model.Projects[x].IsSelected)
#Html.HiddenFor(m => Model.Projects[x].ProjectDescription)
#Html.HiddenFor(m => Model.Projects[x].Customer)
#Html.HiddenFor(m => Model.Projects[x].ProjectNumber)
#Html.HiddenFor(m => Model.Projects[x].XLSX)
#Html.HiddenFor(m => Model.Projects[x].PDF)
<div class="ms-Dropdown">
<i class="ms-Dropdown-caretDown ms-Icon ms-Icon--caretDown"></i>
#Html.DropDownListFor(m => m.Projects[x].TemplateId, new SelectList(Model.Templates, "Id", "Name"), new { #class = "ms-Dropdown-select" })
</div>
<div class="ms-ChoiceField">
<input id="excel+#Model.Projects[x].ProjectNumber" class="ms-ChoiceField-input" value="#Model.Projects[x].XLSX" type="checkbox">
<label for="excel+#Model.Projects[x].ProjectNumber" class="ms-ChoiceField-field"><span class="ms-Label is-required">Excel</span></label>
</div>
<div class="ms-ChoiceField">
<input id="pdf+#Model.Projects[x].ProjectNumber" class="ms-ChoiceField-input" value="#Model.Projects[x].PDF" type="checkbox">
<label for="pdf+#Model.Projects[x].ProjectNumber" class="ms-ChoiceField-field"><span class="ms-Label is-required">PDF</span></label>
</div>
</li>
}
</ul>
<div>
#Html.ValidationMessageFor(model => model.Projects, "", new { #class = "text-danger" })
</div>
</div>
EDIT:
Here's my POST action method:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ActivityReportViewModel report)
{
using (AppContainer _db = new AppContainer())
{
if (ModelState.IsValid)
{
_db.SaveChanges();
return RedirectToAction("Index");
}
return PartialView(report);
}
}
The DefaultViewModel Binder uses the HTML attribute name to determine which property to bind back to on the server. I cannot see name attribute specified on your input element. Please specify name attribute on the elements you wish to post back to the server with the property of the view model.
Specifiy name attribute as below. Notice I have added name attribute with value as the property of your view model
<input id="excel+#Model.Projects[x].ProjectNumber" class="ms-ChoiceField-input" name="#Model.Projects[x].ProjectNumber" value="#Model.Projects[x].XLSX" type="checkbox">
My main entity is the Recipe which contains a collection of Ingredient items as follows:
public class Recipe {
[Key]
public virtual int RecipeId { get; set; }
public string RecipeName { get; set; }
...
public virtual ApplicationUser LastModifiedBy { get; set; }
public virtual IList<Ingredient> Ingredients { get; set; }
}
public class Ingredient {
public virtual int IngredientId { get; set; }
[Display(Name = "Name")]
public string IngredientName { get; set; }
....
public virtual IList<Recipe> Recipes { get; set; }
}
Which is fine. Then my controller and view for creating a new Recipe are as follows:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "stuff to include")] Recipe recipe)
{
IList<int> ingredientIds = (ModelState.Values.ElementAt(1).Value.AttemptedValue).Split(',').Select(int.Parse).ToList(); //[1,2,3,4,5]
foreach (int id in ingredientIds) {
Ingredient ing = db.Ingredients.Where(i => i.IngredientId == id).FirstOrDefault() as Ingredient;
recipe.Ingredients.Add(ing);
}
db.Recipes.Add(recipe);
db.SaveChanges();
return RedirectToAction("Index");
ViewBag.Ingredients = new MultiSelectList(db.Ingredients,
"IngredientId", "IngredientName", string.Empty);
ViewBag.CreatedById = new SelectList(db.Users, "Id", "Email", recipe.CreatedById);
return View(recipe);
}
And the view:
#for (Int16 i = 0; i < 5; i++) {
<div class="form-group">
#Html.LabelFor(model => model.Ingredients, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("Ingredients", null, htmlAttributes: new { #class = "form-control" })
</div>
</div>
}
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="button" value="Add Ingredients" class="btn btn-default" />
</div>
</div>
So this sets ModelState.Values.ElementAt(1).Value.AttemptedValue = "1,3,5,4,5" where this is a list of id numbers. I know I can come in before the if (ModelState.IsValid) and iterate through the above and place it into recipe.Ingredients which is fine except...
It feels just so un ASP.NET MVC like, as if there's no way they could have thought of so much and not thought of this scenario? Am I missing something here? The ingredients list will be too long to make a multi select list any use.
You are creating arbitrary dropdownlists that all have the same id (invalid html) and name attribute that has no relationship to your model and wont bind on post back. You first need to create view models that represent what you want to display.
public class RecipeVM
{
[Required]
public string Name { get; set; }
[Display(Name = Ingredient)]
[Required]
public List<int?> SelectedIngredients { get; set; }
public SelectList IngredientList { get; set; }
}
Then in the controller
public ActionResult Create()
{
RecipeVM model = new RecipeVM();
// add 5 'null' ingredients for binding
model.SelectedIngredients = new List<int?>() { null, null, null, null, null };
ConfigureViewModel(model);
return View(model);
}
[HttpPost]
public ActionResult Create(RecipeVM model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model);
return View(model);
}
// Initialize new instance of your data model
// Map properties from view model to data model
// Add values for user, create date etc
// Save and redirect
}
private void ConfigureViewModel(RecipeVM model)
{
model.IngredientList = new SelectList(db.Ingredients, "IngredientId", "IngredientName");
}
View
#model RecipeVM
#using (Html.BeginForm())
{
#Html.LabelFor(m => m.Name)
#Html.TextBoxFor(m => m.Name)
#Html.ValidationMessageFor(m => m.Name)
for (int i = 0; i < Model.SelectedIngredients.Count; i++)
{
#Html.LabelFor(m => m.SelectedIngredients[i])
#Html.DropDownListFor(m => m.SelectedIngredients[i], Model.IngredientList, "-Please select-")
}
}
Note this is based on your current implementation of creating 5 dropdowns to select 5 ingredients. In reality you will want to dynamically add ingredients (start with none). The answers here and here give you a few options to consider.
I'm trying to bind a ListBox in my partial view but I'm getting the exception...
Object reference not set to an instance of an object.
Referencing #Html.ListBoxFor(x => x.SelectedRoles, Model.Roles)
I'm not sure what I'm doing wrong...
Model:
public class RegisterModel {
public string UserName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string ConfirmPassword { get; set; }
public string[] SelectedRoles { get; set; }
public MultiSelectList Roles { get; set; }
}
Controller:
public class AdminController : Controller {
[ChildActionOnly]
public ActionResult _AddUser() {
var model = new RegisterModel {
Roles = new MultiSelectList(Roles.GetAllRoles())
};
return View(model);
}
}
PartialView _AddUser.cshtml:
#model RobotDog.Models.RegisterModel
#using(Html.BeginForm("_AddUser","Admin", FormMethod.Post)) {
#Html.EditorFor(x => x.Email, new { #class = "input-xlarge", #placeholder = "Email"})
#Html.EditorFor(x => x.UserName, new { #class = "input-xlarge", #placeholder = "User Name"})
#Html.EditorFor(x => x.Password, new { #class = "input-xlarge", #placeholder = "Password"})
#Html.ListBoxFor(x => x.SelectedRoles, Model.Roles)
}
Here is the view that is referencing _AddUser.cshtml
Users.cshtml:
#model IEnumerable<RobotDog.Models.UserModel>
<table></table>
<div id="addUser">
#Html.Partial("_AddUser", new ViewDataDictionary())
</div>
The problem with your example is that by calling #Html.Partial you're rendering the partial _AddUser without ever going through the controller _AddUser method. This means that the Model of the partial view has no Model.Roles to access. You can resolve this a couple of different ways.
One possibility is to use #Html.RenderAction or #Html.Action to call the controller method and let the controller method _AddUser populate the values as you've set it up to do.