Razor/MVC Empty List Validation - asp.net-mvc

I have a SelectList of IDs that the user can choose zero-many items into a List. By default the validation logic appears to require at least one element to be selected. I have tried using the annotation [MinLength(0)] as various documentation implies that it applies to not only strings but collections/lists.
The obvious answer to my question is to just disable validation of the property.
The slightly less obvious answer is to write my own custom ValidationAttribute which seems to be more effort than warranted.
I am looking if there is another simple way.
My ViewModel property:
public List<int> DiverIDList { get; } = new List<int>();
My CSHTML:
#{
var DiverSL = (SelectList)ViewData["DiverSL"];
}
<div class="form-group">
<label asp-for="DiverIDList" class="control-label"></label>
<select asp-for="DiverIDList" class="form-control"
asp-items="#DiverSL" size="8">
<option value="">-- Select Divers --</option>
</select>
<span asp-validation-for="DiverIDList" class="text-danger" />
</div>

In order to better understand the problem, I decided to create a custom ValidationAttribute derived from RequiredAttribute. Although I was able to verify that my new validation attribute's IsValid was getting called, there were still cases when the ICollection.Count == 0 was causing the custom validation to not be called and the server ModelState contained an error. Something in the framework is bypassing attribute based validation in this case.
Because I have Nullable enabled, I tried the different combinations of making the List nullable or not. No change in behavior.
The only viable solution appears to be disabling validation for that property. This requires two steps:
In the OnPost method before calling ModelState.IsValid, insert a call to ModelState.Remove("ModelDiver.DiverIDList");
In the CSHTML, add data-val="false" to the <select> tag.

Related

Tag helpers seem to interfere with traditional Razor syntax

