I can't seem to get the value of a select list to populate the value of the parent object.
I'm using a wrapper object to bind to so that I have access to the values needed for the SelectList as well as the object which needs the value.
I'm willing to bet I'm missing something basic but I can't find it.
I have these models:
public class Status
{
public virtual int Id { get; protected set; }
public virtual string Name { get; set; }
public virtual bool IsClosed { get; set; }
public override string ToString()
{
return Name;
}
}
public class Issue
{
public virtual int Id { get; protected set; }
public virtual string Title { get; set; }
public virtual string Description { get; set; }
public virtual Status Status { get; set; }
public virtual DateTime CreatedOn { get; set; }
public virtual DateTime UpdatedOn { get; set; }
}
And a FormViewModel (from NerdDinner examples) to contain the list of Statuses
public class IssueFormViewModel
{
public Issue Issue { get; set; }
public SelectList Statuses { get; set; }
public IssueFormViewModel(Issue issue, IList<Status> statuses)
{
Issue = issue;
Statuses = new SelectList(statuses, "Id", "Name", statuses[1].Id );
}
public IssueFormViewModel() { }
}
My Create Actions on the Controller look like...
public ActionResult Create()
{
IList<Status> statuses;
Issue issue = new Issue();
// NHibernate stuff getting a List<Status>
return View(new IssueFormViewModel(issue,statuses));
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(IssueFormViewModel issueFormView)
{ // At this point issueFormView.Issue.Status == null
// ...
}
My Issue object is bound partially except the status using these names in the View:
<p>
<label for="Issue.Title">Title:</label>
<%= Html.TextBox("Issue.Title", Model.Issue.Title)%>
<%= Html.ValidationMessage("Title", "*") %>
</p>
<p>
<label for="Statuses">Status:</label>
<!-- also tried "Issue.Status" -->
<%= Html.DropDownList("Statuses", Model.Statuses)%>
<%= Html.ValidationMessage("Status", "*")%>
</p>
I tried the dropdown list on my computer and it works, you should make sure NHibernate is bringing back more than 1 item as your code is trying to set the selected item to be the second item.
Statuses = new SelectList(statuses, "Id", "Name", statuses[1].Id);
Remember that Lists a re zero based indexed.
As expected - it WAS something simple.
I changed the view to look like:
<label for="Status.Id">Status:</label>
<%= Html.DropDownList("Status.Id", Model.Statuses)%>
<%= Html.ValidationMessage("Status.Id", "*")%>
and the controller signature to take an "issue" (can't bind to a selectlist!)
[AcceptVerbs(HttpVerbs.Post)]
//public ActionResult Create(IssueFormViewModel issueFormView)
public ActionResult Create(Issue issueToAdd)
{
And in my Post-Create action I have an Issue with a Status. Albeit the status is invalid (it only contains the Id). So before commiting the Issue to the db, I set the issueToAdd like this:
issueToAdd.Status = (from p in GetAllStatuses()
where p.Id == issueToAdd.Status.Id
select p).First();
Edit: And it turns out I didn't even need to fetch a "proper" Status object. It's bound to Id and that's good enough.
Related
I have an MVC 5 site, I would like to use a strongly typed DropDownListFor with a ViewModel - not with ViewBag.
I have found various articles on this - but they all seem to have huge holes - for example this one doesnt cover editing, and I do not understand how or when "SelectedFlavourId" should be used.
http://odetocode.com/blogs/scott/archive/2013/03/11/dropdownlistfor-with-asp-net-mvc.aspx
I have several requirements.
When editing the story I would like a drop down list of all places to
be displayed - with the associated place (if any) - selected.
I want to use the strongly typed DropDownListFOR (as opposed to
DropDownList).
I would like to use a ViewModel not the ViewBag.
I want to add a "No Associated Place" which will be
selected if PlaceId is null.
I want to add a css class = "form-control" to the DropDownListFor.
The below is as far as I have got after a day of frustration.
A story can be optionally associated with a PlaceId. A blank placeId is also valid. A place can also be associated with more than one story.
Models
public class Place
{
public Guid Id { get; set; }
public string PlaceName { get; set; }
}
public class Story
{
public Guid Id { get; set; }
public Guid? PlaceId { get; set; }
public string StoryName { get; set; }
}
public class StoryPlaceDropdown
{
public Story story { get; set; }
public Guid SelectedStoryId;
public IEnumerable<Place> places;
public IEnumerable<SelectListItem> placeItems
{
get
{
return new SelectList(places, "Id", "PlaceName");
}
}
}
Controller
public ActionResult Edit(Guid Id)
{
var spd = new StoryPlaceDropdown();
spd.places = PlaceRepo.SelectAll();
spd.story = StoryRepo.SelectStory(Id);
spd.selectedStoryID = apd.story.Id;
// Return view
return View(spd);
}
[HttpPost]
public ActionResult Edit(StoryPlaceDropdown spd)
{
// Never gets this far
spd.Places = PlaceRepo.SelectAll();
return View();
}
In View
#Html.DropDownListFor(m => m.SelectedStoryId, Model.PlaceItems)
This populates the DropDownList fine. However it does not select the correct item in edit view. Also when I submit the form I get this error:
Object reference not set to an instance of an object. on this line in the view #Html.DropDownListFor(m => m.SelectedStoryId, Model.PlaceItems)
How can I get this all working? Thanks.
I solved this - I had stupidly forgotten the { get; set; } accessors on the ViewModel, doh!
You can resolved this by these three steps:
Step 1:Create viewmodel
public class StoryPlaceDropdown
{
Required]
[Display(Name = "SelectedStory")]
public int SelectedStoryId { get; set; }
}
Step 2:After this on controller you can write:
public ActionResult Edit(Guid Id)
{
var spd = new StoryPlaceDropdown();
ViewBag.PlaceItems= PlaceRepo.SelectAll();
spd.story = StoryRepo.SelectStory(Id);
spd.selectedStoryID = apd.story.Id;
return View(spd);
}
Step 3: And on view you can write
<div class="col-sm-6">
#Html.DropDownListFor(m => m.SelectedStoryId, new SelectList(#ViewBag.PlaceItems, "Id", "PlaceName"), "---Select---", new { #class = "form-control select-sm" })
</div>
I am having difficulty with my understanding of MVC coming from an aspx world.
I have a Model called CustomerGarment. This has a Order and a Customer along with a few garments.
public class CustomerGarment
{
public int CustomerGarmentId { get; set; }
public virtual Customer Customer { get; set; }
public virtual Order Order { get; set; }
public virtual GarmentJacket GarmentJacket { get; set; }
public virtual GarmentShirt GarmentShirt { get; set; }
}
I have a method for get and post. When the page loads, it creates a new CustomerGarment instance and querys the database to fill the Customer and Order variables. I then use the viewbag to show on the screen a list of GarmentJackets and GarmentShirts
The page then views and using the view I can access the model perfectly. Drop downs load with the viewbag contents and I can access all Customer and Order variables using the model I have passed.
The problem I then face is when I use the HttpPost. The model is not passed back with the information I passed to it.
public ActionResult AddGarments(int orderId, int customerId)
{
CustomerGarment cg = new CustomerGarment();
cg.Order = (from a in db.Orders where a.OrderId == orderId select a).FirstOrDefault();
cg.Customer = (from a in db.Customers where a.CustomerId == customerId select a).FirstOrDefault();
var jackets = from a in db.GarmentJackets orderby a.Type, a.SleeveLengthInches, a.ChestSizeInches select a;
var shirts= from a in db.GarmentKilts orderby a.PrimarySize, a.DropLength select a;
ViewBag.GarmentJacket = new SelectList(jackets, "GarmentJacketId", "GarmentJacketId");
ViewBag.GarmentShirt = new SelectList(shirts, "GarmentShirtId", "GarmentShirtId");
return View(cg);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddGarments(CustomerGarment cg)
{
// Here, I do not have the customer info for example
db.CustomerGarments.Add(cg);
db.SaveChanges();
return RedirectToAction("Index");
return View(cg);
}
This is a bit of my view
#Html.HiddenFor(model => model.Order.OrderId)
#Html.HiddenFor(model => model.Order.CustomerId)
<div class="display-field">
#Html.DisplayFor(model => model.Customer.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.GarmentJacket, "Jacket")
</div>
<div class="editor-field">
#Html.DropDownListFor(m => m.GarmentJacket, (SelectList)ViewBag.GarmentJacket, new {style="width:312px;height:30px;margin-top:2px;margin-bottom:5px"})
</div>
EDIT
My Garment Jacket Model
public class GarmentJacket : Garment
{
public int GarmentJacketId { get; set; }
[Required]
public string Type { get; set; }
[Required]
[Display(Name = "Chest Size")]
public int ChestSizeInches { get; set; }
[Required]
[Display(Name = "Sleeve Length")]
public int SleeveLengthInches { get; set; }
}
public class Garment
{
[DataType(DataType.Date)]
public DateTime? DateRetired { get; set; }
[Required]
public string Barcode { get; set; }
[Required]
public bool Adults { get; set; }
}
In your CustomerGarment class, you should have:
public class CustomerGarment
{
public int CustomerGarmentId { get; set; }
public int CustomerId { get; set; }
public int OrderId { get; set; }
public int GarmentJacketId { get; set; }
public int GarmentShirtId { get; set; }
public virtual Customer Customer { get; set; }
public virtual Order Order { get; set; }
public virtual GarmentJacket GarmentJacket { get; set; }
public virtual GarmentShirt GarmentShirt { get; set; }
}
And, then, in your View, your DropDownList will look like:
#Html.DropDownListFor(m => m.GarmentJacketId, (SelectList)ViewBag.GarmentJacket, new {style="width:312px;height:30px;margin-top:2px;margin-bottom:5px"})
Your DropDownList only posts one value, which is the GarmentJacketId. You can't bind that Id to the whole GarmentJacket class.
By the way, you also need to replace your hidden inputs with these:
#Html.HiddenFor(model => model.OrderId)
#Html.HiddenFor(model => model.CustomerId)
I think I know your problem. As you suggested in you comment above you need to post everything you want retained in the view. This is one of the differences beteween webforms and MVC, webforms has viewstate that could contain information that you don't explicitly add to the view and post back, giving the impression of state. In MVC you have to add it to the view.
On the other hand you don't need to pass in more information than you need either. You pass inn the customerId as a hidden field. On post method you get the customer from the db using the Id, then you add the order to the customer.
I have some questions about your design, but given that a customer holds a collection of Orders, you could do something like this:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddGarments(CustomerGarment cg)
{
// Get the customer from the database
var customer = db.Customers.Find(c=>c.id==cb.Customer.Id)
var order = new Order();
//Create your order here using information from CustomerGarment model
//If the model already holds a valid Order object then just add it.
//i.e. you could get a Garment object from the DB using the GarmentId from
//the ViewModel if you really need more than just the Id to create the order
customer.Orders.Add(order);
db.SaveChanges();
return RedirectToAction("Index");
}
Hope someone can help - this has been bugging me for around 2 hours - its probably something simple :)
Kendo UI Grid sends a request to my controller
http://localhost:1418/user/update?UserID=1&UserName=Admin&RoleName=Admin&Email=c.j.hannon%40gmail.com&Active=true&Company%5BCompanyID%5D=1&Company%5BCompanyName%5D=asd
However, the controller class 'Company' isnt bound by the binder? Can any one help my view model and controller action signature are below:
[HttpGet]
public JsonResult Update(UserViewModel model)
{
svcUser.UpdateUser(new UpdateUserRequest() {
UserID=model.UserID,
RoleID = model.RoleName,
Email = model.Email,
Active = model.Active.GetValueOrDefault(false),
UserName = model.UserName
});
return Json("", JsonRequestBehavior.AllowGet);
}
public class UserViewModel
{
public int UserID { get; set; }
public string UserName { get; set; }
public string RoleName { get; set; }
public string Email { get; set; }
public bool? Active { get; set; }
public CompanyViewModel Company { get; set; }
}
Cheers
Craig
A few things. Your immediate problem is that Company is mapped to a complex object not a primitive type. Kendo Grid just does not do this (as of this writing). Just guessing, but you probably want to setup a foreign key binding on the Grid and just pass back the Id of the company from a listbox. This is not as bad as you think and it will immediatly fix your problem and look nice too.
Maybe personal taste but seems to be a convention. Use the suffix ViewModel for the model that is bound to your View and just the suffix Model for your business objects. So a Kendo Grid is always populated with a Model.
Ex.:
public class UserModel
{
public int UserID { get; set; }
public string UserName { get; set; }
public string RoleName { get; set; }
public string Email { get; set; }
public bool? Active { get; set; }
public int CompanyID { get; set; }
}
public class CompanyModel
{
public int ID { get; set; }
public string Name { get; set; }
}
public class UserViewModel
{
public UserModel UserModel { get; set; }
public IList<CompanyModel> Companies { get; set; }
}
public ActionResult UserEdit(string id)
{
var model = new UserViewModel();
model.UserModel = load...
model.Companies = load list...
return View(model);
}
#model UserViewModel
...
column.ForeignKey(fk => fk.CompanyId, Model.Companies, "ID", "Name")
(Razor Notation)
BUT! This is just an example, you are better off Ajax loading the Grid with the IList becuase I assume you have many Users in the Grid at once, though you could server bind off the ViewModel with a List too. But the list of Companies is probably the same every time, so map it to the View just liek this rather than Ajax load it every time you do a row edit. (not always true)
Looks like others have had this problem but I can't seem to find a solution.
I have 2 Models: Person & BillingInfo:
public class Person
{
public string Name { get; set;}
public BillingInfo BillingInfo { get; set; }
}
public class BillingInfo
{
public string BillingName { get; set; }
}
And I'm trying to bind this straight into my Action using the DefaultModelBinder.
public ActionResult DoStuff(Person model)
{
// do stuff
}
However, while the Person.Name property is set, the BillingInfo is always null.
My post looks like this:
"Name=statichippo&BillingInfo.BillingName=statichippo"
Why is BillingInfo always null?
I had this problem, and the answer was staring me in the face for a few hours. I'm including it here because I was searching for nested models not binding and came to this answer.
Make sure that your nested model's properties, like any of your models that you want the binding to work for, have the correct accessors.
// Will not bind!
public string Address1;
public string Address2;
public string Address3;
public string Address4;
public string Address5;
// Will bind
public string Address1 { get; set; }
public string Address2 { get; set; }
public string Address3 { get; set; }
public string Address4 { get; set; }
public string Address5 { get; set; }
Status no repro. Your problem is elsewhere and unable to determine where from what you've given as information. The default model binder works perfectly fine with nested classes. I've used it an infinity of times and it has always worked.
Model:
public class Person
{
public string Name { get; set; }
public BillingInfo BillingInfo { get; set; }
}
public class BillingInfo
{
public string BillingName { get; set; }
}
Controller:
[HandleError]
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new Person
{
Name = "statichippo",
BillingInfo = new BillingInfo
{
BillingName = "statichippo"
}
};
return View(model);
}
[HttpPost]
public ActionResult Index(Person model)
{
return View(model);
}
}
View:
<% using (Html.BeginForm()) { %>
Name: <%: Html.EditorFor(x => x.Name) %>
<br/>
BillingName: <%: Html.EditorFor(x => x.BillingInfo.BillingName) %>
<input type="submit" value="OK" />
<% } %>
Posted values: Name=statichippo&BillingInfo.BillingName=statichippo is perfectly bound in the POST action. Same works with GET as well.
One possible case when this might not work is the following:
public ActionResult Index(Person billingInfo)
{
return View();
}
Notice how the action parameter is called billingInfo, same name as the BillingInfo property. Make sure this is not your case.
I had the same issue, the previous developer on the project had the property registered with a private setter as he wasn't using this viewmodel in a postback. Something like this:
public MyViewModel NestedModel { get; private set; }
changed to this:
public MyViewModel NestedModel { get; set; }
This is what worked for me.
I changed this:
[HttpPost]
public ActionResult Index(Person model)
{
return View(model);
}
To:
[HttpPost]
public ActionResult Index(FormCollection fc)
{
Person model = new Person();
model.BillingInfo.BillingName = fc["BillingInfo.BillingName"]
/// Add more lines to complete all properties of model as necessary.
return View(model);
}
public class MyNestedClass
{
public string Email { get; set; }
}
public class LoginModel
{
//If you name the property as 'xmodel'(other than 'model' then it is working ok.
public MyNestedClass xmodel {get; set;}
//If you name the property as 'model', then is not working
public MyNestedClass model {get; set;}
public string Test { get; set; }
}
I have had the similiar problem. I spent many hours and find the problem accidentally that I should not use 'model' for the property name
#Html.TextBoxFor(m => m.xmodel.Email) //This is OK
#Html.TextBoxFor(m => m.model.Email) //This is not OK
I have the folowing code in my view, however, I can see that I don`t have the values in the controller. What is wrong?
In the view I have,
<%
using (Html.BeginForm())
{%>
<%=Html.TextBox("Addresses[0].Line1") %>
<%=Html.TextBox("Addresses[0].Line2")%>
<%=Html.TextBox("Addresses[1].Line1")%>
<%=Html.TextBox("Addresses[1].Line2")%>
<input type="submit" name="submitForm" value="Save products" />
<%
}
%>
My classes are as follows:
public class Customer
{
public string FirstName { get; set; }
public string Lastname { get; set; }
public List<Address> Addresses { get; set; }
public Customer()
{
Addresses = new List<Address>();
}
}
public class Address
{
public int Line1 { get; set; }
public int Line2 { get; set; }
}
My controller as follows:
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(Customer customer)
{
return View();
}
The parameter for your ActionResult is named customer, so the default model binder will be looking for that name in the form by default. I believe if you modify your code to the following it should pick it up:
<%=Html.TextBox("customer.Addresses[0].Line1") %>
<%=Html.TextBox("customer.Addresses[0].Line2")%>
<%=Html.TextBox("customer.Addresses[1].Line1")%>
<%=Html.TextBox("customer.Addresses[1].Line2")%>
Check to ensure your View is bound to the Customer model.
Also, when viewing the web page containing the form, view the source generated by the View to see if the fields are being properly named.
Finally, if none of the above helps, change the parameter in your Index action like so:
public ActionResult Index(FormCollection form)
then you can use the debugger to inspect the FormCollection object that is passed in to see exactly what the View is sending you.