Dropdown Data Binding Problem in ASP.NET Core 6 MVC - data-binding

I am using SelectListItem in the controller for binding my dropdown data. All the dropdown options are showing perfectly in the dropdown list, but when I try to save, the problem occurs. It's not adding the dropdown options data rather than its adding dropdown data's id.
All the related models, controller and views are shown here:
BuyerSelectList model class:
public class BuyerSelectList
{
[Key]
public int Id { get; set; }
[DisplayName("BUYER")]
public string Buyer { get; set; }
}
ItemSelectList model class:
public class ItemSelectList
{
[Key]
public int Id { get; set; }
[DisplayName("ITEM")]
public string Item { get; set; }
}
BTBNewLien2 model class:
public class BTBNewLien2
{
public int Id { get; set; }
[Required]
[DisplayName("Buyer")]
public int BuyerSelectListId { get; set; }
[ForeignKey("BuyerSelectListId")]
[ValidateNever]
public BuyerSelectList BuyerSelectList { get; set; }
[Required]
[DisplayName("Item")]
public int ItemSelectListId { get; set; }
[ForeignKey("ItemSelectListId")]
[ValidateNever]
public ItemSelectList ItemSelectList { get; set; }
}
BTBNewLien2 controller (here I added all the data binding functionalities for my dropdown):
namespace CommercialCalculatorWeb.Areas.Admin.Controllers
{
public class BTBNewLien2Controller : Controller
{
private readonly IUnitOfWork _unitOfWork;
public BTBNewLien2Controller(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public IActionResult Index()
{
IEnumerable<BTBNewLien2> objBTBNewLienList = _unitOfWork.BTBNewLien2.GetAll();
return View(objBTBNewLienList);
}
public IActionResult Create()
{
BTBNewLien2 btbNewLien2 = new();
IEnumerable<SelectListItem> BuyerSelectList = _unitOfWork.Buyer.GetAll().Select(
c => new SelectListItem
{
Text = c.Buyer,
Value = c.Id.ToString()
});
IEnumerable<SelectListItem> ItemSelectList = _unitOfWork.Item.GetAll().Select(
c => new SelectListItem
{
Text = c.Item,
Value = c.Id.ToString()
});
ViewBag.BuyerSelectList = BuyerSelectList;
ViewBag.ItemSelectList = ItemSelectList;
return View(btbNewLien2);
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(BTBNewLien2 obj)
{
if (ModelState.IsValid)
{
_unitOfWork.BTBNewLien2.Add(obj);
_unitOfWork.Save();
TempData["success"] = "Row Created Successfully!";
return RedirectToAction("Index");
}
return View(obj);
}
}
}
BTBNewLien2 create view:
#model CommercialCalculator.Models.BTBNewLien2
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>BTBNewLien2</h4>
<hr />
<div class="row ml-6">
<div class="col-md-4">
<form asp-action="Create">
<div class="form-group">
<label asp-for="BuyerSelectListId" class="control-label">Buyer</label>
<select asp-for="BuyerSelectListId" asp-items="ViewBag.BuyerSelectList" class="form-control">
<option disabled selected>--Select Buyer--</option>
</select>
</div>
<div class="form-group">
<label asp-for="ItemSelectListId" class="control-label">Item</label>
<select asp-for="ItemSelectListId" asp-items="ViewBag.ItemSelectList" class="form-control">
<option disabled selected>--Select Item--</option>
</select>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
BTBNewLien2 index view:
#model IEnumerable<CommercialCalculator.Models.BTBNewLien2>
#{
ViewData["Title"] = "Index";
}
<table class="table table-bordered table-hover table-sm align-middle m-0" id="header">
<tr class="m-0" style="text-align:center;background-color: #17A2B8">
<th width="20%">
#Html.DisplayNameFor(model => model.BuyerSelectList)
</th>
<th>
#Html.DisplayNameFor(model => model.ItemSelectList)
</th>
</tr>
#foreach (var BTBNewLien2 in Model)
{
<tr class="m-0">
<td>
#Html.DisplayFor(modelItem => BTBNewLien2.BuyerSelectList)
</td>
<td>
#Html.DisplayFor(modelItem => BTBNewLien2.ItemSelectList)
</td>
</tr>
}
</table>

Try this way:
#Html.DropDownList("ItemSelectListId", new SelectList(ViewBag.ItemSelectListId, "Text", "Text"), "-- Select Item --", new { required = true, #class = "form-control" })
In my code, it works fine:
Controller:
[HttpGet]
public IActionResult Create()
{
List<SelectListItem> test = new()
{
new SelectListItem { Value = "1", Text = "test1" },
new SelectListItem { Value = "2", Text = "test2" },
new SelectListItem { Value = "3", Text = "test3" },
new SelectListItem { Value = "4", Text = "test4" }
};
ViewBag.ItemSelectListId = test;
return View();
}
[HttpPost]
public IActionResult Create(Test test)
{
return View();
}
View:
<div class="row ml-6">
<div class="col-md-4">
<form asp-action="Create">
<div class="form-group">
<label asp-for="ItemSelectListId" class="control-label">Buyer</label>
#Html.DropDownList("ItemSelectListId", new SelectList(ViewBag.ItemSelectListId, "Text", "Text"), "-- Select Item --", new { required = true, #class = "form-control" })
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
Test Result:

Related

MVC4 - Partial View List Model binding during Submit to Main view

I have view model which has a list of child models to render the partial view (below).
public class PRDocument
{
[Key]
[Column(Order = 0)]
public int Id { get; set; }
[DisplayName("Vendor Name")]
[Column(Order = 2)]
public int VendorId { get; set; }
public virtual ICollection<PRDocumentQuotation> PRDocumentQuotations { get; set; }
[NotMapped]
public List<PRDocumentQuotation> Quotations { get; set; }
public PRDocument()
{
Quotations = new List<PRDocumentQuotation>();
}
}
public class PRDocumentQuotation
{
[Key]
[Column(Order = 0)]
public int Id { get; set; }
[Required]
[Column(Order = 1)]
public int PRDocumentId { get; set; }
[Display(Name = "Uploaded File")]
[Column(Order = 2)]
public string FileName { get; set; }
}
And in the Partial View rendered like this.
#Html.Partial("_PRDocs", Model.Quotations)
Here is my partial view.
#model IEnumerable<JKLLPOApprovalApp.Models.PRDocumentQuotation>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.FileName)
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
#Html.HiddenFor(modelItem => item.PRDocumentId)
<td>
#Html.DisplayFor(modelItem => item.FileName)
</td>
<td>
#Html.ActionLink("Delete", "Delete", new { id=item.Id })
</td>
</tr>
}
</table>
And the controller actions
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(PRDocument pRDocument)
{
if (ModelState.IsValid)
{
pRDocument.PRDocumentQuotations = pRDocument.Quotations;
db.tbl_PRDocuments.Add(pRDocument);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(pRDocument);
}
View Data Main View
#model JKLLPOApprovalApp.Models.PRDocument
#{
ViewBag.Title = "Create";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>PRDocument</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.VendorId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.VendorId, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.VendorId, "", new { #class = "text-danger" })
</div>
</div>
#Html.Partial("_PRDocs", Model.Quotations)
<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>
#Html.ActionLink("Back to List", "Index")
</div>
What i want is to get the partial view data list (PRDocumentQuotation) into the Create action, bound with main model (PRDocument). How can i do this?
Interesting Question :)
The issue was that Model binding to list should have unique names. So the Generated HTML Should look like below:
<input id="Quotations_0__PRDocumentId" name="Quotations[0].PRDocumentId" type="hidden" value="0">
<input id="Quotations_1__PRDocumentId" name="Quotations[1].PRDocumentId" type="hidden" value="0">
The recommended solution is to use Editor Templates, Check this and this.
But I am giving alternate solution below using for loop to create unique names with index, taken from this post which faced same issue.
In Main View:
Pass The Main Model Instead
#Html.Partial("_PRDocs", Model)
Partial View:
#model JKLLPOApprovalApp.Models.PRDocument
<table class="table">
#if (Model.Quotations != null)
{
for (var i = 0; i < Model.Quotations.Count(); i++)
{
<tr>
<th>
#Html.DisplayNameFor(model => Model.Quotations[i].FileName)
</th>
<th></th>
</tr>
<tr>
#Html.HiddenFor(modelItem => Model.Quotations[i].PRDocumentId)
<td>
#Html.DisplayFor(modelItem => Model.Quotations[i].FileName)
</td>
<td>
#Html.ActionLink("Delete", "Delete", new { id = Model.Quotations[i].Id })
</td>
</tr>
}
}
</table>
Hope helps.

ASP.NET MVC empty KeyValuePair into Controller

I have an Model like KeyValuePair<Book, List<Author>>, after saving changes to controller comes empty model. To Edit(int id) comes Id of Book.
To this method need to get KeyValuePair filled, but it comes empty. Help please.
[HttpPost]
public ActionResult Edit(KeyValuePair<Book, List<Author>> data)
Controller
public class HomeController : Controller
{
static ViewModelAuthorsBooks data = new ViewModelAuthorsBooks();
static Dictionary<Book, List<Author>> tempDict = new Dictionary<Book, List<Author>>();
public ActionResult Index()
{
data = new ViewModelAuthorsBooks();
data.dictionary = new Dictionary<Book, List<Author>>();
List<Author> temp = new List<Author>();
Book book1 = new Book
{
Id = 0,
Name = "Book1",
Genre = "Genre1",
Description = "DescriptionDescription",
Price = 22.42M
};
Author author = new Author
{
Id = 0,
Name = "Name1",
Surname = "Surname1",
SecondName = "Secondname1"
};
temp.Add(author);
Author author2 = new Author
{
Id = 1,
Name = "Name2",
Surname = "Surname2",
SecondName = "Secondname2"
};
temp.Add(author2);
data.dictionary.Add(book1, temp);
temp = new List<Author>();
Book book2 = new Book
{
Id = 1,
Name = "Book2",
Genre = "Genre2",
Description = "DescriptionDescription2",
Price = 44.44M
};
Author author3 = new Author
{
Id = 2,
Name = "Name3",
Surname = "Surname3",
SecondName = "Secondname3"
};
temp.Add(author3);
data.dictionary.Add(book2, temp);
tempDict = data.dictionary;
return View(data.dictionary);
}
public ActionResult Edit(int id)
{
var model = tempDict.FirstOrDefault(x => x.Key.Id == id);
return View(model);
}
[HttpPost]
public ActionResult Edit(KeyValuePair<Book, List<Author>> data)
{
if (ModelState.IsValid)
{
return RedirectToAction("Index");
}
else
{
return View(data);
}
}
}
Classes
public class Book
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Description { get; set; }
public string Genre { get; set; }
}
public class Author
{
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string SecondName { get; set; }
}
public class ViewModelAuthorsBooks
{
public Dictionary<Book,List<Author>> dictionary { get; set; }
}
Views/Index
#using KeyValuePairTest.Models
#model IEnumerable<KeyValuePair<Book,List<Author>>>
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<div class="panel panel-default">
<div class="panel-heading">
Books List
</div>
<div class="panel-body">
<table class="table table-striped table-condensed table-bordered">
<tr>
<th class="text-center">
#Html.DisplayNameFor(x => x.Key.Id)
</th>
<th class="text-center">
#Html.DisplayNameFor(x => x.Key.Name)
</th>
<th class="text-center">
#Html.DisplayNameFor(x => x.Key.Genre)
</th>
<th class="text-right">
#Html.DisplayNameFor(x => x.Key.Price)
</th>
<th class="text-center">
Action
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td class="text-right">
#item.Key.Id
</td>
<td>
#Html.ActionLink(item.Key.Name, "Edit", new { item.Key.Id })
</td>
<td>
#Html.DisplayFor(modelItem => item.Key.Genre)
</td>
<td class="text-right">
#item.Key.Price.ToString("# USD")
</td>
<td class="text-center">
#using (Html.BeginForm("Delete", "Admin"))
{
#Html.Hidden("id", item.Key.Id)
<input type="submit" class="btn btn-default btn-xs" value="Remove" />
}
</td>
</tr>
}
</table>
</div>
<div class="panel-footer">
#Html.ActionLink("Add", "Create", null, new { #class = "btn btn-default" })
</div>
</div>
Views/Edit
#using KeyValuePairTest.Models
#model KeyValuePair<Book, List<Author>>
#{
ViewBag.Title = "Edit";
}
<div class="panel">
<div class="panel-heading">
<h5>Edit Book: #Model.Key.Name</h5>
</div>
#using (Html.BeginForm("Edit", "Home", FormMethod.Post, new { app = #Model }))
{
<div class="panel-body">
#Html.HiddenFor(b => b.Key.Id)
<div class="form-group">
<label>Name:</label>
#Html.TextBoxFor(x => x.Key.Name, new { #class = "form-control" })
<input type="text" name="m" />
<label>Genre:</label>
#Html.TextBoxFor(x => x.Key.Genre, new { #class = "form-control" })
<label>Authors:</label>
#foreach (var author in Model.Value)
{
<label>Name</label>
#Html.TextBoxFor(x => author.Name);
<label>Surname</label>
#Html.TextBoxFor(x => author.Surname);
<label>Second name</label>
#Html.TextBoxFor(x => author.SecondName);
<p></p>
}
<label>Description:</label>
#Html.TextAreaFor(x => x.Key.Description, new { #class = "form-control", rows = 5 })
<label>Price:</label>
#Html.TextBoxFor(x => x.Key.Price, new { #class = "form-control" })
</div>
</div>
<div class="panel-footer">
<input type="submit" value="Save" class="btn btn-primary" />
#Html.ActionLink("Cancel", "Index", null, new { #class = "btn btn-default" })
</div>
}
</div>
Dictionary contains keys and values
In edit we see, that dictionary has values, they shown
Change value of Book name
Null
Thank's all for help, I find a solution
public class ViewModelUpdated
{
public Book book { set; get; }
public List<Author> lstAuthors { set; get; }
}
Just don't do a dictionary that's all :3

