Bind Value from Dropdown List (Not Key) From Database - data-binding

I am trying to bind a value from my dropdown list from a database to another database table, not the key. Currently, it is binding the key. The dropdown list displays the values choices. Not sure how to change the value that gets stored in the database table (when the form is submitted) from the key to the value.
Example:
I am using dbo.ResCategory to get values to populate my dropdown list. dbo.ResCategory contains these values:
Key
Description
1
Assisted Living
2
Independent Living
3
Indepdent/Assisted Living
Right now, when I submit the form that contains this dropdown list, even though I select 'Assisted Living' the value being stored in the table is '1' and not 'Assisted Living'.
Here is my Select code from my form Create.cshtml:
<select asp-for="InfoSite.ResType" id="Select1" class="form-select" asp-items="#(new SelectList(Model.DisplayResCatData,"ID", "Description"))"><option value="" selected disabled>---Select Residence Type---</option></select>
And here is my code from Create.cshtml.cs:
public IEnumerable<ResCategory> DisplayResCatData { get; set; }
public async Task OnGet()
{
DisplayResCatData = await _db.ResCategory.ToListAsync();
}
public async Task<IActionResult> OnPost()
{
await _db.InfoSite.AddAsync(InfoSite);
await _db.SaveChangesAsync();
TempData["success"] = "Site Information added successfully.";
return RedirectToPage("Index");
}
}
And from my InfoSite.cs model:
public class InfoSite
{
[Key]
public int ID { get; set; }
[Required]
public string Name { get; set; }
[Display(Name = "Office Type")]
public string? Specialty { get; set; }
[Display(Name = "Res Type")]
public string? ResType { get; set; }
[Display(Name = "Res Sub Type")]
public string? ResSubType { get; set; }
I'm guessing it's just one small change, but haven't found anything on it when Googling for an answer.
If anyone has any ideas, I could sure use the help. This has been posted for awhile and no takers. If it can't be done, please let me know that. Thanks!

New knowledge. I did not know what the values in the SelectList constructor did. Finally, found a site that explained the difference.
SYNTAX:
public SelectList (System.Collections.IEnumerable items, string dataValueField, string dataTextField);
dataValueField = the value that will be binded in your database
dataTextField = the value that will be displayed in the dropdown list.
Therefore:
<select asp-for="InfoSite.ResType" id="Select1" class="form-select" asp-items="#(new SelectList(Model.DisplayResCatData,"ID", "Description"))"><option value="" selected disabled>---Select Residence Type---</option></select>
Should be:
<select asp-for="InfoSite.ResType" id="Select1" class="form-select" asp-items="#(new SelectList(Model.DisplayResCatData,"Description", "Description"))"><option value="" selected disabled>---Select Residence Type---</option></select>
Yes, it was something very simple, but a show stopper if you didn't know it.

Related

Mapping list of checked InputCheckbox elements to list of string on the form model

I have a list of strings that I'd like to list in a form. Users should be able to check the boxes for the strings they want and I'd like to bind the list values back to the form model.
Let's take a look at the code I've figured out so far:
//mypage.razor
//...
<EditForm Model="MyModel">
#foreach(var opt in AvailableOptions)
{
<label for="option-#opt">#opt</label>
<InputCheckbox id="option-#opt" name="option-#opt" type="checkbox" #bind="#(/* bind to what?!? */)"/>
}
</EditForm>
#code
{
private MyPageModel MyModel = new ();
private List<string> AvailableOptions = new List<string>
{
"Apple",
"Banana",
"Cherry"
}
public class MyPageModel
{
public List<string> SelectedValues { get; set; } = new();
}
}
So this is where I started at. Of course, while I can show the value of the label as part of the loop, it's not clear how I'd bind the boolean value of the Checked property to the form model, especially not when I'm wanting to save the opt string value to the form model's list (for checked values) and not a collection of boolean values.
Looking at an answer to a similar question, the thought is that I'd create a "carrier" class with the name and a boolean like:
public class MyCheckedOption
{
public string Name {get; set;}
public bool IsChecked {get; set;}
}
Now, when I bind in the InputCheckbox, I can now set AvailableOptions to a list of MyPageModel and do #bind="opt.IsChecked", but this now binds to MyPageModel and doesn't bind back to my form model.
Now, in my OnValidSubmit, I could harvest the values of these and populate my form model, but that doesn't seem ideal.
Rather, is there some way to map the boolean of the checked properties (as populated by something in my code behind) to a list of string (wherein the value I want to use is another property of the "carrier" class I'm looping through a list of) that I can store directly on the form model?
You can use Linq's Select method to convert your list to something else-- in the following case, a list of MyCheckableOption objects. Later, when you want to do something with the list, you can do the same in reverse to get back to a List<string>
#foreach (var opt in AvailableOptions)
{
<label>#opt.Name </label>
<input type="checkbox" #bind="opt.IsChecked" />
<br/>
}
<hr />
#foreach (var item in AvailableOptions.Where(option => option.IsChecked))
{
<div>#item.Name has been selected. </div>
}
#code
{
private List<MyCheckableOption> AvailableOptions = new List<string>
{
"Apple",
"Banana",
"Cherry"
}
.Select(option => new MyCheckableOption {Name = option }).ToList();
public class MyCheckableOption
{
public string Name { get; set; }
public bool IsChecked { get; set; }
}
}

#Html.DropDownListFor not posting back to controller

I am using #Html.DropDownListFor for the first time. Code is below.
Model:
class Student
{
[Required]
[Display(Name = "Name")]
public string Name { get; set; }
[Required]
[Display(Name = "Roll Number")]
public string RollNumber { get; set; }
[Required]
[Display(Name = "ClassId")]
public int ClassId { get; set; }
}
class Class
{
[Required]
[Display(Name = "ClassId")]
public string RollNumber { get; set; }
[Required]
[Display(Name = "ClassName")]
public string RollNumber { get; set; }
}
Controller:
public ActionResult Create()
{
Student student = new BusinessEntities.Student();
List<Class> classes = GetAllClasses();
ViewBag.ClassId = new SelectList(classes, "ClassId", "ClassName");
return View(student);
}
[HttpPost]
public ActionResult Create(BusinessEntities.Student student)
{
if (ModelState.IsValid)
{
//Integer has 0 by default. But in our case if it contains 0,
//means no class selected by user
if(student.ClassId==0)
{
ModelState.AddModelError("ClassId", "Select Class to Enroll in");
return View(student);
}
}
}
Student Create View:
<form method="post">
Select Class :
#Html.DropDownListFor(Model=>Model.ClassId,ViewBag.ClassId as IEnumerable<SelectListItem>, "ClassId","ClassName")
#Html.ValidationMessageFor(Model => Model.ClassId)
<input type="submit" value="Submit" />
</form>
Error Message:
The ViewData item that has the key 'ClassId' is of type 'System.Collections.Generic.List`1[[BusinessEntities.Class, BusinessEntities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]' but must be of type 'IEnumerable'.
I want ClassId of Student be binded and populated automatically when posted back to Controller. Please help me to get rid of it.
Thanks.
You need to give the SelectList a different name that the property your binding to (say)
ViewBag.ClassList = new SelectList(classes, "ClassId", "ClassName");`
and then
#Html.DropDownListFor(m => m.ClassId, ViewBag.ClassList as IEnumerable<SelectListItem>)
and then ensure if you return the view (for example if ModelState is invalid), that you repopulate the SelectList (as you have done in the GET method). Currently when you return the view, it is null resulting in an error, because if the second parameter is null the fallback is that the helper expects the first parameter to be IEnumerable<SelectListItem> (but its not - its typeof int)
Side notes: Do not use Model => Model.XXX (capital M) and your current use of DropDownistFor() as 2 parameters which make no sense. "ClassId" will add a label option <option value="">ClassId</option> and the last one ,"ClassName" will not do anything.
Edit
In addition, your
if(student.ClassId==0)
{
ModelState.AddModelError("ClassId", "Select Class to Enroll in");
return View(student);
}
is a bit pointless. student.ClassId will never be 0 unless one of the items in your GetAllClasses() has ClassId = 0. You should be using
[Required(ErrorMessage = "Select Class to Enroll in")] // add error message here
public int ClassId { get; set; }
and in the view
#Html.DropDownListFor(m => m.ClassId, ViewBag.ClassList as IEnumerable<SelectListItem>, "--please select--")
which will create the first option with a value of null. If this option were selected, then the DefaultModelBinder will attempt to set the value of ClassId = null which fails (because typeof int cannot be null) and a ModelState error is added and ModelState becomes invalid.
The in the POST method
[HttpPost]
public ActionResult Create(BusinessEntities.Student student)
{
if (!ModelSTate.IsValid)
{
ViewBag.ClassList = // repopulate select list
return View(student);
}
// Save and redirect
}

How to set a hidden field from a Check box selection in jquery and passing it to the controller action method?

I want to set the Hidden field in jquery for selected checkboxes. A user can select multiple checkboxes. I have multiple checkboxes which I want to save as comma separated value in Database. I am doing this with help of jquery and setting it using hidden field but no luck.
<div id="chkbox">
<input type="checkbox" class="Course" name="Courses" id="CoursesScience" value="Science">Science<br>
<input type="checkbox" class="Course" name="Courses" id="CourseMath" value="Math">Math<br />
<input type="checkbox" class="Course" name="Courses" id="CourseIt" value="IT">IT<br />
<input type="checkbox" class="Course" name="Courses" id="CourseCommerce" value="Commerce">Commerce<br />
<input type="checkbox" class="Course" name="Courses" id="CourseEnglish" value="English">English
</div>
<input type="hidden" name="CoursesSelected" id="CoursesSelected" />
In jquery
var Values = [];
$('.Course').change(function () {
if (this.checked) {
Values.push($(this).val());
}
else{
Values.pop(this);
}
});
$('#CoursesSelected').val(Values).appendTo('form');
This is not working as when I pop the value it pop last instance save in the array but not the one which is being deselected.
1) Also please tell me how I can access the hidden field in a controller.
2) Will this hidden field populate the property Courses in my class.
public partial class Register
{
public int Id { get; set; }
public string Name { get; set; }
public string Class { get; set; }
public string Courses { get; set; }
public string Gender { get; set; }
}
You should not need to use jquery or a hidden field. Instead I would use a view model. Create a new class that only contains the information you want to post from your view, e.g:
public class RegisterViewModel
{
public string Name { get; set; }
public string Class { get; set; }
public string[] Courses { get; set; }
public string Gender { get; set; }
}
I don't know what your controller action looks like, but it should now be something like:
[HttpPost]
public ActionResult Register(RegisterViewModel model)
{
//Map the data posted to the form to your Register class
var student = new Register();
student.Name = model.Name;
student.Class = model.Class;
student.Gender = model.Gender;
if (model.Courses != null)
student.Courses = String.Join(",", model.Courses);
//Then do whatever you need to do to save the data to the database
}
When you post your form, the Courses array should contain an element whose value is the id for each checkbox that has been ticked, e.g. ["CourseMath", "CourseCommerce"].

SelectList Default Value don't work

today I'm having trouble with Default Value for a SelectList in ASP.NET.
here is how I do after trying lots of things I found in the Internet:
#{
IEnumerable<SelectListItem> secteurSelectList = from x in Model.secteurList select new SelectListItem {
Selected = (x.Id == this.GetSessionSecteurId()),
Text = x.Secteur,
Value = x.Id.ToString()
};
SelectList selectList = new SelectList(secteurSelectList, "Value", "Text", secteurSelectList.Where(x => x.Selected==true).FirstOrDefault().Value); }
#Html.DropDownListFor(m => m.secteur, selectList)
Here is the description of My entities:
In the ViewModel I use
public List<AuditSecteur> secteurList { get; set; }
Here is the AuditSecteur's object:
public class AuditSecteur
{
public int Id { get; set; }
public string Secteur { get; set; }
}
This is the result:
<select data-val="true" data-val-number="The field Secteur de l'audit must be a number." data-val-required="The Secteur de l'audit field is required." id="secteur" name="secteur">
<option value="1">option1</option>
<option value="2">option2</option>
<option value="3">option3</option>
</select>
SecteurSelectedList has one item at true for the selected:
SelectedList too:
Thanks to help me
The whole purpose of using the strongly typed helpers is to bind your model properties so in the case of
#Html.DropDownListFor(m => m.secteur, selectList)
if the value of secteur does not match the value of one of your options, then it wont be selected (actually because it cant find a match, the first option will be selected because something needs to be selected).
But there are numerous other issues with your code. First you creating IEnumerable<SelectListItem> (setting the Selected property is pointless since its ignored by the helper), then you create another IEnumerable<SelectListItem> (the SelectList) based on the first one in the next line (whats the point?). The value of the options generated are based on the ID property of AuditSecteur but then you bind to to the Secteur property so it would never post back a correct value anyway. Its not really clear how your using this (you have not included you model, controllers or view) but the correct approach would be something like
View model
public class SecteurVM
{
[Display(Name = "Secteur")]
[Required]
public int? SelectedSecteur { get; set; }
public SelectList SecteurList { get; set; }
}
Controller
public ActionResult Create()
{
List<AuditSecteur> secteurList = // get AuditSecteur items from the database
SecteurVM model = new SecteurVM();
model.SecteurList = new SelectList(secteurList, "ID", "Secteur");
model.SelectedSecteur = // set the default value you want selected here
return View(model)
}
[HttpPost]
public ActionResult Create(SecteurVM model)
{
// model.SelectedSecteur contains the ID of the selected item
}
View
#model SecteurVM
....
#Html.LabelFor(m => m.SelectedSecteur)
#Html.DropDownListFor(m => m.SelectedSecteur, Model.SecteurList, "--Please select--")
#Html.ValidationMessageFor(m => m.SelectedSecteur)
If the value of SelectedSecteur matches one of the ID properties of the items in the list, then that is the option that will be selected when you display the view.
you're binding DropDown to secteur property of the Model while Value propety of the dropdown is Id so dropdown won't find the corresponding value while making pre-selection.
here even if you have defined pre-selected item it will reset while rendering, so i would suggest bind you dropdown to Id instead of secteur.
#Html.DropDownListFor(m => m.Id, selectList)

How to code an auction site with ASP.Net/ MVC 5 / EF

I'm relatively new to ASP.NET so please bear with me.
I'm trying to code a straightforward auction site for a charity, using MVC 5 and Entity Framework with Code-First.
I have created an Item model and controller. The Item model holds fields like the title, description, starting bid, current bid, number of bids, high bidder, etc.
public class Item
{
public int ID { get; set; }
public string Code { get; set; }
public string Title { get; set; }
public string Description { get; set; }
[DisplayName("Starting Bid")] public int StartingBid { get; set; }
public int Increment {get; set;}
public int Bids { get; set; }
[DisplayName("Current Bid")] public int CurrentBid { get; set; }
public string HighBidder { get; set; }
public int CurrentOrStartingBid
{ // The price that is displayed next to an item
get
{
return Bids > 0 ? CurrentBid : StartingBid;
}
}
public int NextBid
{ // The minimum amount that is valid for a new bid
get
{
return Bids > 0 ? CurrentBid + Increment : StartingBid;
}
}
}
(What I have tried to do in the code above is to add these properties CurrentOrStartingBid and NextBid which are not intended to be part of the database record, they are just derivative properties rather than columns in the DB. So hopefully having these as read-only properties will do that...)
I am now making a view for the item detail. This shows the item description, and also features form controls for placing a bid, similar to what you would see on eBay. My question is about how to wire up the logic for the Bid button correctly.
I figure that in the view I should use an HTML form with a submit button for the bidding. This does an HTTP post when the button is hit, and allows me to write a method in the Item controller that gets called at that time.
So my Razor code in the view currently looks like this:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div class="form-group">
<div class="input-group">
<span class="input-group-addon">£</span>
<input type="number" class="form-control" value="#Model.NextBid" id="CurrentBid" name="CurrentBid"/>
</div>
<br />
<button type="submit" class="btn btn-primary btn-lg">Place bid</button>
</div>
}
Using this code allows me to write a controller method with this signature:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Bid([Bind(Include = "ID,CurrentBid")] Item item)
This kind of works so far. But note that I am having to pass the amount that has been bid in the CurrentBid field of the item, before it has been validated server-side. This doesn't feel quite right to me. Is there a way of writing the method so it just takes a signature like this?
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Bid(int itemID, int bidAmount)
Maybe there's a way to do that with query strings or something?
Anyway, once inside the method, things again seem a little weird. Protecting from over-posting, the only fields in the item variable that are valid are ID and CurrentBid. So I then do a lookup in the DbContext to find the actual item that corresponds with that ID and update it:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Bid([Bind(Include = "ID,CurrentBid")] Item item)
{
if (ModelState.IsValid)
{
Item i2 = db.Items.Find(item.ID);
if (item.CurrentBid >= i2.NextBid)
{
i2.Bids++;
i2.CurrentBid = item.CurrentBid;
i2.HighBidder = HttpContext.User.Identity.Name;
db.Entry(i2).State = EntityState.Modified;
db.SaveChanges();
}
return View(i2);
}
return RedirectToAction("Auction");
}
This seems to work. But it does not feel right. I think I am missing some important concepts/patterns here. If any experienced MVC folks could sketch how they would wire up the client/server logic for this simple button, that would be great. (You would also be helping a good cause!)
Is there a way of writing the method so it just takes a signature like
this?
[HttpPost] [ValidateAntiForgeryToken]
public ActionResult Bid(int itemID, int bidAmount)
Sure there is a way, lets say you have a simple model like this -
public class Item
{
public int ID { get; set; }
public int NextBib { get; set; }
public int CurrentBid { get; set; }
public string Description { get; set; }
}
Then your Action is creating an Item and sending to View as shown below -
public ActionResult BidForm()
{
Item i = new Item();
i.ID = 100;
i.CurrentBid = 10;
return View(i);
}
And your view is as follows -
#model MVC.Controllers.Item
#{
ViewBag.Title = "BidForm";
}
<h2>BidForm</h2>
#using (Html.BeginForm("Bid", "sample", FormMethod.Post))
{
#Html.AntiForgeryToken()
<input type="hidden" name="itemId" value="#Model.ID" />
<input type="number" class="form-control" value="#Model.NextBib" id="CurrentBid" name="bidAmount" />
<input type="submit" value="Create" />
}
And your NextBid action would be -
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Bid(int itemID, int bidAmount)
{
return View();
}
As you see we have an HiddenField with name = itemID and a input type=number field with name = `bidAmount. Those are mapped to parameters of the action as shown below -
Some recommendations which you can consider -
Instead of using regular HTml.BeginForm(), you can go for Ajax.BeginForm() to give more intuitive user experience. Alternatively you can use JQuery POST operation too.
To prevent OVER posting, you need to create specific ViewModels for specific activities like increasing a bid, displaying a product etc. And you post only required viewModels for required activities, so that you can prevent over posting of form.

Resources