MVC Html.CheckBox and form submit issue - asp.net-mvc

Crazy issue with submitting of values in Html.Checkbox in ASP.NET MVC RC
Some of the values are just not come to Request.Params
At my form I have this line inside the cycle:
<%=Html.CheckBox("cb" + p.Option.Id, p.Option.IsAllowed, new { value = 6 })%>
and it renders to next:
<input checked="checked" id="cb17" name="cb17" type="checkbox" value="6" />
<input name="cb17" type="hidden" value="false" />
<input checked="checked" id="cb18" name="cb18" type="checkbox" value="6" />
<input name="cb18" type="hidden" value="false" />
<input id="cb19" name="cb19" type="checkbox" value="6" />
<input name="cb19" type="hidden" value="false" />
<input id="cb20" name="cb20" type="checkbox" value="6" />
<input name="cb20" type="hidden" value="false" />
<input checked="checked" id="cb21" name="cb21" type="checkbox" value="6" />
<input name="cb21" type="hidden" value="false" />
After submitting the Form I'm get something like:
Form.Params["cb17"] = {6, "false"}
Form.Params["cb18"] = {6, "false"}
Form.Params["cb19"] = {"false"}
Form.Params["cb20"] = {"6,false"}
Form.Params["cb21"] = {"false"}
In the request string Some of the params are displayed twice (normal situation) and some only ONE TIME (only value of hidden field).
It seems that it doesn't rely on whether checkbox was checked or not, whether value has changed or so...
Does anybody faced with such a situation? How can I work around?

<% using(Html.BeginForm("Retrieve", "Home")) %>//Retrieve is the name of the action while Home is the name of the controller
<% { %>
<%foreach (var app in newApps) { %>
<tr>
<td><%=Html.CheckBox(""+app.ApplicationId )%></td>
</tr>
<%} %>
<input type"submit"/>
<% } %>
and in your controller
List<app>=newApps; //Database bind
for(int i=0; i<app.Count;i++)
{
var checkbox=Request.Form[""+app[i].ApplicationId];
if(checkbox!="false")// if not false then true,false is returned
}
the reason you check for false because the Html Checkbox helper does some kind of freaky thing for value true
True returns as:
it makes the string read "true, false"
so you may have thought it was two values but its just one and means true
False returns as:
it makes the string read "false"

This is actually the way it should work according to specifications.
It has nothing to do with ASP.NET MVC, but when a check box is left unchecked it is not included in the POST collection.
You get two values because you have both a checkbox and an input with the same name (and the ones you have two values for are most likely the ones with checkboxes checked).
Edit: specifications from W3C

Without the need to ask database about the ids after form submitting/before saving (Stateless mode) I've produced such code:
foreach (string key in Request.Form)
{
var checkbox = String.Empty;
if (key.StartsWith("cb"))
{
checkbox = Request.Form["" + key];
if (checkbox != "false")
{
int id = Convert.ToInt32(key.Remove(0, 2));
}
}
}
Thanks you guys to help me work around this issue!

I use this:
public struct EditedCheckboxValue
{
public bool Current { get; private set; }
public bool Previous { get; private set; }
public bool Changed { get; private set; }
public EditedCheckboxValue(System.Web.Mvc.FormCollection collection, string checkboxID) : this()
{
string[] values = collection[checkboxID].Split(new char[] { ',' });
if (values.Length == 2)
{ // checkbox value changed, Format: current,old
Current = bool.Parse(values[0]);
Previous = bool.Parse(values[1]);
Changed = (Current != Previous);
}
else if (values.Length == 1)
{
Current = bool.Parse(values[0]);
Previous = Current;
Changed = false;
}
else
throw new FormatException("invalid format for edited checkbox value in FormCollection");
}
}
and then call it like this:
EditedCheckboxValue issomething = new EditedCheckboxValue(collection, "FieldName");
instance.IsSomething = issomething.Current;

Related

How to get MVC button to populate and display a table after being clicked

