I'm completely puzzled how this simple concept doesn't seem to work. I have a ViewModel, a Controller and a View...
This is my basic ViewModel
public class CreateProfessionalEducationViewModel
{
public Models.ProfessionalEducation ProfessionalEducation;
public int ConsultantId;
public CreateProfessionalEducationViewModel() {} // parameterless constr
}
These are my 2 controller methods
public ActionResult Create(int id)
{
var viewModel = new CreateProfessionalEducationViewModel
{
ConsultantId = id
};
return View(viewModel);
}
//
// POST: /ProfessionalEducation/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CreateProfessionalEducationViewModel collection)
{
if (ModelState.IsValid)
{
//db.ProfessionalEducations.Add(professionaleducation);
db.SaveChanges();
return RedirectToAction("Index");
}
return View();
}
and my view...
#model Axxes_Virtual_Resume_MVC.ViewModel.ProfessionalEducation.CreateProfessionalEducationViewModel
#using (Html.BeginForm("Create", "ProfessionalEducation")) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>ProfessionalEducation</legend>
<div class="editor-label">
#Html.LabelFor(model => model.ProfessionalEducation.Year)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ProfessionalEducation.Year)
#Html.ValidationMessageFor(model => model.ProfessionalEducation.Year)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.ProfessionalEducation.Title)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ProfessionalEducation.Title)
#Html.ValidationMessageFor(model => model.ProfessionalEducation.Title)
</div>
#Html.HiddenFor(model => model.ConsultantId)
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
For some weird reason my ConsultantId is 0 and ProfessionalEducation is null in my ViewModel when posted back to the HttpPost Create method in my controller.
This should be so basic, what am I forgetting?
Thanks in advance!!
The main problem is that, the loony that is the DefaultModelBinder (what reconstruct viewmodels from an HttpFormCollection) works on properties not variables.
And if I'm not mistaken, I do believe the default model binder don't work so well if you use inner objects like that in your Viewmodel and access their properties directly in the View.
Try something like that :
public class CreateProfessionalEducationViewModel
{
public int ConsultantId { get; set; }
public int ProfessionalEducationYear { get; set; }
public string ProfessionalEducationTitle { get; set; }
}
And for the view of course
#model Axxes_Virtual_Resume_MVC.ViewModel.ProfessionalEducation.CreateProfessionalEducationViewModel
#using (Html.BeginForm("Create", "ProfessionalEducation")) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>ProfessionalEducation</legend>
<div class="editor-label">
#Html.LabelFor(model => model.ProfessionalEducationYear )
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ProfessionalEducationYear )
#Html.ValidationMessageFor(model => model.ProfessionalEducationYear )
</div>
<div class="editor-label">
#Html.LabelFor(model => model.ProfessionalEducationTitle)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ProfessionalEducationTitle)
#Html.ValidationMessageFor(model => model.ProfessionalEducationTitle)
</div>
#Html.HiddenFor(model => model.ConsultantId)
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
As a rule try to avoid referencing Model objects in your ViewModels (but that's just a design rule)
Using properties instead of fields in my ViewModel fixed it for me.
Thanks to anyone for their effort.
The name attribute of the tags must be "ProfessionalEducation.Year" or "ProfessionalEducation.Title" in order that the default binding to work.
So use this:
#Html.TextBox("ProfessionalEducation.Year")
instead of
#Html.EditorFor(model => model.ProfessionalEducation.Year)
and the same for Title property.
You can read more about ASP.NET MVC Model Binding in this useful post:
http://dotnetslackers.com/articles/aspnet/Understanding-ASP-NET-MVC-Model-Binding.aspx
Related
I am new to MVC 4.
I am receiving the following error:
Object reference not set to an instance of an object. Line 47:#Html.ActionLink("Back to List", "Index", new { id = Model.RestaurantId })
Here is my view that is causing the error (please see the last couple of lines):
#model OdeToFood.Models.RestaurantReview
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>New Review</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Rating)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Rating)
#Html.ValidationMessageFor(model => model.Rating)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Body)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Body)
#Html.ValidationMessageFor(model => model.Body)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.ReviewerName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ReviewerName)
#Html.ValidationMessageFor(model => model.ReviewerName)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index", new { id = Model.RestaurantId })
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Here is my controller action that is being called when the Back To List link is clicked:
public ActionResult Index([Bind(Prefix="id")]int restaurantId)
{
var restaurant = _db.Restaurants.Find(restaurantId);
if (restaurant != null) {
return View(restaurant);
}
return HttpNotFound();
}
And here is the RestaurantReview model that is being strongly-typed in the view:
public class RestaurantReview
{
public int Id { get; set; }
public int Rating { get; set; }
public string Body { get; set; }
public string ReviewerName { get; set; }
public int RestaurantId { get; set; }
}
Any help would be muchly appreciated.
Cheers!
In your code you are showing the Create view, and the Index controller. Check if in the Create controller you are passing the Model, or check if it needs model.
I have the following View
#using LearningMVCDAL
#using LearningMVCDAL.Classes
#model Employee
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Employee</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Gender)
</div>
<div class="editor-field">
#Html.DropDownList("Gender", new List<SelectListItem>{
new SelectListItem { Text="Male", Value="Male"},
new SelectListItem { Text="Female", Value="Female"}},"Select Gender")
#Html.ValidationMessageFor(model => model.Gender)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.City)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.City)
#Html.ValidationMessageFor(model => model.City)
</div>
<div class="editor-label">
#Html.Label("Department")
</div>
<div class="editor-field">
#Html.DropDownList("Department", (SelectList)ViewBag.Departments,"Select Department")
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
and here is my code in the controller for the POST request.
[HttpPost]
public ActionResult Create(Employee employeeform)
{
if (ModelState.IsValid)
{
Employee employee = new Employee();
employee.Name = employeeform.Name;
employee.Gender = employeeform.Gender;
employee.City = employeeform.City;
employee.Department.ID = employeeform.Department.ID;
objEmployeeBusinessLayer.AddEmployee(employee);
List<Department> departments = new List<Department>();
departments = objDepartmentBusinessLayer.Department;
SelectList departmentList = new SelectList(departments, "ID", "Name");
ViewBag.Departments = departmentList;
}
return View();
}
With the above code I am able to get Name, Gender and City but not Department as it is an class object. Please help me to bind the properties to Department Property Also.
Try to add a property in your Employee class for the DropDownList as selected departmentId, like below:
public class Employee{
public int selectedDepartmentId {get;set;}
// Other properties
}
#Html.DropDownList("selectedDepartmentId",(SelectList)ViewBag.Departments,"Select Department",String.Empty)
Or you can change the DropDownList in your view like below:
#Html.DropDownList("Department.DepartmentId" , (SelectList)ViewBag.Departments,"Select Department",String.Empty)
Change to:
#Html.DropDownList("DepartmentId", (SelectList)ViewBag.Departments,"Select Department")
public class Employee
{
...
public string DepartmentId { get; set; } // now binding
public Department Department { get; set; }
}
Because you can not bind the selected value from a drop-down list to the Custom class.
[HttpPost]
public ActionResult Create(Employee employeeform)
{
if (ModelState.IsValid)
{
...
Employee employee = new Employee();
employee.DepartmentID = employeeform.DepartmentId;
...
}
return View();
}
The Main Controller
public class TestPartialController : Controller
{
//
// GET: /TestPartial/
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(Main model)
{
HttpContext.Items.Add("MainModel", model);
return View();
}
public ActionResult PartialA()
{
return PartialView();
}
[HttpPost]
public ActionResult PartialA(PartialA a)
{
if (HttpContext.Items["MainModel"] != null)
{
Main model =(Main) HttpContext.Items["MainModel"];
model.PA = a;
}
return PartialView();
}
public ActionResult PartialB()
{
return PartialView();
}
[HttpPost]
public ActionResult PartialB(PartialB b)
{
if (HttpContext.Items["MainModel"] != null)
{
Main model = (Main)HttpContext.Items["MainModel"];
model.PB = b;
}
SubmitDatatoDB();
return PartialView();
}
public void SubmitDatatoDB()
{
if (HttpContext.Items["MainModel"] != null)
{
Main model = (Main)HttpContext.Items["MainModel"];
//SubmitDatatoDB
}
}
}
Models:-
namespace TestingMVC4.Models
{
public class Main
{
public string Main1 { get; set; }
public string Main2 { get; set; }
public virtual PartialA PA { get; set; }
public virtual PartialB PB { get; set; }
}
public class PartialA
{
public string UserName { get; set; }
public string UserID { get; set; }
}
public class PartialB
{
public string UserNameB { get; set; }
public string UserIDB { get; set; }
}
}
View :-
#model TestingMVC4.Models.Main
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Main</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Main1)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Main1)
#Html.ValidationMessageFor(model => model.Main1)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Main2)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Main2)
#Html.ValidationMessageFor(model => model.Main2)
</div>
<div>
#Html.Action("PartialA","TestPartial")
</div>
<div>
#Html.Action("PartialB","TestPartial")
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
#model TestingMVC4.Models.PartialA
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>PartialA</legend>
<div class="editor-label">
#Html.LabelFor(model => model.UserName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.UserName)
#Html.ValidationMessageFor(model => model.UserName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.UserID)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.UserID)
#Html.ValidationMessageFor(model => model.UserID)
</div>
</fieldset>
}
Mine Question is if doing like this, the Index of HTTPPOST of main view will fire first. than follow by Partial view A and Partial View B. In this case I need to store the data in HttpContext.Items and call the submit to Database in the last Partial view B.
What I want is fire the Partial view A and B first, and store the data into Main View's model and call the SubmitDatatoDB function in Main View's POST Action.
Figure out 2 method to achieve mine goal
1) Passing the main model to each partial view, sample of partial view :-
#model TestingMVC4.Models.Main
//#model TestingMVC4.Models.PartialA
<fieldset>
<legend>PartialA</legend>
<div class="editor-label">
#Html.LabelFor(model => model.PA.UserName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.PA.UserName)
#Html.ValidationMessageFor(model => model.PA.UserName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.PA.UserID)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.PA.UserID)
#Html.ValidationMessageFor(model => model.PA.UserID)
</div>
</fieldset>
disadvantages of this method is the partial view is tight to other view.
2) convert all partial view to become templates helpers :- reference from http://lostechies.com/jimmybogard/2011/09/07/building-forms-for-deep-view-model-graphs-in-asp-net-mvc/
this second method is reuse able because it used it own model binding rather than model from parent, for example :-
#model TestingMVC4.Models.ProductEditModel
#{
ViewBag.Title = "Index";
}
<p>
#Html.LabelFor(m => m.Name)
#Html.TextBoxFor(m => m.Name)
</p>
#Html.EditorFor(m => m.Price)
#model TestingMVC4.Models.PriceEditModel
<p>
#Html.LabelFor(m => m.Currency)
#Html.TextBoxFor(m => m.Currency)
</p>
<p>
#Html.LabelFor(m => m.Value)
#Html.TextBoxFor(m => m.Value)
</p>
You need to change your binds to get PartialModelA to populate MainModel and ensure that Partial View A is posting back to and action on the Main Controller
Something like this:
#using (Html.BeginForm("MainAction", "MainController")) {
<fieldset>
<legend>PartialA</legend>
<div class="editor-label">
#Html.Label("UserName", model.UserName)
</div>
<input type="submit" />
</fieldset>
}
Notice the Action and Controller added to the form and the change of LabelFor to use just Label.
I don't know why, the Model is not binding to the Create Action:
ViewModel:
public class AddressContactViewModel
{
// Primary properties
public int Id { get; set; }
public string Contact { get; set; }
// Navigation properties
[Display(ResourceType = typeof(HeelpResources), Name = "AddressContactViewModelNameLabel")]
[Required(ErrorMessageResourceName = "ErrorMsgRequiredField", ErrorMessageResourceType = typeof(HeelpResources))]
public int ContactType_Id { get; set; }
public int Address_Id { get; set; }
// ViewModel dropdownlists
public IEnumerable<SelectListItem> ContactTypeList { get; set; }
}
View:
#model Heelp.ViewModels.AddressContactViewModel
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.HiddenFor(m => m.Address_Id)
<fieldset>
<legend>AddressContactViewModel</legend>
<div id="year">
#Html.DisplayNameFor(m => m.ContactType_Id)
#Html.DropDownListFor(m => m.ContactType_Id, Model.ContactTypeList, HeelpResources.AddressContactViewModelContactTypesListDropDownFirstRecord)
#Html.ValidationMessageFor(m => m.ContactType_Id)
</div>
<div class="editor-label">
#Html.LabelFor(m => m.Contact)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.Contact)
#Html.ValidationMessageFor(m => m.Contact)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
Action Post:
[HttpPost]
[ValidateAntiForgeryToken]
public virtual ActionResult ContactCreate(AddressContactViewModel contact)
{
var contactDto = Mapper.Map<AddressContactViewModel, AddressContactDto>(contact);
_addressContactService.Create(contactDto);
return RedirectToAction(MVC.Address.ContactList());
}
EDIT: Get:
public virtual ActionResult ContactCreate(int addressId)
{
var model = new AddressContactViewModel
{
Address_Id = addressId,
ContactTypeList = ContactTypeDropDownList()
};
return View(model);
}
When I submit the Form, I receive "null" in the contact parameter of the ContactCreate action, why is this happening?
Thanks
Unbelievable, I found the problem, the parameter of the action has the same name of a field in the ViewModel, a change of the parameter name and everything works find:
[HttpPost]
[ValidateAntiForgeryToken]
public virtual ActionResult ContactCreate(AddressContactViewModel addressContact) // HERE I CHANGE FROM contact TO addressContact AND THE PROBLEM GET SOLVED
{
var contactDto = Mapper.Map<AddressContactViewModel, AddressContactDto>(addressContact);
_addressContactService.Create(contactDto);
return RedirectToAction(MVC.Address.ContactList(addressContact.Address_Id));
}
I ran into this same problem on my last app. try encompassing the button and the field you want to submit into the same div:
#model Heelp.ViewModels.AddressContactViewModel
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.HiddenFor(m => m.Address_Id)
<fieldset>
<legend>AddressContactViewModel</legend>
<div id="year">
#Html.DisplayNameFor(m => m.ContactType_Id)
#Html.DropDownListFor(m => m.ContactType_Id, Model.ContactTypeList, HeelpResources.AddressContactViewModelContactTypesListDropDownFirstRecord)
#Html.ValidationMessageFor(m => m.ContactType_Id)
</div>
<div class="editor-label">
#Html.LabelFor(m => m.Contact)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.Contact)
#Html.ValidationMessageFor(m => m.Contact)
<input type="submit" value="Create" />
</div>
</fieldset>
}
I'm pretty sure this is because the form only POSTS inputs when submitting a form, no?
So when I recieve a POST response, the name I edited is correctly received but the ID is always set to 0.
Any workarounds?
public ActionResult Edit(int id)
{
var productBrand = brandRepo.FindProductBrand(id);
ProductBrandModel model = Mapper.Map<ProductBrand, ProductBrandModel>(productBrand);
return View(model);
}
[HttpPost]
public ActionResult Edit(ProductBrandModel model)
{
if (ModelState.IsValid)
{
var productBrand = brandRepo.FindProductBrand(model.ProductBrandId);
productBrand.Name = model.Name;
brandRepo.SaveChanges();
return RedirectToAction("Index", "ProductBrands");
}
return View(model);
}
/* THIS IS THE VIEW*/
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>ProductBrandModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.ProductBrandId)
</div>
<div class="editor-field">
#Html.DisplayFor(model => model.ProductBrandId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
I don't want to let the users edit the ID of the entity, only the name.
Thank you.
What about adding
#Html.HiddenFor(model => model.ProductBrandId)
I think that will do what you want, if I understand your question.