Model with DropDownListFor SelectList not binding on the HttpPost - asp.net-mvc

When I have the following code and I submit the form my post action shows the Contact object as null. If I remove the DropDownListFor from the view the Contact object contains the expected information (FirstName). Why? How do I get the SelectList value to work?
My classes:
public class ContactManager
{
public Contact Contact { get; set; }
public SelectList SalutationList { get; set; }
}
public class Contact
{
public int Id{get;set;}
public string FirstName{get; set;}
public SalutationType SalutationType{get; set;}
}
public class SalutationType
{
public int Id { get; set; }
public string Name { get; set; }
}
My view:
#model ViewModels.ContactManager
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.Contact.Id)
#Html.DropDownListFor(model => model.Contact.SalutationType.Id, Model.SalutationList, "----", new { #class = "form-control" })
#Html.EditorFor(model => model.Contact.FirstName)
<input type="submit" value="Save" />
}
My Controller:
public ActionResult Edit(int? id)
{
Contact contact = db.Contacts.FirstOrDefault(x => x.Id == id);
ContactManager cm = new ContactManager();
cm.Contact = contact;
cm.SalutationList = new SelectList(db.SalutationTypes.Where(a => a.Active == true).ToList(), "Id", "Name");
return View(cm);
}
[HttpPost]
public ActionResult Edit(ContactManger cm)
{
//cm at this point is null
var test = cm.Contact.FirstName;
return View();
}

You will pass the DropdownList using ViewBag:
ViewBag.SalutationList = new SelectList(db.SalutationTypes.Where(a => a.Active == true).ToList(), "Id", "Name");
than u have to call this list inside your edit view:
#Html.DropDownList("SalutationList",String.Empty)

The problem is that the DefaultModelBinder won't be able to map nested models properly if you use a different parameter name. You must use the same parameter name as the model name.
public ActionResult Edit(ContactManager contactManager)
As a general practice, always use the name of the model as the parameter name to avoid mapping problems.
Further Suggestion:
You can just use Contact as the parameter model, no need to use ContactManager if you only need the contact model.
[HttpPost]
public ActionResult Edit(Contact contact)
{
var test = contact.FirstName;
return View();
}

Related

ViewModel property binding to null

