DropDownListFor with ViewBag does not pass values - c#

I want to pass a new booking via a create view where the user can select different locations. I can select the locations in the view, but the location attribute is still set to null, when I click submit, everything else works.
The locations are stored in a List in an infrastructure model, I am guessing, the problem is, that my values are LocationIds and not Location objects, but I don't know how to create an selected list with object values, since all the examples are with ids/names.
These are my models:
public class Infrastructure
{
public int InfrastructureId { get; set; }
[Required]
public List<Location> Locations { get; set; }
}
public class Location
{
public int LocationId { get; set; }
public Address Address { get; set; }
public int CountEmployee { get; set; }
public GPS Coordinates { get; set; }
public List<ChargingStation> ChargingStations { get; set; }
}
this is the Create in the Controller:
public IActionResult Create()
{
Infrastructure infrastructure = _infrastructure.GetRealInfrastructure();
List<Location> locations = infrastructure.Locations;
ViewBag.Locations = new SelectList(locations, "LocationId", "Address.City");
return View();
}
and this is the view:
<div class="form-group">
<label asp-for="end" class="control-label"></label>
<input asp-for="end" class="form-control" id="endTime"/>
<span asp-validation-for="end" class="text-danger"></span>
</div>
</div>
<div class="col-3">
<div class="form-group">
<label asp-for="location" class="control-label"></label>
#Html.DropDownListFor(model => model.location, (IEnumerable<SelectListItem>)ViewBag.Locations, "Choose Location", new { #class = "form-control" })
<span asp-validation-for="location" class="text-danger"></span>
</div>
</div>
The values for endtime for example are saved, but location is still set too null, so I am guessing I am missing some select/submit tag? Or as written before its because I pass an string value (LocationId) instead of an actual location object?

Related

Error getting information from two tables for editing - one-to-one relationship. C#

Friends, I'm a beginner and I'm having a hard time getting information from the database of two tables with a one-to-one relationship
I'm currently getting this error:
An unhandled exception occurred while processing the request.
Error
InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'LojaVirtual.Models.person', but this ViewDataDictionary instance requires a model item of type 'LojaVirtual.Models.ViewModels.Student.PersonAddressViewModel'.
I tried to replace it with PersonAddressViewModel, but I get an error stating that it is not a database table. I created it only as an intermediate class. How can I get the data?
I have the "person" and "addresses" table.
below I have the two models for "person" and "address".
person.cs
namespace LojaVirtual.Models
{
public class person
{
public int id_person { get; set; }
public string name { get; set; }
public string email { get; set; }
public string password { get; set; }
public string confirmpassword { get; set; }
[ForeignKey("id_person")]
[JsonIgnore]
public address Address { get; set; }
}
}
address.cs
namespace LojaVirtual.Models
{
public class address
{
[Key]
public int id_address { get; set; }
public string city { get; set; }
public string state { get; set; }
[ForeignKey("person")]
public int? id_person { get; set; }
public virtual person Person { get; set; }
}
}
I created an intermediate class.
PersonAddress.cs
namespace LojaVirtual.Models.ViewModels.Student
{
public class PersonAddressViewModel
{
[Key]
public int id_person { get; set; }
public string name{ get; set; }
public string email { get; set; }
public string password { get; set; }
public string confirmpassword { get; set; }
public string city { get; set; }
public string state{ get; set; }
}
}
The registration is done normally in the two tables, but I have difficulty in bringing the data filled in with the values ​​of the two tables for editing.
This is my editing method:
[HttpGet]
public IActionResult Update()
{
person person = _clientRepository.getperson(_loginPerson.GetClient().id_person);
return View(person);
}
My View
Update.cshtml
#model LojaVirtual.Models.ViewModels.Student.PersonAddressViewModel
#{
ViewData["Title"] = "Update";
}
<h2>Atualizar</h2>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Atualizar">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="id_person" />
<div class="form-group">
<label asp-for="name" class="control-label"></label>
<input asp-for="name" class="form-control" />
<span asp-validation-for="name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="email" class="control-label"></label>
<input asp-for="email" class="form-control" />
<span asp-validation-for="email" class="text-danger"></span>
.
.
.
.
<div class="form-group">
<label asp-for="city" class="control-label"></label>
<input asp-for="city" class="form-control" />
<span asp-validation-for="city" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="state" class="control-label"></label>
<input asp-for="state" class="form-control" />
<span asp-validation-for="state" class="text-danger"></span>
</div>
EDIT
public person getperson(int id_person)
{
return _db.person.Find(id_person);
}
I appreciate if anyone can help.
Any comment is very welcome!
The Update view expect a PersonAddressViewModel type model, so the Update action needs to return a instance of PersonAddressViewModel to it. As #insane_developer metioned, you can use some mapping library, such as Automapper. It’s okay if you haven’t heard that, you can simply map it one by one.
[HttpGet]
public IActionResult Update()
{
person person = _clientRepository.getperson(_loginPerson.GetClient().id_person);
var personAddressViewModel = new PersonAddressViewModel()
{
id_person = person.id_person;
name= person.name;
email = person.email;
password = person.password;
confirmpassword = person.confirmpassword;
city = (person.Address == null) ? null : person.Address.city;
state= (person.Address == null) ? null : person.Address.state
};
return View(personAddressViewModel);
}