I've looked for 4 or 5 hours now on how to get this to work but I simply cannot figure it out. I'm suppose to get a form that has both a submit and delete button. The submit should submit the data in the form to a table that gets populated and created at the same time while the delete button would delete the most recent addition. It doesn't seem to matter what I've tried to do it just doesn't work. Whenever I click on my save button it just reloads the page with empty form fields and no table with the data.
My Controller code
public class PersonController : Controller
{
private static List<Person> Persons = new List<Person>();
public ActionResult Index()
{
return View();
}
public ActionResult Start()
{
return View("PersonData");
}
public ActionResult AddPerson(string firstName, string lastName, string birthDate)
{
Person p = new Person();
p.firstName = firstName;
p.lastName = lastName;
p.birthDate = birthDate;
if (Persons.Count > 0)
{
Persons.Add(p);
}
return View("PersonData");
}
public ViewResult DeletePerson()
{
if(Persons.Count > 0)
{
Persons.RemoveAt(0);
}
return View("PersonData");
}
}
My View code
#model IEnumerable<UsingViewsandModels.Models.Person>
....
#using (Html.BeginForm("AddPerson", "PersonController"))
{
}
<form>
<label name="firstName">First Name: </label>
<input type="text" name="firstName" />
<br />
<label name="lastName">Last Name: </label>
<input type="text" name="lastName" />
<br />
<label name="birthDate">Birth Date: </label>
<input type="text" name="birthDate" />
<br />
<button type="submit" value="Submit" name="AddPerson" onclick="AddPerson()">Save</button>
<button type="submit" value="Delete" name="DeletePerson" onclick="DeletePerson()">Delete</button>
</form>
#if (Model != null && Model.Count() > 0)
{
<table>
<tr><th>FirstName</th><th>LastName</th><th>BirthDate</th></tr>
#foreach (UsingViewsandModels.Models.Person p in Model)
{
<tr>
<td>p.firstName)</td>
<td>p.lastName)</td>
<td>p.birthDate)</td>
</tr>
}
</table>
}
Any help would be greatly appreciated. I'm fairly certain I'm just being an idiot and it's something very simple.
You have this code:
return View("PersonData");
That means: return the view named "PersonData".
You are not sending no data to the view. Use the overload and send the model to your view like this:
return View("PersonData", Persons);
Now your view has access to all the data in Persons and it will work.

check the checkbox based on model in mvc?

I have a edit form which should have checkbox.
My Model for the page is
public class MyModel{
public string Name {get;set;}
public List<AdType> AdTypeList { get; set; }
}
Enum AdType{
[Display(Name = "None")]
None,
[Display(Name = "Additional")]
Additional_Photo,
}
So i have to check the check box with respect to the data coming from the database.
And also update should happen if i make changes and submit.
To work that way what changes i need to make in my html helper for the checkbox?
foreach (AdType role in Enum.GetValues(typeof(AdType)))
{
<label>
<input name="Roles" type="checkbox" value="#role" checked="#(Model != null)" />
#{var x = EHelper.GetDisplayValue<AdType>(role);}
#x
</label>
}
I am new to mvc, please parden if i m doing something stupid.
you can put if else condition in model to create condition based html control
foreach (AdType role in Enum.GetValues(typeof(AdType)))
{
<label>
#if(Model != null)
{
<input name="Roles" type="checkbox" value="#role" checked="checked" />
}
else
{
<input name="Roles" type="checkbox" value="#role" />
}
#{var x = EHelper.GetDisplayValue<AdType>(role);}
#x
</label>
}
#foreach (AdType role in Enum.GetValues(typeof(AdType)))
{
<label class="col-sm-12">
<input name="AdTypeList" type="checkbox" value="#role" checked="#(Model != null && Model.AdTypeList!= null && Model.AdTypeList.Any(i => i.HasFlag(role)))" />
#{var text = EHelper.GetDisplayValue<AdType>(role);}
#text
</label>
}

ASP.NET MVC - why ViewBag prop return "value" string instead of actual value?