asp.Net MVC view model is empty on post

I have a complex view model that I am passing to a create view. When I enter data on the page and post it the model is empty. Both the fields in the sub-object and the "test" field are empty. Why?
public class ContactIncident
{
[Key]
public int Id { get; set; }
[DataType(DataType.MultilineText)]
public string Description { get; set; }
[Display(Name = "Incident Date")]
[DataType(DataType.Date)]
public DateTime? IncidentDateTime { get; set; }
[Display(Name = "Follow Up Date")]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:dd/MM/yyyy}")]
[DataType(DataType.Date)]
public DateTime? FollowUpDate { get; set; }
}
public class IncidentManager
{
public ContactIncident Incident { get; set; }
public string Test { get; set; }
}
public ActionResult Create(int? id)
{
IncidentManager im = new IncidentManager();
ContactIncident ci = new ContactIncident();
ci.IncidentDateTime = DateTime.Now;
ci.FollowUpDate = DateTime.Now.AddDays(14);
im.Incident = ci;
return View(im);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(IncidentManager im)
{
if (ModelState.IsValid)
{
ContactIncident ci = new ContactIncident();
ci.IncidentDateTime = incident.Incident.IncidentDateTime;
ci.Description = im.Incident.Description;
return RedirectToAction("Index");
}
return View(incident);
}
View:
#model MyApp.Web.ViewModels.IncidentManager
#{
ViewBag.Title = "Edit Incident";
}
<h4>#ViewBag.Title</h4>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal well">
#Html.ValidationSummary(true)
#Html.EditorFor(model=>model.Test)
<div class="row">
<div class="col-md-2">
#Html.LabelFor(model => model.Incident.IncidentDateTime)
</div>
<div class="col-md-2">
#Html.DisplayFor(model => model.Incident.IncidentDateTime)
</div>
</div>
<div class="row">
<div class="col-md-2">
#Html.LabelFor(model => model.Incident.Description)
</div>
<div class="col-md-10">
#Html.EditorFor(model => model.Incident.Description, new { htmlAttributes = new { #class = "form-control", rows = "5" }, })
</div>
<div class="col-md-2">
#Html.LabelFor(model => model.Incident.FollowUpDate)
</div>
<div class="col-md-2">
#Html.EditorFor(model => model.Incident.FollowUpDate, new { htmlAttributes = new { #class = "form-control"}, })
</div>
</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>
}
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 ViewModel name.
public ActionResult Create(IncidentManager incidentManager)
As a general practice, always use the name of the model as the parameter name to avoid mapping problems.
UPDATE:
The DefaultModelBinder uses "convention based" mapping.
IncidentManager.Incident = incidentManager.Incident (will map)
IncidentManager.Incident = im.Incident //won't map because 'im' != 'incidentManager'

