I'm trying to query List from database which will be shown in dropdownlistfor in view, and chosen result will be post.
The problem is I'm using two models in view I tried more options
I'm lost with this, I don't know how to post it. Data are shown in listbox without problem. I'm getting this error when I try to post:
"There is no ViewData item of type 'IEnumerable' that
has the key 'Item2.idcity'." System.Exception
{System.InvalidOperationException}
CityModel
public class CityModel
{
public int idcity { get; set; }
[Required]
public string cityName { get; set; }
public IEnumerable<CityModel> citys { get; set; }
}
HospitalModel
public class HospitalShowModels
{
[Required]
public string hospitalName { get; set; }
[Required]
public string cityName { get; set; }
}
HospitalControler.Create()
public ActionResult Create()
{
ViewBag.cityModel = new SelectList(DataAccess.DAL.showCity(), "idcity", "cityName");
var tuple = new Tuple<DataAccess.HospitalShowModels, DataAccess.CityModel>(new DataAccess.HospitalShowModels(), new DataAccess.CityModel());
return View(tuple);
}
[HttpPost]
public ActionResult Create(DataAccess.HospitalShowModels model, DataAccess.CityModel model1)
{
if (ModelState.IsValid) {
DataAccess.DAL.insertHospital(model.hospitalName, model1.cityName);
}else{
ModelState.AddModelError("","Invalid options");
}
return View();
}
View.Create
#model Tuple<ProjektZaja.DataAccess.HospitalShowModels,ProjektZaja.DataAccess.CityModel>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Create Hospital</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Item1.hospitalName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Item1.hospitalName)
#Html.ValidationMessageFor(model => model.Item1.hospitalName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Item1.cityName)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => Model.Item2.idcity,(IEnumerable<SelectListItem>)ViewBag.cityModel)
#Html.ValidationMessageFor(model1 => Model.Item2.idcity)
You can't strongly type a view to multiple models. You need to create a view model which will simply be a composite class containing each of the other two classes, and strongly type the view to that view model.
public class CreateViewModel
{
public CityModel CityModel {get; set;}
public HospitalShowModel HospitalShowModel {get; set;}
}
Then strongly type your view to the view model
# model Full.Path.To.CreateViewModel
And access the models properties appropriately
Model.CityModel.cityName //etcetera...
And bind it in the post method
[HttpPost]
public ActionResult Create(CreateViewModel viewModel)
{
//...
The model type in your view should match the model type in the post action. Therefore it should be a Tuple (though I personally wouldn't use that) or a new model type with 2 properties (one for city, one for hospital)
Related
This question already has answers here:
The ViewData item that has the key 'XXX' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'
(6 answers)
Closed 7 years ago.
I have a model like this;
public int ID{ get; set; }
public string MidName{ get; set; }
public string FirstName{ get; set; }
public string Surname{ get; set; }
This is my controller:
public ActionResult Create(){
ViewBag.Names= new SelectList(db.TbName, "ID", "MidName");
return Viwe();
}
This is my view
#Html.LabelFor(model => model.Names, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("Names", String.Empty)
#Html.ValidationMessageFor(model => model.Names)
</div>
</div>
Now when click create button i get an error saying
`The ViewData item that has the key 'Names' is of type 'System.Int32'
but must be of type 'IEnumerable'.
I am getting this error is it because ID is int, if yes then how do i convert it?
I personally prefer to avoid dynamic stuff like ViewBag /ViewData as much as possible to transfer data between action methods and views. Let's build a strongly typed Viewmodel.
public class CreateCustomerVM
{
public string MidName{ get; set; }
[Required]
public string FirstName{ get; set; }
public string Surname{ get; set; }
public List<SelectListItem> MidNames { set;get;}
public CreateCustomerVM()
{
MidNames=new List<SelectListItem>();
}
}
and in your Create action method
public ActionResult Create()
{
var vm=new CreateCustomerVM();
vm.MidNames=GetMidNames();
return View(vm);
}
private List<SelectListItem> GetMidNames()
{
return new List<SelectListItem> {
new SelectListItem { Value="Mr", Text="Mr"},
new SelectListItem { Value="Ms", Text="Ms"},
};
}
and in your view, which is strongly typed to our viewmodel
#model CreateCustomerVM
#using(Html.Beginform())
{
<div>
Mid name : #Html.DropdownListFor(s=>s.MidName,Model.MidNames)
FirstName : #Html.TextBoxFor(s=>s.FirstName)
<input type="submit" />
</div>
}
Now when your form is posted, You will get the selected item value in the MidName property of the viewmodel.
[HttpPost]
public ActionResult Create(CreateCustomerVM customer)
{
if(ModelState.IsValid)
{
//read customer.FirstName , customer.MidName
// Map it to the properties of your DB entity object
// and save it to DB
}
//Let's reload the MidNames collection again.
customer.MidNames=GetMidNames();
return View(customer);
}
Use this in your view:
#Html.DropDownListFor(x => x.ID, ViewBag.Names, new Dictionary<string, object>{{"class", "control-label col-md-2"}})
That should work.
populate in viewbag again in post action of create:
public ActionResult Create(){
ViewBag.Names= new SelectList(db.TbName, "ID", "MidName");
return View();
}
[HttpPost]
public ActionResult Create(){
ViewBag.Names= new SelectList(db.TbName, "ID", "MidName");
return View();
}
or try with helper like this:
#Html.DropDownListFor(x => x.ID, (SelectList)ViewBag.Names,
new Dictionary<string, object>{{"class", "control-label col-md-2"}})
I wanna Add new multi records to databese,
and I design View have multi form to filed data and insert it.
My View have:
--item 1
#Html.EditorFor(modelitem => item.Description)...
.
.
.
--item 2
#Html.EditorFor(modelitem => item.Description)...
I use
[HttpPost]
public ActionResult Edit(FormCollection c)
{
var desc = c.GetValue("item.Description");
}
I use it do get value, but I wanna separate value of desc.
I use .Split(','); but if desc has value :
"test value, test desc"
So I can't use it.
So Anyone tell me. how to loop item to insert it
You can also use -
Request.Form["Your property Name"]
WORKING CODE:
Model -
public class MyClass
{
public string MyName { get; set; }
}
View -
#using (Html.BeginForm("SubmitData","Post", FormMethod.Post))
{
<div class="form-horizontal">
<h4>MyClass</h4>
<hr />
#Html.ValidationSummary(true)
<div class="form-group">
#Html.LabelFor(model => model.MyName, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.MyName)
#Html.ValidationMessageFor(model => model.MyName)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
On POST, you can access it like this -
[HttpPost]
public ActionResult SubmitData(MyClass c)
{
var name = Request.Form["MyName"];
return View();
}
We will have the input value in the name variable. Check below image.
Also in the above code I showed how to do model binding too. If you use variable 'c', that will also have value from UI. Check below image.
You could prefix the id's of the fields so that you can retrieve the two separate fields;
#Html.TextBoxFor(x => x.SomeProperty, new { #id = "item_1_description" });
And that would allow you to get that specific value from the FormCollection;
string description = c.GetValue("item_1_description");
However, it would be cleaner if you could take advantage of strongly-typing your views. In your position, I would create a ViewModel that represents both of your objects. Since I don't know what your data types are, I'll use the example of a Person, which looks like this;
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
To to add two people in one form, you would create a `ViewModel' that represents what the form would look like - a flattened version of two people;
public class MuliplePeopleViewModel
{
public string Person1Name { get; set; }
public string Person2Name { get; set; }
}
Which would generate a view with two textboxes;
#model MuliplePeopleViewModel
#Html.EditorFor(x => x.Person1Name);
#Html.EditorFor(x => x.Person2Name);
Now in your Controller you can replace your use of FormCollection with our new ViewModel, that will automatically bind the values of the TextBox's to the relevant properties;
[HttpPost]
public ActionResult Edit(MuliplePeopleViewModel viewModel)
{
string person1Name = viewModel.Person1Name;
}
Food for thought: I'm not entirely sure the DefaultModelBinder will bind an unflattened version of the ViewModel, such as;
public class MuliplePeopleViewModel
{
public Person Person1 { get; set; }
public Person Person2 { get; set; }
}
But perhaps you could try it!
EDIT
Following comments from the author, you could also create a ViewModel that contains an ICollection<Person>;
public class MuliplePeopleViewModel
{
public ICollection<Person> People { get; set; }
}
And in your view you can loop through the collection and display fields for each;
#model MuliplePeopleViewModel
#foreach (Person person in Model.People)
{
#Html.EditorFor(x => person.Name);
}
Now, the DefaultModelBinder should now give you a list of people to use in your action. However, if not, you may need to write a custom model binder.
More food for thought: You might be able to utilise EditorTemplates to simplify your view.
Let me know if I can help further.
Matt
Try this:
[HttpPost]
public ActionResult Edit(FormCollection c, string[] description)
{
}
So you will have the values in an array.
I am having trouble getting the value of the selected item from a drop down list.
Apologies for how much code I've posted, but it is very simple code to read.
When I edit a vehicle my Get Edit method returns the vehicle and the dropdown list has the correct VehicleType selected in the view.
BUT
When my [HttpPost] Edit gets the model back, the the VehicleType is null, the other fields, name, description are correctly updated.
I have a class called Vehicle and one called VehicleType
public class Vehicle
{
public string Name { get; set; }
public string Description { get; set;
public virtual VehicleType VehicleType { get; set; }
}
public class VehicleType
{
public int VehicleTypeID { get; set; }
public string VehicleTypeDescription { get; set; }
}
in my controller I have an get and set edit methods
public ActionResult Edit(int id = 0)
{
Vehicle myVehicle = db.Vehicle.Find(id);
if (myVehicle == null)
{
return HttpNotFound();
}
PopulateVehicleTypeList(myVehicle.VehicleType.VehicleTypeID);
return View(myVehicle);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Vehicle myVehicle)
{
if (ModelState.IsValid)
{
db.Entry(myVehicle).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(myVehicle);
}
And my view
//View
#model Vehicle
<h2>Edit</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Vehicle</legend>
#Html.HiddenFor(model => model.VehicleID)
<div class="editor-label">
#Html.LabelFor(model => model.VehicleType.VehicleTypeDescription)
</div>
<div class="editor-field">
#Html.DropDownList("VehicleTypeID", String.Empty)
#Html.ValidationMessageFor(model => model.VehicleType.VehicleTypeID)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
There's a few things you should do.
Firstly, create a view Model which has Vehicles and VehicleTypes
public class VehicleViewModel
{
public IEnumerable<SelectListItem> VehicleTypes { get; set; }
public Vehicle vehicle { get; set; }
}
Secondly, in your view , change to DropDownListFor:
#Html.DropDownListFor(
x => x.Vehicle.VehicleType,
Model.VehicleTypes,
"-- Select a vehicle type --", null)
Your controller will look something like this:
VehicleViewModel viewModel = new VehicleViewModel
{
Vehicle vehicle = <populate via Linq query>
VehicleTypes = VehicleTypes.Select(x => new SelectListItem
{
Value = x.VehicleTypeID,
Text = VehicleTypeDescription
}).ToList(),
vehicle = vehicle
};
Hope that helps.
Overview:
I'm trying to use a ViewModel that has a property that is the model class that I'm trying to edit. I've seen the edit form work using the MVC scaffolding edit forms when editing a model directly, however I am trying to use a ViewModel that contains the model being edited. Everything works except for the saving of a field that's displayed in a DropDownList.
Explanation:
I've attempted to use the scaffolding features of MVC 3 to create an edit form for a model.
In the MVC Music Store tutorial, this is done for the Edit page of an Album, in the StoreManagerController.
Within that page, they have two drop downs for Genre and Artist. Each looks similar to this in the view:-
<div class="editor-label">
#Html.LabelFor(model => model.GenreId, "Genre")
</div>
<div class="editor-field">
#Html.DropDownList("GenreId", String.Empty)
#Html.ValidationMessageFor(model => model.GenreId)
</div>
As far as I can tell, these have their options filled out in the controller using the ViewBag.
ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name", album.GenreId);
ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name", album.ArtistId);
Working with the Model directly
In my code, I managed to do the same with an object that's being saved to the DB through Entity Framework.
Model
public class Season
{
public int SeasonId { get; set; }
public int ClubId { get; set; }
public Club Club { get; set; }
}
Code in Controller
ViewBag.ClubId = new SelectList(clubs, "ClubId", "Name", season.ClubId);
View
<div class="editor-label">
#Html.LabelFor(model => model.ClubId, "Club")
</div>
<div class="editor-field">
#Html.DropDownList("ClubId", String.Empty)
#Html.ValidationMessageFor(model => model.ClubId)
</div>
This works fine.
Working with the View Model
However now I have realised that the page needs more text displayed than what is available in the model I'm editing.
I was hoping to create a special View Model, with the edited model (season) and the extra text I want to display.
ViewModel
public class EditSeasonViewModel
{
public string PlayerName {get; set;}
public string SummaryText {get; set;}
public int PlayerId {get; set;}
public Season season {get; set;}
}
I did this, changed the controller to have the HttpGet and HttpPost methods use the new ViewModel, changed the View to accept the new ViewModel, and changed the all 'EditorFor' methods in the view to use model.season.MyProperty.
Code in Controller
ViewBag.ClubId = new SelectList(clubs, "ClubId", "Name", seasonVM.season.ClubId);
Code in View
<div class="editor-label">
#Html.LabelFor(model => model.season.ClubId, "Club")
</div>
<div class="editor-field">
#Html.DropDownList("ClubId", String.Empty)
#Html.ValidationMessageFor(model => model.season.ClubId)
</div>
When debugging the HttpPost method, all of the values for season properly exist, except for the ClubId value which should come from the DropDropList.
I haven't changed the DropDownList in the View at all from the way it was when we were using the Model directly.
Question:
My question is, what do I need to change to get the ClubId to be saved properly when using this new EditSeasonViewModel?
Also, how does the ViewBag.ClubId in the HttpGet method in the controller match to the DropDownList in the View, and then have it's value passed back to the HttpPost method?
It's your DropDownList declaration that's incorrect. Try this instead:
#Html.DropDownListFor(m => m.season.ClubId, ViewBag.ClubId)
But really, you should put that select list in your model:
public SelectList Clubs { get; set; }
Then populate it in your controller:
Model.Clubs = new SelectList(clubs, "ClubId", "Name", season.ClubId);
Then you can do:
#Html.DropDownListFor(m => m.season.ClubId, Model.Clubs)
Avoid using dynamic stuff like ViewBag/ViewData to transfer data from action methods to views. Switch to the strongly typed approach.
Update your Viewmodel to have 2 more properties. one to hold a collection of available clubs and one to hold the selected club.
public class EditSeasonViewModel
{
public List<SelectListItem> Clubs { set;get;}
public int SelectedClub { set;get;}
public string PlayerName {get; set;}
public string SummaryText {get; set;}
public int PlayerId {get; set;}
public Season season {get; set;}
public EditSeasonViewModel()
{
Clubs=new List<SelectListItem>();
}
}
Now in your GET action, Get the clubs and load it to the Clubs Property.
public ActionResult create(int id)
{
var vm=new EditSeasonViewModel();
vm.Clubs=GetListOfSelectListItemFromClubsFromSomeWhere();
return View(vm);
}
assuming GetListOfSelectListItemFromClubsFromSomeWhere is a method which returns a list of SelectListItem for your Clubs
public List<SelectListItem> GetListOfSelectListItemFromClubsFromSomeWhere()
{
// to do : return List<SelectListItem> with Value and Text(ClubId Id & Name)
}
and in your view, use the Html.DropDownListFor helper method
#model EditSeasonViewModel
#using(Html.Beginform())
{
#Html.DropdownlistFor(x=>x.SelectedClub,
new SelectList(Model.Clubs,"Value","Text"),"select")
<input type="submit" />
}
When this form is being posted, you will get the selected club's id in the SelectedClub property of your posted model.
[HttpPost]
public ACtionResult Create(EditSeasonViewModel model)
{
if(ModelState.IsValid)
{
int clubId= model.SelectedClub;
//to do : save and redirect (PRG pattern)
}
vm.Clubs=GetListOfSelectListItemFromClubsFromSomeWhere();
return View(model);
}
Please be sure that in your edit/creat action controller, the property is correctly binded:
public async Task<ActionResult> Edit([Bind(Include = "SeasonId,ClubId, otherproperties"]){
// code ...
}
I have the following models:
public class Expense
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Required]
public string Name { get; set; }
[Required]
[Column(TypeName = "Money")]
public decimal Limit { get; set; }
[Required]
[ForeignKey("UserProfile")]
public int UserProfileId { get; set; }
[Required]
public virtual UserProfile UserProfile { get; set; }
}
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public virtual ICollection<Expense> Expenses { get; set; }
}
I have a logged in user, and I would like that user to be able to add new Expenses. The create controller actions looks as follows:
//
// GET: /Expenses/Create
public ActionResult Create()
{
return View();
}
//
// POST: /Expenses/Create
[HttpPost]
public ActionResult Create(Expense expense)
{
if (ModelState.IsValid)
{
UserProfile user = db.UserProfiles.Single(u => u.UserName == User.Identity.Name)
user.Expenses.Add(expense);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(expense);
}
With the view:
#model MoneyDrainPlug.Models.Expense
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Expense</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.Limit)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Limit)
#Html.ValidationMessageFor(model => model.Limit)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
When the post happens, ModelState.IsValid is false in the controller. This is because the there is no UserProfileId set in the expense. It does not help if I set the UserProfile and\or UserProfileId before calling ModelState.IsValid.
What would be the correct and secure way of handling this? Surely I should not trust a UserProfileId sent from the client?
In case it matters, I am using asp.net MVC 4.
Thanks in advance
Possible solutions
remove the Required attribute on UserProfileId (DataAnnotation is just related to views : if you don't want something in your views, don't mark it as Required : RequiredAttribute is not a DB constraint)
or
remove the UserProfileId property (you have the UserProfile proeprty, it's probably enough)
or
create a ViewModel without the UserProfileId property
EDIT :
To be clear : you can perfectly use a ViewModel. Just be conscient that Required attributes have nothing to do directly with your DB. If you don't use the Expense class directly in a view, you can remove all your Required attributes in Expense, they will never be used.
for one, You should make sure those pages can only be accessed by authorized users (Add [Authorize] above every method in the controller that requires the user to be logged in)
There are a couple of ways actually of doing this:
One would be a hidden field with the name UserProfileId and value of the logged in users Id
This would be the least safe method of doing it as the Id will be posted from the client to the server therefor it could be manipulated by the client.
I notice that those fields have the required annotation so either you remove the [Required] to make it validate OR you make a new viewmodel with all those attributes without having UserProfileId required (and obviously you dont include the virtual attribute as it isnt needed in this case).
It would turn out to something like this:
public class ExpenseModel
{
[Required]
public string Name { get; set; }
[Required]
public decimal Limit { get; set; }
public int UserProfileId { get; set; }
}
As a final touch, you change the model that the view expects:
#model MoneyDrainPlug.Models.ExpenseModel
And the model the controller expects to recieve after a post:
[Authorize]
[HttpPost]
public ActionResult Create(ExpenseModel expense)
{
if (ModelState.IsValid)
{
UserProfile user = db.UserProfiles.Single(u => u.UserName == User.Identity.Name)
_expense = new Expense() { Name = expense.Name, Limit = expense.Limit, UserProfile = user };
user.Expenses.Add(_expense);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(expense);
}