This seems like it should be prettty easy - but I just can't get it to work!
I have an enum in my model, which I want to display as a list of checkboxes. The user can select multiple checkboxes, and I want to save this in the database.
So the enum is like so (approx 20 elements unabridged):
public enum ReferrerType
{
[Description("No Data")]
NoData = 9999,
[Description("Father or Mother")]
Parents = 1,
[Description("Brother or Sister")]
Sibling = 2,
[Description("Other")]
Other = 10
}
Whereby the Description is what is shown on the UI, and the numeric value is what is to be saved in the database. The numbers have to remain as listed, as they go directly into a stats package.
I then defined a Referrer class:
public class Referrer
{
public virtual Guid Id { get; private set; }
public virtual ReferrerType{ get; set; }
}
I realise this might be an odd (anti)pattern. I developed it in haste, and am repenting at leisure. Any advice on improving this model would also be much appreciated!
My controller sets up the list:
private static IList<string> GenerateReferrerList()
{
var values = from ReferrerType e in Enum.GetValues(typeof(ReferrerType))
select new { Name = e.ToDescription() };
return values.Select(x => x.Name).ToList();
}
And I use it in my View like this:
<div class="radio-boolean form-field" id="Referrers">
<p class="form-field-attribute"> <span class="label">Referred By </span> </p>
<% for (var i = 0; i < ((IList<string>)ViewData["ReferrerList"]).Count; i++)
{ %>
<p class="form-field-value">
<%= Html.CheckBox(string.Format("Referrers[{0}].Type", i) ) %>
<label for="Referrers"> <%= ((IList<string>)ViewData["ReferrerList"])[i]%></label>
</p>
</div>
And it doesn't work! I guess I'm missing something obvious, but I can't work out what. There are no errors - just an empty database table where referrers should be...
As always, any help much appreciated!
Let's take a moment and see what do we need here. We need to show a form which will contain multiple checkboxes (one for each value of the enum) and an associated label (this label should come from the Description attribute use on the enum). When this form is submitted we want to fetch all the values that the use has checked.
So as always once we have clear definition of what we are trying to do we introduce our view model:
public class MyViewModel
{
public bool IsChecked { get; set; }
public ReferrerType ReferrerType { get; set; }
public string Text { get; set; }
}
Then we write a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = Enum.GetValues(typeof(ReferrerType)).Cast<ReferrerType>().Select(x => new MyViewModel
{
ReferrerType = x,
Text = x.ToDescription() // I guess that's an extension method you wrote
});
return View(model);
}
[HttpPost]
public ActionResult Index(IEnumerable<MyViewModel> model)
{
...
}
}
And finally a strongly typed view corresponding to the Index action of our controller (~/Views/Home/Index.aspx):
<% using (Html.BeginForm()) { %>
#Html.EditorForModel()
<input type="submit" value="OK" />
<% } %>
and the last part is the corresponding editor template (~/Views/Home/EditorTemplates/MyViewModel.ascx):
<%# Control
Language="C#"
Inherits="System.Web.Mvc.ViewUserControl<AppName.Models.MyViewModel>" %>
<%= Html.CheckBoxFor(x => x.IsChecked) %>
<%= Html.HiddenFor(x => x.ReferrerType) %>
<label><%: Model.Text %></label>
Now when this form is submitted inside the POST index action you would get a list of all enums with a corresponding boolean value indicating whether the user checked it or not.
OT: Don't perform excess actions:
return (from e in Enum.GetValues(typeof(ReferrerType))
select e.ToDescription()).ToList();
or just
return Enum.GetValues(typeof(ReferrerType)).Select(e => e.ToDescription()).ToList();
Related
I have an a href link to a page which adds a parameter to the link for example:
tsw/register-your-interest?Course=979
What I am trying to do is to extract the value in Course i.e 979 and display it in the view. When attempting with the below code, I only return with 0 rather than the course value expected. ideally I'd like to avoid using routes.
Here is the view:
<div class="contact" data-component="components/checkout">
#using (Html.BeginUmbracoForm<CourseEnquiryPageSurfaceController>("PostCourseEnquiryForm", FormMethod.Post, new { id = "checkout__form" }))
{
//#Html.ValidationSummary(false)
#Model.Course;
}
And my controller:
public ActionResult CourseEnquiry(string Course)
{
var model = Mapper.Map<CourseEnquiryVM>(CurrentContent);
model.Course = Request.QueryString["Course"];
return model
}
This is the View Model:
public class CourseEnquiryVM : PageContentVM
{
public List<OfficeLocation> OfficeLocations { get; set; }
public string Test { get; set; }
public string Course { get; set; }
public List<Source> SourceTypes { get; set; }
}
SOLUTION:
After some research and comments I've adjusted the code to the below which now retrieves the value as expected
#Html.HiddenFor(m => m.Course, new { Value = #HttpContext.Current.Request.QueryString["Course"]});
Thanks all
Based on the form code you provided you need to use #Html.HiddenFor(m => m.Course) instead of just #Model.Course. #Model.Course just displays the value as text instead of building a input element that will be sent back to your controller.
If your problem is with a link prior to the view you referenced above, here's what I'd expect to work:
View with link:
#model CourseEnquiryVM
#Html.ActionLink("MyLink","CourseEnquiry","CourseController", new {course = #Model.Course}, null)
CourseController:
public ActionResult CourseEnquiry(string course)
{
// course should have a value at this point
}
In your view, you are only displaying the value of Course.. which isn't able to be submitted. You need to incorporate the value of course with a form input element (textbox, checkbox, textarea, hidden, etc.).
I would highly suggest using EditorFor or Textboxfor, but because your controller action is expecting just a string parameter you could just use Editor or TextBox.
#using (Html.BeginUmbracoForm<CourseEnquiryPageSurfaceController>("PostCourseEnquiryForm", FormMethod.Post, new { id = "checkout__form" }))
{
//#Html.ValidationSummary(false)
#Html.TextBox(Model.Course, null, new { #class = "form-control"});
<input type="submit" value="Submit" />
}
Then you should just be able to do this in your controller:
public ActionResult CourseEnquiry(string course) // parameter variables are camel-case
{
var model = Mapper.Map<CourseEnquiryVM>(CurrentContent);
if(!string.IsNullOrWhiteSpace(course))
model.Course = course;
return model;
}
Let me know if this helps.
I have an MVC view
<%# Page Language="C#" MasterPageFile="PathToMaster" Inherits="System.Web.Mvc.ViewPage<ModelData>" %>
and I have a form with HTML markup for a set of checkboxes:
<label for="MyCheckbox">Your choice</label>
<input type="checkbox" id="Option1" class="checkbox" name="MyCheckbox" value="Option one" />
<label for="Option1">Option one</label><br />
<input type="checkbox" id="Option2" class="checkbox" name="MyCheckbox" value="Option two" />
<label for="Option2">Option two</label><br />
and I have a controller-action pair
class MyController : Controller {
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult RequestStuff( ModelData data )
{
}
}
and that action is invoked when the form is submitted.
How do I map the checkboxes onto members of ModelData (and what members I have to add to ModelData) so that when the form is submitted data stores information on which checkboxes are checked?
OK, this one will be for MVC3, but - save for syntax changes - should work in MVC2 too. The approach is essentially the same.
First of all, you should prepare an appropriate (view)model
public class MyViewModel
{
[DisplayName("Option 1")]
public bool Option1 { get; set; }
[DisplayName("Option 2")]
public bool Option2 { get; set; }
}
Then you pass this model to the view you're showing (controller):
public ActionResult EditMyForm()
{
var viewModel = new MyViewModel()
return View(viewModel);
}
with the form:
#model MyViewModel
#using( Html.BeginForm())
{
#Html.Label("Your choice")
#Html.LabelFor(model => model.Option1) // here the 'LabelFor' will show you the name you set with DisplayName attribute
#Html.CheckBoxFor(model => model.Option1)
#Html.LabelFor(model => model.Option2)
#Html.CheckBoxFor(model => model.Option2)
<p>
<input type="submit" value="Submit"/>
</p>
}
Now here the HTML helpers (all the CheckBoxFor, LabelFor, EditorFor etc) allow to bind the data to the model properties.
Now mind you, an EditorFor when the property is of type bool will give you the check-box in the view, too. :)
And then, when you submit to the controller, it will auto-bind the values:
[HttpPost]
public ActionResult EditMyForm(MyViewModel viewModel)
{
//And here the view model's items will be set to true/false, depending what you checked.
}
First you define SelectList for Options. This will be used just to render checkboxes
public IList<SelectListItem> OptionsSelectList { get; set; }
Than, you define model that will hold value of single chosen option after post
public class ChooseOptionViewModel
{
public int OptionIdentifier { get; set; } //name or id
public bool HasBeenChosen { get; set; } //this is mapped to checkbox
}
Then IList of those options in ModelData
public IList<ChooseOptionViewModel> Options { get; set; }
And finally, the view
#for (int i = 0; i < Model.OptionsSelectList.Count(); i++)
{
<tr>
<td class="hidden">
#Html.Hidden("Options[" + i + "].OptionIdentifier", Model.OptionsSelectList[i].Value)
</td>
<td>
#Model.OptionsSelectList[i].Text
</td>
<td>
#Html.CheckBox("Options[" + i + "].HasBeenChosen", Model.Options != null && Model.Options.Any(x => x.OptionIdentifier.ToString().Equals(Model.OptionsSelectList[i].Value) && x.HasBeenChosen))
</td>
</tr>
}
After post, you just inspect Options.Where(x => x.HasBeenChosen)
This is full-functional, and it will allow you to redisplay view when validation errors occur, etc. This seems a bit complicated, but I haven't come up with any better solution than this.
I have a problem in my application
I am using MVC 3 with razor and i want to get the value of checked box in the form
#using (Html.BeginForm("ConfirmItemAvilabilty", "Order", FormMethod.Post, new { id = "frmConfirmAvilabilty", name = "frmConfirmAvilability" }))
{
foreach (OrderItem orderItem in orderAction.Order.OrderItems)
{
<div class="product">
<ul>
<li>
<div class="productImg">
<img src="#orderItem.Product.Image" width="100px" height="100px"/>
</div>
<div class="centered">
Link <span>#orderItem.Product.TitleAr</span>
<span>#orderItem.Product.Price</span>
</div>
#if (currentUser.UserTypeEnum == UserTypeEnum.Reseller)
{
<div>
#Html.CheckBox("ChkConfirm", orderItem.IsAvailable, new { id="chkConfirm" ,#class="chkConfirm"})
#Html.Hidden("OrderItemId", orderItem.Id, new { id="hdnConfirm"})
</div>
}
</li>
</ul>
</div>
}
if (currentUser.UserTypeEnum == UserTypeEnum.Reseller)
{
<button>Confirm</button>
}
}
Simply i want to get the value of all checked checkboxs i tryed to create a model holding the value of my checkbox and the value of the hidden text below it
public class ItemOrderModel
{
public string ChkConfirm { get; set; }
public string OrderItemId { get; set; }
}
and in my controller i do the following but nothing happened
public ActionResult ConfirmItemAvilabilty(List<ItemOrderModel> OrderItems)
{
return View();
}
but orderItems always returns null, Can anyone help me in that?
----------------- Edit ------------------
Thank you Sam and Jesse
I Found a solution for my problem but I am facing another problem now first of all i solved my problem by changing in model view to be like that
public class ItemOrderModel
{
public List<bool> ChkConfirms { get; set; }
public List<string> OrderItemId { get; set; }
}
and change the checkbox name to be
#Html.CheckBox("ChkConfirms", orderItem.IsAvailable, new { id = "chkConfirm", #class = "chkConfirm" })
the problem now is
when i submit i found two values false that is the actual representation of my checkboxs and two ids that's also the actual representation of my hidden fields "Correct scenario"
when i check one of check boxs in the same form i found 3 result for the check box and 2 results for the hidden field Can any one help in that or have a solution
You need to look into Editor Templates:
How to create custom editor/display templates in ASP.NET MVC 3?
These allow you to do the exact thing you are talking about.
I need to populate two listboxes with data from a database.
But I need to display two listboxes in one view, so I created a ListBoxModel class.
public class ListboxModel
{
public ListBox LB1 { get; set; }
public ListBox LB2 { get; set; }
}
And in my Controller:
public ActionResult IndexStudents(Docent docent, int lessonid, int classid)
{
var MyListBox = new ListboxModel
{
LB1 = new ListBox(docent.ReturnStudentsNormal(lessonid, classid),
LB2 = new ListBox(docent.ReturnStudentsNoClass(lessonid, classid)
};
return View(MyListBox);
}
But this code does not work, how can I bind the data to the listboxes?
So I'd like to use 2 models, one for each listbox... one with normal students and one with students who are not subscribed for lessons.
How could I do that?
And what code do I have to write in my view?
Something like:
<div class="editor-field">
<%: Html.ListBox("IndexStudentsNormal", Model.LB1) %>
<div class="editor-field">
<%: Html.ListBox("IndexStudentsNoClass", Model.LB2) %>
The listbox has to be a listbox with multiple colums so that it can contain the name, sirname, class, teacher of the student.
Student is an object, I'd like to display student.Name, student.Sirname, student.Class, and so on in that listbox.
So can I use an object in a ListBox, or do I have to convert everything to strings?
How can I do that?
Thanks in advance!
The model shouldn't contain a ListBox, it should just contain the lists of students. The trick is to keep the Model as simple as possible, it should really only be a bunch of property getters and setters. The view is responsible for binding a Model's properties to HTML elements.
Model:
public class StudentModel
{
public IList<string> NormalStudents {get;set;}
public IList<string> NoClassStudents {get;set;}
}
Controller:
public ActionResult IndexStudents(Docent docent, int lessonid, int classid)
{
var studentModel = new ListboxModel
{
NormalStudents = docent.ReturnStudentsNormal(lessonid, classid),
NoClassStudents = docent.ReturnStudentsNoClass(lessonid, classid)
};
return View(studentModel);
}
View:
<div class="editor-field">
<%: Html.ListBox("IndexStudentsNormal", Model.NormalStudents) %>
</div>
<div class="editor-field">
<%: Html.ListBox("IndexStudentsNoClass", Model.NoClassStudents) %>
</div>
Based on Jason's answer, the first line in your view should include:
<%# Inherits="System.Web.Mvc.ViewPage<StudentModel>" %>
This tells your view that "Model" is of type StudentModel. If there's other bits in this first line (Title, Language, MasterPageFile, etc), they're fine to stay there.
-- edit: add longish comments --
The thing to remember is that a SelectListItem has three required parts: Value, Text, and Selected. Value is the key, so something like StudentId or DocentId. Text is displayed in the list, so something like StudentName or DocentName. Selected indicates whether this item is selected in the list, typically false.
Right now it looks like you have methods that only return a list of the student names (Docent.ReturnStudentsNormal() and Docent.ReturnStudentsNoClass()). I would have these methods return a list of key-value pairs, key being StudentId and value being StudentName.
Then you can change your model class to be
public class StudentModel
{
List<SelectListItem> NormalStudents;
List<SelectListItem> StudentsNoClass;
}
and in your controller
public ActionResult IndexStudents(Docent docent, int lessonId, int classId)
{
var studentModel = new StudentModel();
var normalStudents = docent.ReturnStudentsNormal(lessonId, classId);
foreach (var student in normalStudents)
{
studentModel.NormalStudents.Add(new SelectListItem() {Value = student.Key, Text = student.Value});
}
var studentsNoClass = docent.ReturnStudentsNormal(lessonId, classId);
foreach (var student in studentsNoClass)
{
studentModel.StudentsNoClass.Add(new SelectListItem() {Value = student.Key, Text = student.Value});
}
return View(studentModel);
}
Now you'll be able to use these properties on your model directly for Html.ListBox().
This is my model:
public class IndexViewModel
{
public FilterConditions conditions { get; set }
public IEnumerable<SelectListItem> Countries { get; set }
}
public class FilterConditions
{
public string condition11 { get; set }
// ...
}
And I have an Index action method like so:
public ActionResult Index()
{
var model = new IndexViewModel();
// fill the model here with default values
return View(model);
}
The view renders a form with the filterconditions as input types.
Now I want the post back from that form be handled by this action method:
[HttpPost]
public ActionResult Index(FilterConditions model)
{
// do some magic with model and return another view here
}
and this actually works (I put a breakpoint in the method, and it gets called), but the properties of my model are always empty (default values), while they should contain the values which were posted by the form.
When I modify the action method like this:
[HttpPost]
public ActionResult Index(IndexViewModel model)
{
// do some magic with model.conditions and return another view here
}
It all works like it should, but this is not "right" (IMHO), as I don't need the ´Countries´ list on return, I only need the selected country (which is one of the conditions).
What is a nice (best practice) way to make this work without having to take the whole original viewmodel as an input parameter?
Btw, I'm using ASP.NET MVC 2 (I don't think it really matters, as I think it's the same problem in MVC 3, but I'm not entirely sure of that).
(I have been looking around the internet for "best practices" regarding dropdownlists and viewmodels within asp.net mvc, but the different recommendations I found didn't really line up with each other, and a lot is already outdated as well. I didn't find an "official" best practice around this. I hope I'm going in the right direction (having the list as part of my viewmodel), feel free to correct me on this matter if I'm not. Also feel free to point me to "endorsed best practices" about this if you know of any.)
Update:
I found out that I can use the [Bind] attribute with a Prefix of "filterconditions". And this indeed works for this view. But my original problem (I admit, it was not included in my question) is not solved.
It happens that this particular action method is also called from another view (it is an ajax call) where it doesn't have that prefix, in that case it doesn't work any more now. Any suggestions?
I've found the solution.
Apparently, when I use the same name for the parameter variable as the name of the type (the case doesn't have to match), like this:
[HttpPost]
public ActionResult Index(FilterConditions filterConditions)
{
// do some magic with model and return another view here
// now the filterConditions variable actually contains values!
}
Everything works like it should (the values of my filterConditions are not empty/null anymore). Apparently, the default modelbinder uses the name of the parameter as the potential prefix for the binding.
I'm glad I found out, but it would be nice if this is more clearly documented somewhere. It's not obvious at all.
Edit:
On request: this is the code in my view (aspx):
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MyProject.Models.IndexViewModel>" %>
<%-- ... more stuff here ... --%>
<% using (Html.BeginForm())
{%>
<%= Html.ValidationSummary(true)%>
<fieldset>
<div class="editor-label">
<%= Html.LabelFor(model => model.FilterConditions.Country)%>
</div>
<div class="editor-field">
<%= Html.DropDownListFor(model => model.FilterConditions.Country, Model.Countries)%>
<%= Html.ValidationMessageFor(model => model.FilterConditions.Country)%>
</div>
<div class="editor-label">
<%= Html.LabelFor(model => model.FilterConditions.Make)%>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(model => model.FilterConditions.Make)%>
<%= Html.ValidationMessageFor(model => model.FilterConditions.Make)%>
</div>
<%-- ... more fields inserted here ... --%>
<p>
<input type="submit" value=" Search... " />
</p>
</fieldset>
<% } %>
Hi fretje: Now I can use your way to solve your problem, First I have two Models "IndexViewModel" & "Index", and the DropDownList(it's doesn't matter, just offer the DropDown items):
public class IndexViewModel : Index
{
//public int value { get; set; }
public List<System.Web.Mvc.SelectListItem> items { get; set; }
}
public class Index
{
public int value { get; set; }
}
class DropDownList
{
public List<System.Web.Mvc.SelectListItem> GetDropDownList()
{
List<System.Web.Mvc.SelectListItem> result = new List<System.Web.Mvc.SelectListItem>();
result.Add(new System.Web.Mvc.SelectListItem
{
Value = "1",
Text = "Apple"
});
result.Add(new System.Web.Mvc.SelectListItem
{
Value = "2",
Text = "Milk"
});
return result;
}
}
And two Controllers is Test() and Test(Models.Index), I pass the IndexViewModel and postback IndexModel:
public ActionResult Test()
{
var result =
new Models.IndexViewModel
{
value = 1,
items = new Models.DropDownList().GetDropDownList()
};
return View(result);
}
[HttpPost]
public ActionResult Test(Models.Index posatback)
{
return View();
}
The View of Test() is:
<% using (Html.BeginForm()) {%>
<%: Html.ValidationSummary(true) %>
<fieldset>
<legend>Fields</legend>
<div class="editor-field">
<%: Html.DropDownListFor(m=>m.value, Model.items )%>
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
<% } %>
that's work! thank you fretje, I learned one more tech. : )
Maybe you can try
[HttpPost]
public ActionResult Index([Bind(Exclude="Countries")]IndexViewModel model)
{
// do some magic with model.conditions and return another view here
}
Hi~
You don't need to combine the whole SelectListItem to ViewModel, actually your ViewModel just only have a field to store user's choise, integer or string, then use DropDownListFor like:
<%: Html.DropDownListFor(item.WeaponID, MyApplication.Models.DropDownList.GetDropDownList() )%>
please see my post in my blogspot, I use a very simple example to explain:
http://maidot.blogspot.com/2011/04/aspnet-mvc-viewdropdownlistfor.html
let me know if you have any problems : )