[Bind(Include = does not work with collection

I have a controller that looks like below:
public async Task<ActionResult> CreateProduct([Bind(Include = "Id,MyCollection")] MyClass myClass)
and this is the view:
<div class="form-group">
#Html.LabelFor(model => model.Id, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Id)
#Html.ValidationMessageFor(model => model.Id)
</div>
</div>
<table>
<tr>
<th>#Html.LabelFor(model => model.MyCollection.First().A)</th>
<th>#Html.LabelFor(model => model.MyCollection.First().B)</th>
<th>#Html.LabelFor(model => model.MyCollection.First().C)</th>
</tr>
#foreach (var item in this.Model.Warnings)
{
<tr>
<td>#Html.ValueFor(model => item.A)</td>
<td>#Html.EditorFor(model => item.B)</td>
<td>#Html.EditorFor(model => item.C)</td>
</tr>
}
</table>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
when I click Save, it posts to the action but only Id is assigned to the object, not myCollection.
What do I need to do to include the collection when posting them to controller?
Update
Model is generated by Entity Framework
public abstract partial class MyBaseClass
{
public Module()
{
this.MyCollection= new HashSet<Warning>();
}
public int Id { get; set; }
public virtual ICollection<Warning> MyCollection { get; set; }
}
public partial class MyClass : MyBaseClass
{
// more properties that aren't used on this controller
}
In order to get this to work you need to understand how ASP.NET MVC handles model binding for a List. Scott Hansleman has a good post explaining this.
Since your question was rather vague in terms of what the actual properties you're dealing with are, I put together a little example which successfully binds to a list:
Controller
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
//Initial test data
var zoo = new Zoo()
{
Id = 1,
Name = "Vilas Zoo",
Animals = new List<Animal>()
{
new Animal() {
Id = 1,
Name = "Red Panda"
},
new Animal() {
Id = 2,
Name = "Sloth"
},
new Animal() {
Id = 3,
Name = "Badger"
},
}
};
return View(zoo);
}
[HttpPost]
public JsonResult Index(Zoo zoo)
{
return Json(zoo);
}
}
Model
public class Zoo
{
public int Id { get; set; }
public string Name { get; set; }
public List<Animal> Animals { get; set; }
}
public class Animal
{
public int Id { get; set; }
public string Name { get; set; }
}
View
<h1>#Model.Id - #Model.Name</h1>
#using (Html.BeginForm("Index", "Home", FormMethod.Post))
{
for (var i=0; i < Model.Animals.Count; i++)
{
#Html.EditorFor(m => Model.Animals[i])
}
<button type="submit">Save it, yo</button>
}
Notice how I'm using a for loop instead of a foreach and the actual index of the loop is being used in the call to EditorFor.