Applying traditional Razor syntax to a textarea with a asp-for tag applied to it (and a RequiredAttribute applied in the view model) seems to interfere with each other.
#{ string disabled = "disabled"; }
<textarea asp-for="Motivation" class="form-control" rows="3" #disabled></textarea>
The disabled attribute is not applied.
<textarea
class="form-control" rows="3"
data-val="true" data-val-required="..."
id="Motivation" name="Motivation" placeholder="...">
</textarea>
I can understand something must be going wrong (or is unsupported) since asp-for needs to be rewritten to the matching attributes derived from the view model.
My main questions are:
Is this documented/expected behavior?
What is the 'ASP.NET' way of going about similar modifications to the HTML output?
I am only just starting out with ASP.NET, so am uncertain as to the first question: "Is this documented/expected behavior?" This is thus only a partial answer and I am looking for more information regarding the first question.
However, to answer the second question, one possibility seems to rely on (older?1) HTML helpers which offer more flexibility:
#{ string disabled = "disabled"; }
#Html.TextAreaFor(
m => m.Motivation,
new { #class = "form-control", rows = 3, disabled = disabled } )
The second parameters are additional attributes which are applied to the output of the textarea.
Furthermore, it should also be noted that in XHTML attribute minimization is forbidden.
In XHTML, attribute minimization is forbidden, and the disabled
attribute must be defined as <input disabled="disabled" />.
1 I can't seem to find official documentation on this.

MVC Unobtrusive validation for a dropdown using knockout.js?

I am using knockout.js to populate a dropdown:
<select data-bind="options: AvailableUsers, optionsText: 'DisplayName', value: SelectedUser, optionsCaption: '-- Select a User --'" data-val="true" data-val-required="You must select a user." id="SelectedUser" name="SelectedUser"></select>
<span class="field-validation-valid" data-valmsg-for="SelectedUser" data-valmsg-replace="true"></span>
and I am registering the validator to the form and having it called on the submithandler (I dont think this is related to the problem since the validation is executing):
$.validator.unobtrusive.parse("#UserProfileCreation");
$("#UserProfileCreation").data("validator").settings.submitHandler = mappedModel.save;
However when trying to submit the page, it always acts like the dropdown has no selected value. Even when I confirm via console that SelectedUser has a value. I have successfully done the same thing in other pages for textareas like so:
<textarea style="width: 100%; height: 50px; min-height: 30px;" name="GroupReply" id="GroupReply" data-bind="value: GroupReply" data-val="true" data-val-required="You must enter a reply."></textarea><br/><span class="field-validation-valid" data-valmsg-for="GroupReply" data-valmsg-replace="true"></span>
And that works fine. So I am not sure what I am missing for the dropdown, but whether I select an option or not, it keeps acting like it's blank and bringing up the validation error message. What am I missing?
I figured it out and it was quite simple due to my lack of understanding of how knockout handles dropdown's selected values.
My AvailableUsers in the KO View Model consisted of a list of KeyValueModels which were based off a C# MVC class (converted using the KO mapping plugin):
public class KeyValueModel{
public Guid Id {get; set;}
public string DisplayName {get; set;}
}
I simply needed to add optionsValue: 'Id' to the data-bind attribute of the dropdown. However it should be noted that this only works because I am passing a Guid as the SelectedUser property of the MVC View Model in the action parameter.
There have been times where I wanted to pass an entire javascript object that the dropdown selection represents to the MVC view model, in those cases this solution wouldn't work.
Note in console, if you do NOT have the optionsValue: 'Id' you select an option from the dropdown and type mappedModel.SelectedUser() you get:
Object {Id: "adb9ae2d-01c8-468d-96e6-06ec39039e29", DisplayName: "Johnson, John"}
Because knockout can store the whole selected object. HOWEVER, knockout does not set ANY value to the options in the dropdown in the actual HTML markup, they are all null (which is why the validation was failing).
If you do add optionsValue: 'Id' and type mappedModel.SelectedUser() into console, then you would simply get:
"adb9ae2d-01c8-468d-96e6-06ec39039e29"
Which for my purposes on this page is fine. As mentioned if you wanted to pass an entire object to the MVC action based on that selection, this setup would not work. You would probably have to do something like setup a hidden field and set it's value to the SelectedUser().Id and put the validation on that hidden field.

Exclude <input type='file'> from ViewModel

I have the following file input tag in the "Create" View:
<input type="file" id="RequestFile" name="RequestFile"/>
#Html.ValidationMessage("RequestFile")
The ViewModel contains this corresponding property:
[Required(ErrorMessage="Please select a file")]
public HttpPostedFileBase RequestFile { get; set; }
This works fine in the "Create" View, but in the "Edit" View, I get ModelState.Isvalid as false. Using the same ViewModel I would like to exclude this field from validations because I would not want to upload the file again.
I tried simply disabling the input tag like this:
<input type="file" id="RequestFile" name="RequestFile" disabled/>
This has a disabled input control but the Validation still fired.
Also applying the BindAttribute in the Controller did not work (see this Question)
Ideally (I know it sounds unlikely), if there is a server-side solution to this, please post your thoughts. If there is a small client-side trick, please let me know!
The best ways are remove the property altogether, and always access it directly from the form collection (and manually validate it) or manually remove the model state error using the property name (as #cheesemacfly has in his comment, ModelState.Remove("RequestFile")). The latter makes it very easy to fix then.
You could use form.onsubmit to check to see it document.getElementById("RequestFile").value is not null/empty and cancel the submit if it is.
something like
<form onsubmit="if(!document.getElementById('RequestFile').value){alert('Please select a file.');return false;}" >
<input type="file" id="RequestFile" name="RequestFile" />
<input type="submit"/>
</form>
return false cancels the submission.
http://jsfiddle.net/Cg7HY/1/
or put it in the click event of the submit button itself
http://jsfiddle.net/Cg7HY/3/

Grails setting values in Select (dropdown menu) for a boolean

I'm using the Grails framework. In my User controller, I have a boolean field named "active" which controls if users are allowed to login. The login action checks this value when the user is logging in.
My domain:
class User {
Boolean active
}
My view (edit.gsp):
<g:select id="active" name="active" from="${[1,0]}" value="${userInstance?.active}" />
The value saves correctly into the database, but I want the Account Status dropdown to say "Enabled" or "Disabled", instead of "1" or "0" as it does now.
It also should show the current value when the Edit page is loaded. Currently, it always shows the value of "1", even if the user has the value of "0" in the database.
This seems like it would be very easy, but I haven't been able to find any examples of anyone setting their dropdown values in the GSP, and nothing I've tried so far is working. Thanks!
I see two solutions, both in the documentation.
One is to us the keys parameter of the tag:
<g:select id="active" name="active" from="${['Enabled','Disabled']}" keys="${[1,0]}" value="${userInstance?.active}" />
This provides a different list of keys vs the list of values.
The other solution is to use the optionKey and/or optionValue parameters, but this is would would require the list to contain objects or something similar that could be used to look up the values:
In src/groovy/BooleanSelectOption.groovy:
class BooleanSelectOption {
String name
String value
private BooleanSelectOption(name, value) {
this.name = name
this.value = value
}
private static List _list;
public static List getList() {
if(!BooleanSelectOption._list) {
BooleanSelectOption._list = [new BooleanSelectOption('Enabled',1), new BooleanSelectOption('Disabled',2)]
}
BooleanSelectOption._list
}
public String toString() { name }
}
In your view:
<g:select id="active" name="active" from="${BooleanSelectOption.list}" optionKey="value" value="${userInstance?.active}" />
Now the tag is looking up the key based on a bean property of the items in the list. Also, an enum might work here, too.
Obviously the first technique is cleaner for short lists, but I wanted to show both options for more complex situations. I haven't tested the second example, either.
One more note: You will probably find that the keys 0 and 1 don't really work, because Disabled will not get selected (in my experience) if the value is false. I don't know if you can get away with using true and false, but you should test to make sure you are getting what you expect.
There's actually a third option, probably the most robust solution, also in the docs:
Use the valueMessagePrefix parameter to allow the displayed value to be looked up from the i18n messages.
In grails-app/i18n/messages.groovy:
boolean.select.0=Disabled
boolean.select.1=Enabled
In your view:
<g:select id="active" name="active" from="${[1,0]}" value="${userInstance?.active}" valueMessagePrefix="boolean.select" />
This has the additional benefit of allowing you to have different labels for different languages, if you ever need it.
You can use optionKey and optionValue to use an object property or map value for the keys and values. Try something like this:
<g:select name="active" optionKey="key" optionValue="value"
from="${[[key: 1, value: 'Enabled'],[key: 0, value: 'Disabled']]}"/>
Just as an alternative for cases like this when there appears there will be much more processing involved than the task warrants remember you can just fallback to plain old html. E.g.
<select name="active">
<option value="0" ${!active ? 'selected' : ''}>Disabled</option>
<option value="1" ${active ? 'selected' : ''}>Enabled</option>
</select>

asp.net MVC checkbox headache!

I have seen lots of questions relating to this topic.
I am using asp.net MVC 1.0
Problem area
If I use
<%= Html.CheckBox("Status", true) %>
Then why It renders like
<input checked="checked" id="Status" name="Status" type="checkbox" value="true" /><input name="Status" type="hidden" value="false" />
I put this in foreach loop and I have 5 rows.
when I submit form with true,true,true,false,false
then I get true,false,true,false,true,false,false,false
i.e. for false => false.
for true => true,false
If I use
<input type="checkbox" value="true" name="Status" checked="checked" />
Then I don't get unchecked one's.
so how do I overcome form this problem?
Please don't post answer with using loop in formcollection object and checking each key!
I know this isn't the elegant one but this is what I did:
collection["response"].Replace("true,false", "true").Split(',').ToList();
In your example, when you submit form with true,true,true,false,false and you get
true,false,true,false,true,false,false,falseit is interesting to note that you are not actually getting eight values back, but five arrays that merely looks like this is the case because all of the values are joined.
I know you asked to not get a loop for your answer, but I can use one to demonstrate what is really happening here:
foreach (string key in postedForm.AllKeys) {
// "value" will contain a joined/comma-separated list of ALL values,
// including something like "true,false" for a checked checkbox.
string value = postedForm[key].GetValue;
// "values" will be an array, where you can access its first element,
// e.g., values[0], to get the actual intended value.
string[] values = postedForm.GetValues(key);
}
So, for your checked boxes, you'll get a values array with two elements, and for unchecked boxes, you'll get just a single-element array.
Thus, to answer your question how do you overcome this problem, the answer lies in using GetValues rather than GetValue, and thinking of your posted fields as arrays rather than strings.
Best of luck!
Personally I think having to check for "true,false" everywhere on the server is a pain. I wrote a jquery fix that will remove the extra hidden field created by the Html.Checkbox helper when a box is checked, then add it back if the box is unchecked. Server values will always be "true" or "false". Checkbox lists are kind of subjective in how you want them to act, which I discuss, but I'm removing "false" from the value set, which means the form value will be excluded if all boxes in the list are unchecked.
http://www.mindstorminteractive.com/blog/?p=386
I've had pretty good success using this technique. Please let me know if you try it out and have issues.
You'll have to do your own model binding for the CheckBox values.
Get the list of values from the FormCollection or Request.Form for that CheckBox id and replace true,false with true:
string chkBoxString = Request.Form["checkboxID"].Replace("true,false", "true")
Now you have a list of whether a CheckBox was selected or not.... do the rest yourself :)
It renders so because default binder requires the FormCollection to have a value for nonnullable parameters. Using this technique we are sure that the value will be sent even the checkbox is not checked (by default the value sent only when it's checked). If you use this controller method with just one html input you'll get error on form post with unchecked checkbox (value of checkbox will not be posted and binder will not know what to use for value of isItemSelected):
public ActionResult SomeActionMethod(bool isItemSelected)
You can try use something like this with just one html input:
public ActionResult SomeActionMethod(bool? isItemSelected)
But in this case isItemSelected will be null or will be true. And it will never become false.
Well there are couple of ways you can do based on your requirement.
I use this method.
<input type="checkbox" value="<%= item.ID %>" name="check" checked="checked")" />
This is may checkboxes.
On server side I will also have array of ID's of item in the model.
So I check it whether it is in array
var strArray = form["checkbox"]; //Request.form["checkbox"] or "FormCollection form" in action parameter; array of ID's in comma separated string.
Different people have different tests.
this was intended to use for just just simple CheckBox, what you want is checkboxList, which is not yet cover in the API of ASP.net MVC
If you looking for some thing like checkboxlist, maybe you should write your own helper, provide you understand HTML well..
That's it! :)
Easier just to check whether AttemptedValue.Contains("true") - it will, if it's checked, not if it's unchecked....
in the View :
<input id="radio5" type="checkbox" name="rb_abc" value="5"/>
Controller:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult YourForm(FormCollection fc)
{
if (fc["rb_abc"] == "on")
{
//Hey .. you have selected a Radio Button.. just kidding.. its a CheckBox
}
}
To get checkbox value even it is true or false
var boolValue = bool.Parse(collection.GetValues("checkboxID")[0])

Resources