I need to pass some flag into a view and get it back.
But requirements are:
this flag is not part of model
and it must not be stored in session or such.
I mean - flag visibility scope should be - current page for current user only.
I tried to put it into a ViewBag like this:
public ActionResult Product_Add()
{
ViewBag.IsNew = true;
ViewData["IsNew"] = ViewBag.IsNew;
ViewBag.RecordActionName = "Add";
return View("Product_Edit");
}
And then use this code in *.cshtml:
<input type="hidden" name="IsNew1" value="#ViewBag.IsNew" />
<input type="hidden" name="IsNew2" value='#ViewData["IsNew"]' />
But in resulting HTML I see this:
<input type="hidden" name="IsNew1" value="value" />
<input type="hidden" name="IsNew2" value='value' />
Then I tried this code in *.cshtml:
#{ string isNewRec = ViewBag.IsNew.ToString(); }
<input type="hidden" name="IsNew1" value="#isNewRec" />
And now it works:
<input type="hidden" name="IsNew1" value="True" />
Question is - why first attempt put "value" text instead of boolean value into HTML?
Also the strange thing - this code works fine:
<input type="hidden" name="ProductID" value="#ViewBag.ProductID" />
So, for integer values in ViewBug it works, returns an actual interger value, but for boolean it returns "value" text.
Another part of question.
Currently I use code like this:
[HttpPost]
public ActionResult Product_Commit(Product pProduct)
{
if (ModelState.IsValid)
{
AbcProducts ctx = new AbcProducts();
bool isNew = Convert.ToBoolean(Request.Form["IsNew"]);
if (isNew)
ctx.Products.Add(pProduct);
else
{
// [...]
}
ctx.SaveChanges();
}
return View();
}
Could you recommend something more correct from MVC point of view to pass non-model parameter into a view and get it back then with submited data?
But this parameter should not be stored anywhere where other pages can see it somehow.

Model binding to a list with Non-Sequential Indices possible in MVC 3?

I'm following the info on http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx from Phil Haack
He talks about Non-Sequential indices:
<form method="post" action="/Home/Create">
<input type="hidden" name="products.Index" value="cold" />
<input type="text" name="products[cold].Name" value="Beer" />
<input type="text" name="products[cold].Price" value="7.32" />
<input type="hidden" name="products.Index" value="123" />
<input type="text" name="products[123].Name" value="Chips" />
<input type="text" name="products[123].Price" value="2.23" />
<input type="hidden" name="products.Index" value="caliente" />
<input type="text" name="products[caliente].Name" value="Salsa" />
<input type="text" name="products[caliente].Price" value="1.23" />
<input type="submit" />
</form>
Is this possible in MVC3 when you use model binding with TextBoxFor?
This is the way to do it with sequentiel indices:
#Html.TextBoxFor(m => m[i].Value)
If it's not possible, is their anything else I can do if my indices will not be sequential?
AFAI understand the non-sequential indices technique would require to add hidden fields for each index value. I'm quite sure that the Html.TextBoxFor helper itself does not generate any additional hidden fields. Probably you could achieve this by manually adding the hidden fields with the non-sequential indices.
I tried this and I couldn't get it to work with textboxfor. I used Textbox and specified the name.
<form method="post" action="/Home/Create">
#{var index = Guid.NewGuid();}
#Html.Textbox("products["+index +"].Name",Beer)
#Html.Textbox("products["+index +"].Price",7.32)
.
.
.
<input type="submit" />
</form>
You don't need a hidden index for MVC 3.0.
Let me know if this does not work I have a way of doing this, I'm just taking this from the top of my head.
To not messing with your view, the easiest way I found to do this is following this extension: BeginCollectionItem.
The complete project is here: https://github.com/danludwig/BeginCollectionItem
But AFAIK you only need this class:
public static class HtmlPrefixScopeExtensions
{
private const string idsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_";
public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName)
{
var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();
// autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, html.Encode(itemIndex)));
return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
}
public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix)
{
return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
}
private static Queue<string> GetIdsToReuse(HttpContextBase httpContext, string collectionName)
{
// We need to use the same sequence of IDs following a server-side validation failure,
// otherwise the framework won't render the validation error messages next to each item.
string key = idsToReuseKey + collectionName;
var queue = (Queue<string>)httpContext.Items[key];
if (queue == null) {
httpContext.Items[key] = queue = new Queue<string>();
var previouslyUsedIds = httpContext.Request[collectionName + ".index"];
if (!string.IsNullOrEmpty(previouslyUsedIds))
foreach (string previouslyUsedId in previouslyUsedIds.Split(','))
queue.Enqueue(previouslyUsedId);
}
return queue;
}
private class HtmlFieldPrefixScope : IDisposable
{
private readonly TemplateInfo templateInfo;
private readonly string previousHtmlFieldPrefix;
public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix)
{
this.templateInfo = templateInfo;
previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix;
templateInfo.HtmlFieldPrefix = htmlFieldPrefix;
}
public void Dispose()
{
templateInfo.HtmlFieldPrefix = previousHtmlFieldPrefix;
}
}
}
How to use it in your Views:
<form method="post" action="/Home/Create">
#foreach (var item in Model.Products) {
#using (Html.BeginCollectionItem("Products"))
{
#Html.TextBoxFor(item => item.Name)
#Html.TextBoxFor(item => item.Price)
}
}
...
...
</form>
I think this is cleaner than messing with the indexs in you views...
Here is the post that explains how to do it step by step: http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/
Nuget Package: http://www.nuget.org/packages/BeginCollectionItem/