I have this ViewModel
public class MyViewModel
{
public Customer Customer{ get; set; }
public Account Account{ get; set; }
public DateTime MyDate{ get; set; }
}
This View
#using (Html.BeginForm("Final", "Home", FormMethod.Post, new { #class = "form" }))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(m => m.Customer)....
#Html.DisplayFor(m => m.Customer.FirstName) //This displays the name ok
And this is my Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Final(MyViewModel viewModel)
{
viewModel.Customer.... //This is null
The problem is, I don´t have to edit the properties, only show them and then save to the database, that´s why I use HiddenFor, but the properties Customer and Account bind to null.
What is the problem? Maybe the HiddenFor?
PS: I have the GET method in which I perform the appropriate return View(viewModel)
This is where I pass the ViewModel
[HttpGet]
public ActionResult Final()
{
var viewModel = new MyViewModel
{
//set the properties, etc
};
return View(viewModel);
}
Customer is a complex object, so you will need each property of Customer as a hidden field.
#Html.HiddenFor(m => m.Customer.FirstName)
#Html.HiddenFor(m => m.Customer.LastName)
Alternatively, you can store the entire MyViewModel in TempData which uses Session State under the hood.

Html.DropDownListFor returns null

There are similar questions and I have tried most of them. Instead of continuing to destroy the remaining code while conjuring demons from the similar past questions, I have decided to ask for help.
When I arrive at the AddMembership action I get a null value instead of the selected item. Below are the details.
View;
#using (Html.BeginForm("AddMembership", "WorkSpace", FormMethod.Post, new { data_ajax = "true", id = "frmAddMembership" }))
{
<div id="newMembershipDiv">
#Html.DropDownListFor(m => m.selectedID, new SelectList(Model.allInfo, "Value","Text",1), "Select!")
<input type="submit" value="Add" name="Command" />
</div>
}
Controller (I just want to see the selectedID or anything appear here.);
public ActionResult AddMembership(SelectListItem selectedID)
{
return View();
}
Model;
public class SomeModel
{
public SelectList allInfo { get; set; }
public SelectListItem selectedID { get; set; }
}
The Monstrosity which initializes the allInfo SelectList
model.allInfo = new SelectList(synHelper.getall().ToArray<Person>().Select(r => new SelectListItem {Text=r.Name, Value=r.prID.ToString() }));
synHelper.getAll() returns a List of the below class;
public class Person
{
public Guid prID { get; set; }
public string Name { get; set; }
}
The only thing that is posted to your action is a selectedID, which is a simple string. If you wander, in the request it looks as simple as:
selectedID=1234
Therefore it should be awaited for as a simple string. Adjust your action parameter:
public ActionResult AddMembership(string selectedID)

How do I get a strongly typed DropDownList to bind to a control Action

I've just started a new MVC project and I'm having trouble getting the post result from a form.
This is my Model Class :
public class User
{
public int id { get; set; }
public string name { get; set; }
}
public class TestModel
{
public List<User> users { get; set; }
public User user { get; set; }
public SelectList listSelection { get; set; }
public TestModel()
{
users = new List<User>()
{
new User() {id = 0, name = "Steven"},
new User() {id = 1, name = "Ian"},
new User() {id = 2, name = "Rich"}
};
listSelection = new SelectList(users, "name", "name");
}
}
This is my view class
#model MvcTestApplicaiton.Models.TestModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm())
{
#Html.DropDownListFor(x => x.user, #Model.listSelection)
<p>
<input type="submit" value="Submit" />
</p>
}
#if (#Model.user != null)
{
<p>#Model.user.name</p>
}
And this is my controller :
public class TestModelController : Controller
{
public TestModel model;
//
// GET: /TestModel/
public ActionResult Index()
{
if(model ==null)
model = new TestModel();
return View(model);
}
[HttpPost]
public ActionResult Test(TestModel test)
{
model.user = test.user;
return RedirectToAction("index", "TestModel");
}
}
The drop down list appears just fine but I can't see to get the ActionResult Test function to run. I thought it would just bind itself with reflection but whatever is wrong, I can't see it.
You have two main errors in your code.
As Brett said you're posting to the Index method, but you don't have Index method that supports POST verb. The easiest way to fix is to change Html.BeginForm() with Html.BeginForm("Test", "TestModel")
You're using Html.DropDownListFor in a wrong way. You could pass only a value types there, because don't forget that the View will generate an HTML page. So instead of User in your Model you should have an UserID and in your View you should have #Html.DropDownListFor(x => x.UserID, #Model.listSelection). And finally in your Action you should query your data source to get the details for the user with this ID.
Hope this helps.
Looks like you're posting back to index. Either use a GET Test() action method, or specify the ACTION parameter in BeginForm().
For example,
#using (Html.BeginForm("Test", "TestModel"))
{
#Html.DropDownListFor(x => x.user, #Model.listSelection)
<p>
<input type="submit" value="Submit" />
</p>
}
Or use a view named Test (rename index.cshtml to test.cshtml):
public ActionResult Test()
{
if(model ==null)
model = new TestModel();
return View(model);
}

Create ActionResult for save throws error saying The model item passed into the dictionary is of type

Although this error is very common in the forum, but i am not able to understand how to fix it in my project. I am new to MVC framework.
View code:-
#model ClassifiedProject.Models.CreateAdvertVM
<div class="editor-label">#Html.LabelFor(model => model.AdvTitle) <i>(E.g. Old Samsung Galaxy Tab 2)</i></div>
<div class="editor-field">
#Html.EditorFor(model => model.AdvTitle)
#Html.ValidationMessageFor(model => model.AdvTitle)
</div>
<div class="editor-label">#Html.LabelFor(model => model.AdvDescription)</div>
<div class="editor-field">
#Html.TextAreaFor(model => model.AdvDescription)
#Html.ValidationMessageFor(model => model.AdvDescription)
</div>
<div class="editor-label">#Html.Label("Advertisement Category")</div>
<div class="editor-label">
#Html.DropDownListFor(model => model.SelectedCategoryId, Model.Categories, new { #class = "ddlcs" })
#Html.ValidationMessageFor(model => model.SelectedCategoryId)
</div>
<p><input type="submit" value="Save" /></p>
Controller code of Save button actionresult:-
[HttpPost]
public ActionResult Create(TR_ADVERTISEMENT tr_advert)
{
if (ModelState.IsValid)
{
tr_advert.CreatedDate = tr_advert.ModifiedDate = DateTime.Now;
if (tr_advert.IsPriceOnRequest)
{
tr_advert.CurrencyID = 0;
tr_advert.Price = 0;
}
db.ADVERTISEMENT.Add(tr_advert);
db.SaveChanges();
return RedirectToAction("Index");
}
Controller code for the form in render stage:-
// GET: /Advert/Create
public ActionResult Create()
{
var model = new CreateAdvertVM();
ViewBag.Message = "Post New Advertisement.";
////Render Category DDL
var cat = from s in db.CategoryDbSet
where s.IsActive == true
orderby s.CatName
select new { s.CatID, s.CatName };
var catListItems = cat.ToList().Select(c => new SelectListItem
{
Text = c.CatName,
Value = c.CatID.ToString()
}).ToList();
catListItems.Insert(0, new SelectListItem { Text = "[--Select the category--]", Value = "" });
model.Categories = catListItems;
return View(model);
ViewModel inherited from EF class:-
[NotMapped]
public class CreateAdvertVM : TR_ADVERTISEMENT
{
[DisplayName("Category")]
[Required]
public int? SelectedCategoryId { get; set; }
public IEnumerable<SelectListItem> Categories { get; set; }
}
EF Model:-
public class TR_ADVERTISEMENT
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int AdvID { get; set; }
[Required]
[DisplayName("Sub Category")]
public int SubCatID { get; set; }
public int CurrencyID { get; set; }
[DisplayName("Price on request")]
public bool IsPriceOnRequest { get; set; }
[DisplayName("Posted Date")]
[DisplayFormat (DataFormatString="{0:dd-MM-yyyy}")]
public Nullable<System.DateTime> CreatedDate { get; set; }
public Nullable<System.DateTime> ModifiedDate { get; set; }
}
On the save button click, i have to save the data into the tr_advertisement table using the EF model.
Please suggest the solution to this problem.
It is the model type you are passing into your Create ActionMethod.
public ActionResult Create(TR_ADVERTISEMENT tr_advert)
should be
public ActionResult Create(CreateAdvertVM tr_advert)
I am assuming that if your model is not valid, you are passing it back further down in your action result (which you are not showing), such as
Return View(tr_advert)
But, you are passing the wrong model type at that point for that view.
EDIT
I would also update your view model so that instead of inheriting from the EF class, simply include the EF class as a property.
public class CreateAdvertVM
{
[DisplayName("Category")]
[Required]
public int? SelectedCategoryId { get; set; }
public IEnumerable<SelectListItem> Categories { get; set; }
public TR_ADVERTISEMENT tr_advert{get;set;}
}
This will make it so that your save code in the Create method can still be used with only minor modifications
[HttpPost]
public ActionResult Create(CreateAdvertVM model)
{
if (ModelState.IsValid)
{
model.tr_advert.CreatedDate = model.tr_advert.ModifiedDate = DateTime.Now;
if (model.tr_advert.IsPriceOnRequest)
{
model.tr_advert.CurrencyID = 0;
model.tr_advert.Price = 0;
}
db.ADVERTISEMENT.Add(model.tr_advert);
db.SaveChanges();
return RedirectToAction("Index");
}

ASP.NET MVC 4 Navigation Virtual Property not populated on Post Action

I have a navigation property (Category) on a Question class for which I am manually creating a DropDownList for in the Create view of Question, and when posting the Create action, the Category navigation property is not populated on the Model, therefore giving me an invalid ModelState.
Here is my model:
public class Category
{
[Key]
[Required]
public int CategoryId { get; set; }
[Required]
public string CategoryName { get; set; }
public virtual List<Question> Questions { get; set; }
}
public class Question
{
[Required]
public int QuestionId { get; set; }
[Required]
public string QuestionText { get; set; }
[Required]
public string Answer { get; set; }
[ForeignKey("CategoryId")]
public virtual Category Category { get; set; }
public int CategoryId { get; set; }
}
Here is my Question controller for both GET and POST actions of Create:
public ActionResult Create(int? id)
{
ViewBag.Categories = Categories.Select(option => new SelectListItem {
Text = option.CategoryName,
Value = option.CategoryId.ToString(),
Selected = (id == option.CategoryId)
});
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Question question)
{
if (ModelState.IsValid)
{
db.Questions.Add(question);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(question);
}
And here is the Create view for Question
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Question</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Category)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.Category.CategoryId, (IEnumerable<SelectListItem>)ViewBag.Categories, "Select a Category")
</div>
<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.Answer)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Answer)
#Html.ValidationMessageFor(model => model.Answer)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
I have tried the following variations of generating the dropdownlist on the view:
#Html.DropDownListFor(model => model.Category.CategoryId, (IEnumerable<SelectListItem>)ViewBag.Categories, "Select a Category")
#Html.DropDownListFor(model => model.Category, (IEnumerable<SelectListItem>)ViewBag.Categories, "Select a Category")
#Html.DropDownList("Category", (IEnumerable<SelectListItem>)ViewBag.Categories, "Select a Category")
#Html.DropDownList("CategoryId", (IEnumerable<SelectListItem>)ViewBag.Categories, "Select a Category")
When I quickwatch the Question object on the POST action, the Category property is null, but the CategoryId field on the property is set to the selected Category on the view.
I know I could easily add code to manually fetch the Category with EF by using the CategoryId value that I get from the view. I also think I could create a custom binder to do this, but I was hoping that this could be done with data annotations.
Am I missing something?
Is there a better way to generate the dropdownlist for the navigation property?
Is there a way to let MVC know how to populate the navigation property without me having to manually do it?
-- EDIT:
If it makes any difference, I do not need the actual navigation property loaded when creating/saving the Question, I just need the CategoryId to be correctly saved to the Database, which isn't happening.
Thanks
Instead of
#Html.DropDownListFor(model => model.Category.CategoryId, (IEnumerable<SelectListItem>)ViewBag.Categories, "Select a Category")
Try
#Html.DropDownListFor(model => model.CategoryId, (IEnumerable<SelectListItem>)ViewBag.Categories, "Select a Category")
Edit:
There is no automatic way to populate Navigation property from the id posted from the form. Because, a database query should be issued to get the data and it should not be transparent. It should be done explicitly. Moreover, doing this operation in a custom binder probably probably is not the best way. There is a good explanation in this link : Inject a dependency into a custom model binder and using InRequestScope using Ninject
I know this question is already answered, but it got me thinking.
So I think I found a way of doing this with some conventions.
First, I made the entities inherit from a base class like this:
public abstract class Entity
{
}
public class Question : Entity
{
[Required]
public int QuestionId { get; set; }
[Required]
public string QuestionText { get; set; }
[Required]
public string Answer { get; set; }
public virtual Category Category { get; set; }
}
public class Category : Entity
{
[Key]
[Required]
public int CategoryId { get; set; }
[Required]
public string CategoryName { get; set; }
public virtual List<Question> Questions { get; set; }
}
So, I also changed the Question model to not have an extra property called CategoryId.
For the form all I did was:
#Html.DropDownList("CategoryId", (IEnumerable<SelectListItem>)ViewBag.Categories, "Select a Category")
So here's the second convention, you'd have to have a property field be named with an Id suffix.
Finally, the CustomModelBinder and CustomModelBinderProvider
public class CustomModelBinderProvider : IModelBinderProvider
{
private readonly IKernel _kernel;
public CustomModelBinderProvider(IKernel kernel)
{
_kernel = kernel;
}
public IModelBinder GetBinder(Type modelType)
{
if (!typeof(Entity).IsAssignableFrom(modelType))
return null;
Type modelBinderType = typeof (CustomModelBinder<>)
.MakeGenericType(modelType);
// I registered the CustomModelBinder using Windsor
return _kernel.Resolve(modelBinderType) as IModelBinder;
}
}
public class CustomModelBinder : DefaultModelBinder where T : Entity
{
private readonly QuestionsContext _db;
public CustomModelBinder(QuestionsContext db)
{
_db = db;
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var model = base.BindModel(controllerContext, bindingContext) as T;
foreach (var property in typeof(T).GetProperties())
{
if (property.PropertyType.BaseType == typeof(Entity))
{
var result = bindingContext.ValueProvider.GetValue(string.Format("{0}Id", property.Name));
if(result != null)
{
var rawIdValue = result.AttemptedValue;
int id;
if (int.TryParse(rawIdValue, out id))
{
if (id != 0)
{
var value = _db.Set(property.PropertyType).Find(id);
property.SetValue(model, value, null);
}
}
}
}
}
return model;
}
}
The CustomModelBinder will look for properties of type Entity and load the data with the passed Id using EF.
Here I am using Windsor to inject the dependencies, but you could use any other IoC container.
And that's it. You have a way to make that binding automagically.

Resources