Morning all.
I can see this has been discussed elsewhere but was wondering if anything had change or things made simpler in MVC 4 for simpletons like me?!
Scenario
I have the following,edited, model:
public class CorporateDetails
{
public Guid? Id { get; set; }
[Key]
public int CorporateDetailId { get; set; }
public int? EmsId { get; set; }
public string EmsName { get; set; }
public virtual EmsType EmsType { get; set; }
}
public class EmsType
{
[Key]
public int? EmsId { get; set; }
public string EmsName { get; set; }
public virtual ICollection<EmsType> EmsTypes { get; set; }
}
With the following standard create view:
<fieldset>
<legend>CorporateDetails</legend>
<div class="editor-label">
#Html.LabelFor(model => model.EmsId, "EmsType")
</div>
<div class="editor-field">
#Html.DropDownList("EmsId", String.Empty)
#Html.ValidationMessageFor(model => model.EmsId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.EmsName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.EmsName)
#Html.ValidationMessageFor(model => model.EmsName)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
This gives me, out of the box, a beautiful drop down list a la Scott Gu's blog
Now my real question is this - how can I effectively convert this drop down box to what will effectively be a multi select,checkbox list?
Again, apologies for going over trodden ground but I was just testing the water to see if any updates have occurred.
Please note, first MVC project so go gently, I'm feeling very thick again :'(
Right ok, I have got it sorted - hurrah! As you can see from the comments, there were a few issues that came up but please find below the complete solution which DOES work :D
Model
public class CorporateDetails
{
public Guid? Id { get; set; }
[Key]
public int CorporateDetailId { get; set; }
public int[] EmsId { get; set; }
}
public class EmsType
{
[Key]
public int EmsId { get; set; }
public string EmsName { get; set; }
public virtual ICollection<EmsType> EmsTypes { get; set; }
}
Controller
public ActionResult Create()
{
CorporateDetails corporatedetails = new CorporateDetails();
ViewBag.EmsId = new MultiSelectList(db.EmsTypes, "EmsId", "EmsName");
return View(corporatedetails);
}
Extension (placed in a folder in the root of the project)
public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty[]>> expression, MultiSelectList multiSelectList, object htmlAttributes = null)
{
//Derive property name for checkbox name
MemberExpression body = expression.Body as MemberExpression;
string propertyName = body.Member.Name;
//Get currently select values from the ViewData model
TProperty[] list = expression.Compile().Invoke(htmlHelper.ViewData.Model);
//Convert selected value list to a List<string> for easy manipulation
List<string> selectedValues = new List<string>();
if (list != null)
{
selectedValues = new List<TProperty>(list).ConvertAll<string>(delegate(TProperty i) { return i.ToString(); });
}
//Create div
TagBuilder divTag = new TagBuilder("div");
divTag.MergeAttributes(new RouteValueDictionary(htmlAttributes), true);
//Add checkboxes
foreach (SelectListItem item in multiSelectList)
{
divTag.InnerHtml += String.Format("<div><input type=\"checkbox\" name=\"{0}\" id=\"{0}_{1}\" " +
"value=\"{1}\" {2} /><label for=\"{0}_{1}\">{3}</label></div>",
propertyName,
item.Value,
selectedValues.Contains(item.Value) ? "checked=\"checked\"" : "",
item.Text);
}
return MvcHtmlString.Create(divTag.ToString());
}
Extension registered in web config of the Views
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Optimization"/>
<add namespace="System.Web.Routing" />
<add namespace="MyProject.Extensions" />
</namespaces>
</pages>
View
#model Valpak.Websites.HealthChecker.Models.CorporateDetails
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>CorporateDetails</legend>
<div class="editor-label">
#Html.CheckBoxListFor(model => model.EmsId, (MultiSelectList) ViewBag.EmsId)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Which gives me a lovely list of check boxes. Hurrah!
Thanks Darin for your help, I've marked this as the answer but +50 for your time and effort.
Nice solution -
Just for others reference - I, like you, had run into a need for a checkboxlist - I have been using this here:
http://www.codeproject.com/Articles/292050/CheckBoxList-For-a-missing-MVC-extension
It works great... very well written - hope this can help someone.
Loren
No changes occurred in ASP.NET MVC 4 RC in this aspect and are unlikely to occur when it hits RTM.
But you could still implement a custom helper to achieve that. And you could even improve this helper so that it takes a lambda expression as first argument instead of a string in order to have strongly typed version.
And if you are not using enums here's another example.
If you pass selected value to MultiSelected (parameter #4)
ViewBag.VfonctionIds = new MultiSelectList(db.tbIntervenantFonctionTypes, "intervenantFonctionType_id", "Nom", fonctionSelected);
Change the Helper to
public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty[]>> expression, MultiSelectList multiSelectList, object htmlAttributes = null)
{
//Derive property name for checkbox name
MemberExpression body = expression.Body as MemberExpression;
string propertyName = body.Member.Name;
//Create div
TagBuilder divTag = new TagBuilder("div");
divTag.MergeAttributes(new RouteValueDictionary(htmlAttributes), true);
//Add checkboxes
foreach (SelectListItem item in multiSelectList)
{
divTag.InnerHtml += String.Format("<div><input type=\"checkbox\" name=\"{0}\" id=\"{0}_{1}\" " +
"value=\"{1}\" {2} /><label for=\"{0}_{1}\">{3}</label></div>",
propertyName,
item.Value,
(item.Selected) ? "checked=\"checked\"" : "",
item.Text);
}
return MvcHtmlString.Create(divTag.ToString());
}
Related
I've got a controller to retrieve and return values for my drop down, and a second, that when an option from the dropdown is selected, uses the values (Title and ID) in an API Request.
Controllers
public ActionResult GetEpics()
{
//Code to retrieve list
Epics = new GetEpicsViewModel();
Epics.Epics = epicsList;
return View(Epics);
}
[HttpPost]
public ActionResult Build(GetEpicsViewModel epic)
{
GetEpicsViewModel epicTest = epic;
//API Request
return View();
}
This is displayed in my drop down list as below:
View
#using (Html.BeginForm("Build", "GetEpics", FormMethod.Post))
{
<label for="input_OutputType"> Process: #Html.DropDownListFor(model => model.Id, new SelectList(Model.Epics, "Id", "Title")) </label>
<input type="submit" value="Submit" />
}
This works fine, but how would I then go about passing both the Title and ID to my controller?
I can pass the ID through fine, but cant figure out how to pass the Title as well.
Screenshot
Models
public class DevOpsEpic
{
public string Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
}
and
public class GetEpicsViewModel
{
public string Id { get; set; }
public string Title { get; set; }
public List<DevOpsEpic> Epics { get; set; }
}
Realise this is probably a really simple answer, but just cant figure it out!
You can use jQuery for that, so when your dropdown is changed, set title value in hidden file.
#using (Html.BeginForm("Build", "GetEpics", FormMethod.Post))
{
<label for="input_OutputType"> Process: #Html.DropDownListFor(model => model.Id, new SelectList(Model.Epics, "Id", "Title"),new { name = "Id" }) </label>
<input type="hidden" id="Title" name="Title" />
<input type="submit" value="Submit" />
}
$('#dropdownId').change(function(){
$('#Title').val($('#dropdownId option:selected').text());
});
(warning newbie)
I am using codefirst entity framework (with MVC3) and trying to display a list of steps and its associated questions. Not sure why my syntax is throwing an exception "the ObjectContext instance has been disposed and can no longer be used for operations that require a connection.":
#model IEnumerable<COPSGMIS.Models.Step>
#{
ViewBag.Title = "Questionaire";
}
<h2>Questionaire</h2>
#using (Html.BeginForm("Questionaire", "Question", FormMethod.Post, new { id = "SignupForm" }))
{
<div>
#foreach (var item in Model)
{
<fieldset class="wizard">
<legend class="wizard">#Html.DisplayFor(modelItem => item.Title)</legend>
#foreach (var question in item.Questions)
//*** item.Questions is throwing an exception ****
{
<label for="question">#Html.DisplayFor(modelItem => question.QuestionText)</label>
<input id="question" type="text" />
}
</fieldset>
}
<p>
<input id="SaveAccount" type="button" value="Save" />
</p>
</div>
}
My model:
public int StepID { get; set; }
public int ReviewID { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public int StepOrder { get; set; }
public virtual ICollection<Question> Questions { get; set; }
My controller:
var steps = from b in db.Steps
orderby b.StepOrder
select b;
return View(steps.ToList());
When item.Questions is access, EF will try to hit the database to grab the questions for that item. If the context has been disposed, it will fail. Since you'll be looping through the questions in your view, I'd suggest adding them to your initial query using "Include".
var steps = from b in db.Steps.Include(s => s.Questions)
orderby b.StepOrder
select b;
return View(steps.ToList());
I've been reading the various posts on view models and check boxes, but my brain is starting to lock up and I need a little push in the right direction.
Here's my simplified view model. I have checkboxes that need to populate the lists with their values. I don't think this can happen automagically. I'm not sure how to bridge the gap between an array of string values and a List correctly. Suggestions?
public int AlertId { get; set; }
public List<int> UserChannelIds { get; set; }
public List<int> SharedChannelIds { get; set; }
public List<int> SelectedDays { get; set; }
Have your View Model like this to represent the CheckBox item
public class ChannelViewModel
{
public string Name { set;get;}
public int Id { set;get;}
public bool IsSelected { set;get;}
}
Now your main ViewModel will be like this
public class AlertViewModel
{
public int AlertId { get; set; }
public List<ChannelViewModel> UserChannelIds { get; set; }
//Other Properties also her
public AlertViewModel()
{
UserChannelIds=new List<ChannelViewModel>();
}
}
Now in your GET Action, you will fill the values of the ViewModel and sent it to the view.
public ActionResult AddAlert()
{
var vm = new ChannelViewModel();
//The below code is hardcoded for demo. you mat replace with DB data.
vm.UserChannelIds.Add(new ChannelViewModel{ Name = "Test1" , Id=1});
vm.UserChannelIds.Add(new ChannelViewModel{ Name = "Test2", Id=2 });
return View(vm);
}
Now Let's create an EditorTemplate. Go to Views/YourControllerName and Crete a Folder called "EditorTemplates" and Create a new View there with the same name as of the Property Name(ChannelViewModel.cshtml)
Add this code ro your new editor template.
#model ChannelViewModel
<p>
<b>#Model.Name</b> :
#Html.CheckBoxFor(x => x.IsSelected) <br />
#Html.HiddenFor(x=>x.Id)
</p>
Now in your Main View, Call your Editor template using the EditorFor Html Helper method.
#model AlertViewModel
<h2>AddTag</h2>
#using (Html.BeginForm())
{
<div>
#Html.LabelFor(m => m.AlertId)
#Html.TextBoxFor(m => m.AlertId)
</div>
<div>
#Html.EditorFor(m=>m.UserChannelIds)
</div>
<input type="submit" value="Submit" />
}
Now when You Post the Form, Your Model will have the UserChannelIds Collection where the Selected Checkboxes will be having a True value for the IsSelected Property.
[HttpPost]
public ActionResult AddAlert(AlertViewModel model)
{
if(ModelState.IsValid)
{
//Check for model.UserChannelIds collection and Each items
// IsSelected property value.
//Save and Redirect(PRG pattern)
}
return View(model);
}
Part of My View Model:
public List<int> UserChannelIds { get; set; }
public List<int> SharedChannelIds { get; set; }
public List<int> Weekdays { get; set; }
public MyViewModel()
{
UserChannelIds = new List<int>();
SharedChannelIds = new List<int>();
Weekdays = new List<int>();
}
I used partial views to display my reusable checkboxes (I didn't know about editor templates at this point):
#using AlertsProcessor
#using WngAlertingPortal.Code
#model List<int>
#{
var sChannels = new List<uv_SharedChannels>();
Utility.LoadSharedChannels(sChannels);
}
<p><strong>Shared Channels:</strong></p>
<ul class="channel-list">
#{
foreach (var c in sChannels)
{
string chk = (Model.Contains(c.SharedChannelId)) ? "checked=\"checked\"" : "";
<li><input type="checkbox" name="SharedChannelIds" value="#c.SharedChannelId" #chk /> #c.Description (#c.Channel)</li>
}
}
All three checkbox partial views are similar to each other. The values of the checkboxes are integers, so by lining up my view model List names with the checkbox names, the binding works.
Because I am working in int values, I don't feel like I need the extra class to represent the checkboxes. Only checked checkboxes get sent, so I don't need to verify they are checked; I just want the sent values. By initializing the List in the constructor, I should be avoiding null exceptions.
Is this better, worse or just as good as the other solution? Is the other solution (involving an extra class) best practice?
The following articles were helpful to me:
http://forums.asp.net/t/1779915.aspx/1?Checkbox+in+MVC3
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
Binding list with view model
This site handles it very nicely
https://www.exceptionnotfound.net/simple-checkboxlist-in-asp-net-mvc/
public class AddMovieVM
{
[DisplayName("Title: ")]
public string Title { get; set; }
public List<CheckBoxListItem> Genres { get; set; }
public AddMovieVM()
{
Genres = new List<CheckBoxListItem>();
}
}
public class MembershipViewData
{
public MembershipViewData()
{
GroupedRoles = new List<GroupedRoles>();
RolesToPurchase = new List<uint>();
}
public IList<GroupedRoles> GroupedRoles { get; set; }
public IList<uint> RolesToPurchase { get; set; }
}
//view
#model VCNRS.Web.MVC.Models.MembershipViewData
#{
ViewBag.Title = "MembershipViewData";
Layout = "~/Views/Shared/_Layout.cshtml";
int i = 0;
}
#using (Html.BeginForm("Membership", "Account", FormMethod.Post, new { id = "membershipForm" }))
{
<div class="dyndata" style="clear: left;">
<table width="100%" cellpadding="0" cellspacing="0" class="table-view list-view">
foreach (var kvp2 in Model.GroupedRoles)
{
string checkBoxId = "RolesToPurchase" + kvp2.RoleType;
<tr>
<td width="240px">
<label class="checkbox-label" for="#checkBoxId">
<input type="checkbox" class="checkbox" name="RolesToPurchase[#i]"
id="#checkBoxId" value="#kvp2.RoleType" />
#kvp2.Key
</label>
</td>
</tr>
i++;
}
<tr style="background-color: #ededed; height: 15px;">
<td colspan="5" style="text-align: right; vertical-align: bottom;">
#Html.SubmitButton(Resources.MyStrings.Views_Account_Next)
</td>
</tr>
</table>
</div>
}
//Post Action
[HttpPost]
public ActionResult Membership(MembershipViewData viewData)
{
..........................
}
}
Good afternoon
I'm hoping this is going to be nice and a simple f**k up by myself but I've gone in deep and currently can't swim.
Ok, previously I asked this question and as such I have implemented the following:
Model
public class CorporateDetails
{
public Guid? Id { get; set; }
[Key]
public int CorporateDetailId { get; set; }
public int? EmsId { get; set; }
public string EmsName { get; set; }
public virtual EmsType EmsType { get; set; }
}
public class EmsType
{
[Key]
public int? EmsId { get; set; }
public string EmsName { get; set; }
public virtual ICollection<EmsType> EmsTypes { get; set; }
}
Controller
public ActionResult Create()
{
ViewBag.EmsId = new MultiSelectList(db.EmsTypes, "EmsId", "EmsName");
return View();
}
View
<fieldset>
<legend>CorporateDetails</legend>
<div class="editor-label">
#Html.LabelFor(model => model.EmsId, "EmsType")
</div>
<div class="editor-field">
#Html.DropDownList("EmsId", String.Empty)
#Html.ValidationMessageFor(model => model.EmsId)
</div>
<div class="editor-label">
#Html.CheckBoxListFor(model => model.EmsId, (MultiSelectList) ViewBag.EmsId)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Where this CheckBoxListFor uses the following extension:
public static class HtmlHelper
{
//Extension
public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty[]>> expression, MultiSelectList multiSelectList, object htmlAttributes = null)
{
//Derive property name for checkbox name
MemberExpression body = expression.Body as MemberExpression;
string propertyName = body.Member.Name;
//Get currently select values from the ViewData model
TProperty[] list = expression.Compile().Invoke(htmlHelper.ViewData.Model);
//Convert selected value list to a List<string> for easy manipulation
List<string> selectedValues = new List<string>();
if (list != null)
{
selectedValues = new List<TProperty>(list).ConvertAll<string>(delegate(TProperty i) { return i.ToString(); });
}
//Create div
TagBuilder divTag = new TagBuilder("div");
divTag.MergeAttributes(new RouteValueDictionary(htmlAttributes), true);
//Add checkboxes
foreach (SelectListItem item in multiSelectList)
{
divTag.InnerHtml += String.Format("<div><input type=\"checkbox\" name=\"{0}\" id=\"{0}_{1}\" " +
"value=\"{1}\" {2} /><label for=\"{0}_{1}\">{3}</label></div>",
propertyName,
item.Value,
selectedValues.Contains(item.Value) ? "checked=\"checked\"" : "",
item.Text);
}
return MvcHtmlString.Create(divTag.ToString());
}
}
So what's the problem?
Well when I run it I get a nasty error telling me:
"CS0411: The type arguments for method
'Extensions.HtmlHelper.CheckBoxListFor(System.Web.Mvc.HtmlHelper,
System.Linq.Expressions.Expression>,
System.Web.Mvc.MultiSelectList, object)' cannot be inferred from the
usage. Try specifying the type arguments explicitly."
Can someone please point out where I have gone wrong as I'm going round in circles and not sure what I have missed.
Any help gratefully appreciated.
Ricardo,
I have shared some of my own MVC helpers on GitHub. Please feel free to browse & use the code as needed. One of the items in the project is a CheckBoxListItemFor helper, it was created specifically to create checkbox lists. Look under InputExtensions.cs
https://github.com/EdCharbeneau/EJC.UIExtensions/tree/master/EJC.UIExtensions/Html
The problem with this here is that my original EmsId is a nullable int and not an int array as required and hence the error.
Please refer to my answer given here for a full explanation of how this can be achieved.
I am still struggling with learning ASP.NET MVC. All my form entries are required so I would like to do validation on them. For brevity I have paired my model down to Description (textbox) and Paradigm (dropdown). I am including Entry.cs, Paradigm.cs and EntryViewModel.cs Model classes and the Display.cshtml View.
[Bind(Exclude = "EntryId")]
public class Entry
{
[ScaffoldColumn(false)]
public int EntryId { get; set; }
[Required(ErrorMessage = "You must include a description.")]
public string Description { get; set; }
[Display(Name = "Type")]
[Required(ErrorMessage = "You must select a type.")]
public int ParadigmId { get; set; }
public virtual Paradigm Paradigm { get; set; }
}
public class Paradigm
{
[ScaffoldColumn(false)]
public int ParadigmId { get; set; }
[Required]
public string Name { get; set; }
public List<Entry> Entries { get; set; }
}
public class EntryViewModel
{
public Entry Entry { get; set; }
public IEnumerable<Entry> Entries { get; set; }
}
#model Pylon.Models.EntryViewModel
#{
ViewBag.Title = "Display";
}
<hr />
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Entry</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Entry.Description)
</div>
<div class="editor-field">
#Html.TextAreaFor(model => model.Entry.Description)
#Html.ValidationMessageFor(model => model.Entry.Description)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Entry.ParadigmId)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.Entry.ParadigmId, ((IEnumerable<Pylon.Models.Paradigm>)ViewBag.PossibleParadigms).Select(option => new SelectListItem {
Text = (option == null ? "None" : option.Name),
Value = option.ParadigmId.ToString(),
Selected = (Model != null) && (option.ParadigmId == Model.Entry.ParadigmId)
}))
<img src="../../Content/Images/add_icon.gif" />
#Html.ValidationMessageFor(model => model.Entry.ParadigmId)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
If I submit the form without entering a description I would like validation to kick in and say "You must include a description." However instead I receive an ArgumentNullException on the DropDownFor line. http://www.wvha.org/temp/ArgumentNullException.png
What should I be doing? As an aside any decent books that cover ASP.NET MVC 3/Razor. I can follow along the basic tuts, but I go astray when I need to deviate to more advance features.
public class EntriesController : Controller
{
private readonly PylonContext _context = new PylonContext();
public ActionResult Display()
{
// DropDown
ViewBag.PossibleParadigms = _context.Paradigms;
var viewModel = new EntryViewModel {Entries = _context.Entries.ToList()};
return View(viewModel);
}
[HttpPost]
public ActionResult Display(EntryViewModel viewModel)
{
if (ModelState.IsValid)
{
_context.Entries.Add(viewModel.Entry);
_context.SaveChanges();
return RedirectToAction("Display");
}
return View(viewModel);
}
}
It's quite difficult to say without seeing your controller code, but looks like your ViewBag.PossibleParadigms might be null.
Does your insert/update controller action look something like this?
if (ModelState.IsValid) {
///...
} else {
return View(model);
}
If so, you need to put the PossibleParadigms back into the ViewBag (so to speak) before you return back to the view.
If you can post the relevant controller action code, it would be easier to know for sure.