How to select an item from <select> tag and save it as Foreign Key in ASP .NET Core 2.0

I have these two classes:
State and Station :
public class State
{
public int Id { get; set; }
[Required]
public string Code { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Station> Stations { get; set; }
}
and
public class Station
{
public int Id { get; set; }
[Required]
public string Code { get; set; }
[Required]
public string Name { get; set; }
[Required]
public virtual State State { get; set; }
}
The design was "Code First" and I used migration to set up the database and it was like:
The station table saves StateId as foreign Key as it should
My StationsController.cs file has a Create Method in which I use ViewBag for listing the state names like this:
// GET: Stations/Create
public IActionResult Create()
{
ViewBag.StatesList = _context.States.ToList();
return View();
}
And finally my HttpPost Create method
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Code,Name,State")] Station station)
{
if (ModelState.IsValid)
{
_context.Add(station);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(station);
}
My create.cshtml file has <select> tag like this:
<div class="form-group">
<label asp-for="State" class="control-label"></label>
<select asp-for="State" asp-items="#(new SelectList(ViewBag.StatesList, "Id", "Name","State"))" ></select>
<span asp-validation-for="State" class="text-danger"></span>
</div>
The Issue I am facing is that after clicking submit, my ModelState.isValid remains false and the State field is null as shown in this image:
The State Field is null
The Controller has been autogenerated and only two things I have changed: 1st is that I have added the ViewBag.StateList in the Create() method and second is that I have added a State field in Create([Bind("Id,Code,Name,State")].
Any help will be greatly appreciated and sorry for the long post..
regards
Ashutosh
I don't know how many times I have said in SO that you shouldn't send your entity model directly from database to the view, and listen to its postback. You should only generate a model (we call it ViewModel) that represents what the view needs.
Here is what I will do (I wrote everything by hand, not tested).
Create a view model for station creation view:
public class CreateStationViewModel
{
// You shouldn't have Station ID here as it's creation
[Required]
public string Code { get; set; }
[Required]
public string Name { get; set; }
[Display(Name = "State")]
public int SelectedStateId { get; set; }
public IDictionary<int, string> AvailableStates { get; set; }
}
Initialize this view model on the get method:
public IActionResult Create()
{
var vm = new CreateStationViewModel
{
// Construct a list of available states.
// We will use it as the dropdown options.
AvailableStates = _context.States
.ToDictionary(x => x.Id, x => $"{ x.Name }({ x.Code })")
};
return View(vm);
}
Build the form on the view:
#model CreateStationViewModel
#{
// You can define a variable here for the dictionary-to-selectListItem
// conversion.
// Or you can write an extension method on IDictionary for that purpose.
var availableStatesSelectListItems = Model.AvailableStates
.Select(x => new SelectListItem
{
Text = x.Value.ToString(),
Value = x.Key.ToString()
});
}
<form asp-area="" asp-controller="station" asp-action="create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Code" class="control-label"></label>
<input type="text" class="form-control" asp-for="Code" />
<span class="form-text" asp-validation-for="Code"></span>
</div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input type="text" class="form-control" asp-for="Name" />
<span class="form-text" asp-validation-for="Name"></span>
</div>
<div class="form-group">
<label asp-for="SelectedStateId" class="control-label"></label>
<select class="form-control" asp-for="SelectedStateId"
asp-items="availableStatesSelectListItems">
<option value="">- select -</option>
</select>
<span class="form-text" asp-validation-for="SelectedStateId"></span>
</div>
<div class="form-group">
<button type="submit" class="btn btn-success">Create</button>
</div>
</form>
Listen to the view model on postback:
[HttpPost]
public IActionResult Create(CreateStationViewModel model)
{
if (ModelState.IsValid)
{
// You build the entity model from the view model
_context.Stations.Add(new Station
{
Code = model.Code,
Name = model.Name,
StateId = model.SelectedStateId
});
_context.SaveChanges();
return RedirectToAction("index");
}
// Rebuild the available states, or
// better, you can use ajax for the whole form (different topic)
model.AvailableStates = _context.States
.ToDictionary(x => x.Id, x => $"{ x.Name }({ x.Code })");
return View(model);
}
AFAIK you can't pass a State object to be used as the value of the <option> elements inside your <select>. Instead, you have to use a simpler datatype. In practice you need to use StateId as the value.
Add the property StateId to your Station class:
public class Station
{
public int Id { get; set; }
[Required]
public string Code { get; set; }
[Required]
public string Name { get; set; }
public virtual State State { get; set; }
[Required]
public int StateId { get; set; }
}
Also, your SelectList constructor is wrong. The fourth argument ("State" in your example) is supposed to define the default selected item. You don't need to define it here, so leave it out:
<select asp-for="StateId" asp-items="#(new SelectList(ViewBag.StatesList, "Id", "Name"))" ></select>
Now your <select> element should be able to select the correct Id and populate your StateId field. Note that in the [HttpPost] method, the Statewill still be null, but your database should be updated with the correct StateId.

Fill ViewModel collection from multiple fields

I have a form in which user can select which shipping methods they want to support for they product that they are selling, e.g. first class letter, second class letter, parcel, etc. I only give users a collection of possible shipping methods, they declare how much each one will cost, so if someone wants to sell a toaster in a parcel, they will charge less than for a set of dumbbells.
My ProductViewModel:
public int Id { get; set; }
public ICollection<SelectedShippingMethodViewModel> SelectedShippingMethods { get; set; }
And SelectedShippingMethodViewModel:
public class SelectedShippingMethodViewModel
{
public string Name { get; set; }
public decimal Price { get; set; }
}
In my form I create a section with possible options like this:
<h3>Add new product</h3>
<hr />
#using (Html.BeginForm("AddNew", "ProductCreator", null, FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<div class="form-group">
<label class="col-sm-2 control-label">Shipping methods</label>
<div class="col-sm-10">
#foreach (ShippingMethod shippingMethod in ViewBag?.ShippingMethods)
{
<div class="row">
<div class="col-md-3">
// I don't know what should be here
#Html.CheckBox("SelectedShippingMethods", false)
#shippingMethod.Name
</div>
<div class="col-md-2">
// I don't know what should be here
#Html.TextBox("SelectedShippingMethods.Price")
</div>
</div>
}
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">Add product</button>
</div>
</div>
}
I have a database table with every possible shipping method that I acquire like this:
[HttpGet]
public async Task<ActionResult> AddNew()
{
ViewBag.ShippingMethods = await _shippingService.GetAllShippingMethodsAsync();
return View();
}
The problem is if checkbox is selected I have to bind Price and Name for each individual SelectedShippingMethodViewModel and I have no idea how to make it work.
Your view models are incorrect. To allow users to select the shipping methods they want and add a price, that view model needs to be
public class ShippingMethodViewModel
{
public string Name { get; set; }
public decimal Price { get; set; }
public bool IsSelected { get; set; } // your checkbox binds to this property
}
and the ProductViewModel should be
public class ProductViewModel
{
public int Id { get; set; }
....
public List<ShippingMethodViewModel> ShippingMethods { get; set; }
}
Then in the GET method, initialize your ProductViewModel and populate the ShippingMethods based on all available ShippingMethods, for example
var shippingMethods = await _shippingService.GetAllShippingMethodsAsync()
ProductViewModel model = new ProductViewModel
{
....
ShippingMethods = shippingMethods.Select(x => new ShippingMethodViewModel
{
Name = x.Name
}).ToList()
};
return View(model);
and in the view, use a for loop or EditorTemplate for typeof ShippingMethodViewModel to correctly generate your form controls
#for (int i = 0; i < Model.ShippingMethods.Count; i++)
{
#Html.LabelFor(m => m.ShippingMethods[i].IsSelected, Model[0].ShippingMethods.Name)
#Html.CheckBoxFor(m => m.ShippingMethods[i].IsSelected)
#Html.LabelFor(m => m.ShippingMethods[i].Price)
#Html.TextBoxFor(m => m.ShippingMethods[i].Price)
#Html.HiddenFor(m => m.ShippingMethods[i].Name) // if you want this to be submitted as well
}
Then in the POST method
public ActionResult AddNew(ProductViewModel model)
{
// Get the selected Shipping Methods and the associated price
var selectedMethods = model.ShippingMethods.Where(x => x.Selected);

Create drop down list asp.net core

I have two view models:
public class PersonViewModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime Dob { get; set; }
public string Email { get; set; }
public ICollection<PetViewModel> Pets { get; set; }
}
public class PetViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public int PersonId { get; set; }
public PersonViewModel Person { get; set; }
}
When creating/editing a pet I want to have a drop down list of all the people to choose from. This is my pets controller:
\\ Other methods omitted for brevity
[HttpGet]
public IActionResult Create()
{
var people = _personService.AsQueryable().ToList();
ViewBag.PeopleList = new SelectList(people , "Id", "FirstName");
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create([Bind("Id,Name,PersonId")] PetViewModel vm)
{
if (!ModelState.IsValid) return View(vm);
var pet = _petService.Create(vm.Name, vm.PersonId);
return RedirectToAction("Details", pet);
}
I am trying to do it via ViewBag and doing the following in the view as such:
#model UI.ViewModels.PetViewModel
<h2>Create</h2>
<form asp-action="Create">
<div class="form-horizontal">
<h4>Create a Pet</h4>
<div class="form-group">
<label asp-for="PersonId" class="col-md-2 control-label"></label>
<div class="col-md-10">
#Html.DropDownListFor(model => model.PersonId, ViewBag.PeopleList, "--Select--", new { #class = "form-control"})
<span asp-validation-for="PersonId" class="text-danger"></span>
</div>
</div>
</div>
</form>
Upon trying to do so I get the following error: Cannot use a lambda expression as an argument to a dynamically dispatched operation without first casting it to a delegate or expression tree type.
Any help is appreciated. Thanks!
I managed to fix it by doing the following:
<div class="form-group">
<label asp-for="PersonId" class="col-md-2 control-label"></label>
<div class="col-md-10">
#Html.DropDownListFor(model => model.PersonId, (SelectList)ViewBag.PeopleList, "--Select--", new { #class = "form-control"})
<span asp-validation-for="PersonId" class="text-danger"></span>
</div>
</div>
I wasn't casting the ViewBag.PeopleList to a SelectList, changing the second parameter in my Html.DropDownList to (SelectList)ViewBag.PeopleList fixed the issue for me.

Start of with empty View Model MVC

I would like to create a view which is linked to multiple tables. From what I understand I need to create a View Model and link that to the page.
I get a couple of errors using the below
'PaymentViewModel' is a type, which is not valid in the given context.
An expression tree may not contain a dynamic operation (related to first error?)
I am new to MVC - come from ASP....Any help is appreciated
public class PaymentViewModel
{
public string playername { get; set; }
public DateTime dob { get; set; }
public string phone { get; set; }
public string email { get; set; }
public string clubname { get; set; }
public string productname { get; set; }
public decimal amount { get; set; }
public int transactionID { get; set; }
public bool approved { get; set; }
public string subtype { get; set; }
public DateTime subdate { get; set; }
}
Controller
I need to start with a blank view as this is the first step to register a player so the information is not in the database.
Below is the code I use to get a populated View.
public ActionResult Payment()
{
DateTime blank = Convert.ToDateTime("01-01-1900");
var prod = from p in db.Product
join c in db.Club on p.clubname equals c.clubname
where p.clubname == "Club1"
select new PaymentViewModel
{
productname = p.prodname,
clubname = c.clubname,
playername = c.add1,
dob = blank,
phone = c.phone,
email = c.email,
transactionID = 0,
amount = p.amount,
approved = Convert.ToBoolean("1"),
subtype = c.city,
subdate = blank
};
return View(prod);
}
View
#S4C.BAL.PaymentViewModel;
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Player Name</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
<b class="control-label col-md-2" style="">Full Name</b>
<div class="col-md-10">
#Html.EditorFor(model => model.playername, new { htmlAttributes = new { autofocus = "autofocus", #maxlength = "25", #class = "form-control" } })
#Html.ValidationMessageFor(model => model.playername, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<br /><br />
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
</div>
}
<div>#Html.ActionLink("Back to List", "Index")</div>
#section Scripts {#Scripts.Render("~/bundles/jqueryval")}
You cannot do approved = Convert.ToBoolean("1") in your select because the whole projection will happen at the database side and it does not know what Convert.ToBoolean() is. You need to do this in your view model:
public class PaymentViewModel {
// other properties ...
public string approved { get; set; }
public bool IsApproved {get {return this.approved == "1" }}
}
Also change the first line in your view to this:
#model S4C.BAL.PaymentViewModel
Not sure if I am understanding this correctly so please tell me if I'm wrong here.
Sounds like you know how to get a view filled with data from your database and you want to get an empty view without the data filled. To get an empty view request with just return a view without the model.
// Must request with /{Controller}/PaymentEmpty
Public ActionResult PaymentEmpty()
{
return View("Payment", new PaymentViewModel());
}
If you look at the default templates for ASP MVC applications the controller contains actions for Index, Details, Create, Edit and Delete. Thinking of actions in this manner can help with structuring your requests. Maybe place Payment into its own controller named PaymentsController and having the actions from the controller follow the default template.

Categories