unobtrusive MVC3 validating group of checkboxes

I need to validate a group of checkboxes using MVC3 unobtrusive validation. how would i do it? I found this and tried it, but it doesn't work.
$(function(){
$.validator.addMethod('cb_selectone', function(value,element){
if(element.length>0){
for(var i=0;i<element.length;i++){
if($(element[i]).val('checked')) return true;
}
return false;
}
return false;
}, 'Please select at least one option');
$('#main').validate({rules:{Services:"cb_selectone"}});
...
My Html:
<input type="checkbox" class="checkbox" name="Services" value="1" />
<input type="checkbox" class="checkbox" name="Services" value="2" />
<input type="checkbox" class="checkbox" name="Services" value="3" />
It would be best if someone provided a complete solution with server side + client side validation (of course using MVC3 unobtrusive validation).
Thanks
Ok, figured it out:
for server validation:
using data annotations (required will do it)
like so in my view model:
[Required(ErrorMessageResourceName = "fld_Service_val_Required_lbl", ErrorMessageResourceType = typeof(Resources.Service.Controllers.Firm))]
public ICollection<int> Services { get; set; }
for client validation in my html i added a class to my input checkboxes:
<input type="checkbox" class="checkbox required-checkbox" name="Services" value="1" />
<input type="checkbox" class="checkbox required-checkbox" name="Services" value="2" />
<input type="checkbox" class="checkbox required-checkbox" name="Services" value="3" />
and also:
$(function(){
$.validator.addMethod('required_group', function(value, element) {
var $module = $(element).parents('form');
return $module.find('input.checkbox:checked').length;
}, 'Select at least one Service please');
$.validator.addClassRules('required-checkbox', { 'required_group' : true });
..
not sure if this is the best solution but it works :). if someone knows a better please post.
This works - validates appropriately on submit, and hides/displays the validation message if a checkbox is subsequently checked or unchecked with minimal overhead (only gets hit once per validation cycle).
JavaScript:
(function ($) {
//functions from unobtrusive:
function setValidationValues(options, ruleName, value) {
options.rules[ruleName] = value;
if (options.message) {
options.messages[ruleName] = options.message;
}
}
var formEls;
function getUniqueFormId(form) {
if (typeof(formEls==='undefined')) {
formEls = document.getElementsByTagName('form');
}
return 'form' + Array.prototype.indexOf.call(formEls, form);
}
//from jQuery validation
function findByName(name, form) {
// select by name and filter by form for performance over form.find("[name=...]")
return $(document.getElementsByName(name)).map(function (index, element) {
return element.form == form && element.name == name && element || null;
});
}
//-------------------------
$.validator.addMethod('requiredgroup', function (value, element, params) {
for (var i = 0; i < params.length; i++) {
if (params[i].checked) { return true; }
}
return false;
});
valGroups = [];
$.validator.unobtrusive.adapters.add('requiredgroup', function (options) {
var groupName = options.element.name,
uniqueGroup = getUniqueFormId(options.form) + groupName;
if (!valGroups[uniqueGroup]) {
valGroups[uniqueGroup] = findByName(groupName, options.form);
}
//jQuery Validation Plugin 1.9.0 only ever validates first chekcbox in a list
//could probably work around setting this for every element:
setValidationValues(options, 'requiredgroup', valGroups[uniqueGroup]);
});
} (jQuery));
of course the elements must have a data-val-requiredgroup attribute. The following MVC code (as part of a helper) will add the appropriate annotations:
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
string name = ExpressionHelper.GetExpressionText(expression);
var baseAttr = htmlHelper.GetUnobtrusiveValidationAttributes(name, metaData);
baseAttr.Add("name", name);
baseAttr.Add("type", "checkbox");
if (metaData.IsRequired)
{
baseAttr.Add("data-val-requiredgroup", baseAttr["data-val-required"]);
baseAttr.Remove("data-val-required");
Because it looks for the Required attribute, server side validation is handled by the existing framework.

Resources