How do I render a group of checkboxes using MVC 4 and View Models (strongly typed)

I'm rather new to the ASP.net MVC world and I'm trying to figure out how to render a group of checkboxes that are strongly typed to a view model. In webforms I would just use the checkboxlist control but im a bit lost with MVC.
I'm building a simple contact form for a wedding planning business and need to pass whatever checkbox values the user selects to my controller.
The form checkboxes need to look like this:
Your help would be greatly appreciated. Thanks!
Here's what I have so far.
CONTROLLER
[HttpPost]
public ActionResult Contact(ContactViewModel ContactVM)
{
if (!ModelState.IsValid)
{
return View(ContactVM);
}
else
{
//Send email logic
return RedirectToAction("ContactConfirm");
}
}
VIEW MODEL
public class ContactViewModel
{
[Required]
public string Name { get; set; }
[Required]
public string Phone { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
[Required]
public string Subject { get; set; }
public IEnumerable<SelectListItem> SubjectValues
{
get
{
return new[]
{
new SelectListItem { Value = "General Inquiry", Text = "General Inquiry" },
new SelectListItem { Value = "Full Wedding Package", Text = "Full Wedding Package" },
new SelectListItem { Value = "Day of Wedding", Text = "Day of Wedding" },
new SelectListItem { Value = "Hourly Consultation", Text = "Hourly Consultation" }
};
}
}
//Not sure what I should do for checkboxes...
}
VIEW
#model NBP.ViewModels.ContactViewModel
#{
ViewBag.Title = "Contact";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm())
{
<div id="ContactContainer">
<div><span class="RequiredField">* </span>Your Name:</div>
<div>
#Html.TextBoxFor(model => model.Name)
</div>
<div><span class="RequiredField">* </span>Your Phone:</div>
<div>
#Html.TextBoxFor(model => model.Phone)
</div>
<div><span class="RequiredField">* </span>Your Email:</div>
<div>
#Html.TextBoxFor(model => model.Email)
</div>
<div>Subject:</div>
<div>
#Html.DropDownListFor(model => model.Subject, Model.SubjectValues)
</div>
<div>Vendor Assistance:</div>
<div>
<!-- CHECKBOXES HERE -->
</div>
<div>
<input id="btnSubmit" type="submit" value="Submit" />
</div>
</div>
}
You could enrich your view model:
public class VendorAssistanceViewModel
{
public string Name { get; set; }
public bool Checked { get; set; }
}
public class ContactViewModel
{
public ContactViewModel()
{
VendorAssistances = new[]
{
new VendorAssistanceViewModel { Name = "DJ/BAND" },
new VendorAssistanceViewModel { Name = "Officiant" },
new VendorAssistanceViewModel { Name = "Florist" },
new VendorAssistanceViewModel { Name = "Photographer" },
new VendorAssistanceViewModel { Name = "Videographer" },
new VendorAssistanceViewModel { Name = "Transportation" },
}.ToList();
}
[Required]
public string Name { get; set; }
[Required]
public string Phone { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
[Required]
public string Subject { get; set; }
public IEnumerable<SelectListItem> SubjectValues
{
get
{
return new[]
{
new SelectListItem { Value = "General Inquiry", Text = "General Inquiry" },
new SelectListItem { Value = "Full Wedding Package", Text = "Full Wedding Package" },
new SelectListItem { Value = "Day of Wedding", Text = "Day of Wedding" },
new SelectListItem { Value = "Hourly Consultation", Text = "Hourly Consultation" }
};
}
}
public IList<VendorAssistanceViewModel> VendorAssistances { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new ContactViewModel());
}
[HttpPost]
public ActionResult Index(ContactViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
//Send email logic
return RedirectToAction("ContactConfirm");
}
}
View:
#using (Html.BeginForm())
{
<div id="ContactContainer">
<div><span class="RequiredField">* </span>Your Name:</div>
<div>
#Html.TextBoxFor(model => model.Name)
</div>
<div><span class="RequiredField">* </span>Your Phone:</div>
<div>
#Html.TextBoxFor(model => model.Phone)
</div>
<div><span class="RequiredField">* </span>Your Email:</div>
<div>
#Html.TextBoxFor(model => model.Email)
</div>
<div>Subject:</div>
<div>
#Html.DropDownListFor(model => model.Subject, Model.SubjectValues)
</div>
<div>Vendor Assistance:</div>
<div>
#for (int i = 0; i < Model.VendorAssistances.Count; i++)
{
<div>
#Html.HiddenFor(x => x.VendorAssistances[i].Name)
#Html.CheckBoxFor(x => x.VendorAssistances[i].Checked)
#Html.LabelFor(x => x.VendorAssistances[i].Checked, Model.VendorAssistances[i].Name)
</div>
}
</div>
<div>
<input id="btnSubmit" type="submit" value="Submit" />
</div>
</div>
}
Use a string array in your view model. You can then use the helper I hacked together. if you don't want to use the helper and the enum then see the actual Html at the bottom. The binder will return a string array with only the selected string values in it. if none are selected it returns a null value for your array. You must account for that, you have been warned :)
View Model:
[Display(Name = "Which Credit Cards are Accepted:")]
public string[] CreditCards { get; set; }
Helper:
public static HtmlString CheckboxGroup<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> propertySelector, Type EnumType)
{
var groupName = GetPropertyName(propertySelector);
var modelValues = ModelMetadata.FromLambdaExpression(propertySelector, htmlHelper.ViewData).Model;//propertySelector.Compile().Invoke(htmlHelper.ViewData.Model);
StringBuilder literal = new StringBuilder();
foreach (var value in Enum.GetValues(EnumType))
{
var svalue = value.ToString();
var builder = new TagBuilder("input");
builder.GenerateId(groupName);
builder.Attributes.Add("type", "checkbox");
builder.Attributes.Add("name", groupName);
builder.Attributes.Add("value", svalue);
var contextValues = HttpContext.Current.Request.Form.GetValues(groupName);
if ((contextValues != null && contextValues.Contains(svalue)) || (modelValues != null && modelValues.ToString().Contains(svalue)))
{
builder.Attributes.Add("checked", null);
}
literal.Append(String.Format("</br>{1} <span>{0}</span>", svalue.Replace('_', ' '),builder.ToString(TagRenderMode.Normal)));
}
return (HtmlString)htmlHelper.Raw(literal.ToString());
}
private static string GetPropertyName<T, TProperty>(Expression<Func<T, TProperty>> propertySelector)
{
var body = propertySelector.Body.ToString();
var firstIndex = body.IndexOf('.') + 1;
return body.Substring(firstIndex);
}
HTML:
#Html.CheckboxGroup(m => m.CreditCards, typeof(VendorCertification.Enums.CreditCardTypes))
Use this if helper extensions scare you:
<input id="CreditCards" name="CreditCards" type="checkbox" value="Visa"
#(Model.CreditCards != null && Model.CreditCards.Contains("Visa") ? "checked=true" : string.Empty)/>
<span>Visa</span><br />
<input id="CreditCards" name="CreditCards" type="checkbox" value="MasterCard"
#(Model.CreditCards != null && Model.CreditCards.Contains("MasterCard") ? "checked=true" : string.Empty)/>
<span>MasterCard</span><br />
For me this works too, and I think this is the simplest (reading the previous answers).
The viewmodel has a string[] for the check boxes.
public string[] Set { get; set; }
The view has this code, and you can repeat the input as many times you need. name, id of the input control has to match the name of the property of the viewmodel.
<div class="col-md-3">
<div class="panel panel-default panel-srcbox">
<div class="panel-heading">
<h3 class="panel-title">Set</h3>
</div>
<div class="panel-body">
<div class="form-group-sm">
<label class="control-label col-xs-3">1</label>
<div class="col-sm-8">
<input type="checkbox" id="Set" name="Set" value="1" />
</div>
<label class="control-label col-xs-3">2</label>
<div class="col-sm-8">
<input type="checkbox" id="Set" name="Set" value="2" />
</div>
</div>
</div>
</div>
</div>
On the post method the Set variable is an array, having the checked value(s).

Resources