Binding to a Dictionary<string, string> where the Key contains square brackets - asp.net-mvc

I am trying to bind items that belong to a Dictionary and it all works fine, until I run into KeyValuePairs where the Key contains square brackets. For example, imagine this contrived scenario:
model = new MyModel {
MyDict = new Dictionary<string, string> {
{ "Apple[1]", "Green" }
}
}
I then create a form as follows:
Html.EditorFor(model => model.MyDict["Apple[1]"])
As expected, the field looks like this:
<input id="MyDict_Apple_1__" name="MyDict[Apple[1]]"> type="text" value="Green">
The problem is that when the form is submitted, the ModelBinder drops the last bracket: ModelBindingContext.ModelName is "MyDict[Apple[1]".
Is this a bug in the DefaultModelBinder, or am I missing something?
Update:
I found that there's a bug in how FormValueProvider reads keys from the data. Specifically, the bug is in the GetKeyFromEmptyPrefix method of Microsoft.AspNetCore.Mvc.Internal.PrefixContainer.
For now I'm working around the issue by using the old binding syntax, with separate fields for the Key and Value:
<input type="hidden" name="MyDict[0].Key" value="Apple[1]" />
<input type="text" name="MyDict[0].Value" value="Green" />

Related

New to React: Why is one array treated differently than the other?

I'm working on a React app that is fed data from a Rails api. I'm currently working on a form that includes a nested association (i.e. in the model_a has many model_b's and you can create them in the same form).
The problem I'm having is that Rails expects nested association with a certain naming convention and the same field that controls how the parameter is named when its sent to rails also controls how React finds the right data when the Rails API responds.
This becomes problematic on the edit page because I want to show the models_a's (Retailers) already existing model_b's (SpendingThresholds in this case) and when I change the 'name' field to suit the rails side, React doesn't know where to look for that data anymore. When I try to pass the data directly it comes in as a different type of array and certain functions fail.
I think its easier to show than tell here so
initially I had this
<FieldArray
name="spending_thresholds"
component={renderSpendingThresholds}
/>
and data was coming through like
Object {_isFieldArray: true, forEach: function, get: function, getAll: function, insert: function…
to my React app from the Rails API, which worked, however that 'name' isn't to Rails liking (Rails wants it to be called 'spending_thresholds_attributes' for accepts_nested_attributes to work) so I changed it to
<FieldArray
name="spending_thresholds_attributes"
fields={this.props.retailer.spending_thresholds}
component={renderSpendingThresholds}
/>
and data start coming through to the renderSpendingThresholds component in this format
[Object]
0:Object
length:1
__proto__:Array(0)
which React doesn't like for some reason.
Anyone know how to fix this/why those two objects, which hold the same information from the Rails side anyway, are being treated differently?
EDITS
renderSpendingThresholds component
The fields attribute in the renderSpendingThresholds component is the object that's coming through differently depending on how I input it
const renderSpendingThresholds = ({ fields }) => (
<ul className="spending-thresholds">
<li>
<Button size="sm" color="secondary" onClick={(e) => {
fields.push({});
e.preventDefault();
}
}>
Add Spending Threshold
</Button>
</li>
{fields.map((spending_threshold, index) => (
<li key={index}>
<h4>Spending Threshold #{index + 1}</h4>
<Button
size="sm"
color="danger"
title="Remove Spending Threshold"
onClick={() => fields.remove(index)}
>
Remove
</Button>
<Field
name={`${spending_threshold}.spend_amount`}
type="number"
component={renderField}
label="Spend Amount"
placeholder="0"
/>
<Field
name={`${spending_threshold}.bonus_credits`}
type="number"
component={renderField}
label="Bonus Credits"
placeholder="0"
/>
</li>
))}
</ul>
);
It looks like you are passing fields through props and then destructuring the fields out of the props in the callback of the renderSpendingThresholds and discarding the rest. According to the docs, a specific redux-form object is passed through to the render callback. You're essentially overwriting this. Try changing {field} to something like member or spending_threshold. Then you can use the specific map function to iterate over the spending_threshold items. Your field prop should still be available under member.fields or something similar.
For the code that you currently show, who exactly handles the submission?
you use the original flow of form submit?
if so, so please handle that by yourself.
** this line of code, looks weird:
onClick={() => fields.remove(index)}
as you interact directly with the state values...
you need to update the state through
this.setState({fields: FIELDS_WITHOUT_ITEM})
and now when you need to handle your own submission, you don't really care of the input names. Because you are using the state as input.
ie:
class FormSpending extends Component {
handleSubmit() {
var fieldsData = this.state.fields.map(field => {
return {
whateverkey: field.dontcare,
otherKey: field.anotherDontCare
};
});
var formData = {
fields: fieldsData
};
ajaxLibrary.post(URL_HERE, formData).....
}
render() {
return (
...
<form onSubmit={()=>this.handleSubmit()}>
...
</form>
...
);
}
}

Checkboxes generated via CheckBoxFor helper turn into type=hidden because of MaterializeCSS

I'm creating a website with ASP.NET MVC5 and I'm using MaterializeCSS for the first time, which looks like a very exciting framework.
However, the checkboxes generated by CheckBoxFor helper become hidden !
When I write :
#Html.CheckBoxFor(m => m.IsAgreeTerms)
The generated HTML is :
<input name="IsAgreeTerms" type="hidden" value="false">
Why does Materialize change my type=checkbox into type=hidden ?
I tried to add type="checkbox" in the CheckboxFor helper, but it doesnt change anything. The only way is to modify in in my browser's console.
The only solution I found is this SO thread.
However, the accepted answer doesn't change anything for me.
The other answer works, but I think it's ugly to add some JS script to modify what Materialize modifies without my consent.
Is there any way to say "Hey, I ask for a type=checkbox, so just let my type=checkbox in the generated HTML" ?
Thank you
UPDATE :
My full ASP.NET MVC code is :
#Html.CheckBoxFor(m => m.IsAgreeTerms, new { #type = "checkbox" })
#Html.LabelFor(m => m.IsAgreeTerms, new { #class = "login-label" })
The full generated HTML is
<input data-val="true" data-val-required="Le champ IsAgreeTerms est requis." id="IsAgreeTerms" name="IsAgreeTerms" type="checkbox" value="true"
<input name="IsAgreeTerms" type="hidden" value="false">
<label class="login-label" for="IsAgreeTerms">IsAgreeTerms</label>
Here's a solution in the form of a html helper. It constructs a checkbox and label in the correct order:
public static IHtmlString CheckBoxWithLabelFor<TModel>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, bool>> expression,
string labelText,
object htmlAttributes = null
)
{
if (expression == null)
{
throw new ArgumentNullException(nameof(expression));
}
var checkBoxWithHidden = htmlHelper.CheckBoxFor(expression, htmlAttributes).ToHtmlString().Trim();
var pureCheckBox = checkBoxWithHidden.Substring(0, checkBoxWithHidden.IndexOf("<input", 1, StringComparison.Ordinal));
var labelHtml = htmlHelper.LabelFor(expression, labelText).ToHtmlString().Trim();
var result = pureCheckBox + Environment.NewLine + labelHtml + Environment.NewLine + $"<input type=\"hidden\" name=\"{ExpressionHelper.GetExpressionText(expression)}\" value=\"false\" />";
return new MvcHtmlString(result);
}
Is there other html generated by materialize.css? I think this happens because it is not possible apply a custom CSS to element input of type checkbox.
So, the checkbox becomes hidden and other html component represents visually the checkbox. Many components work like that.
UPDATE:
Why is Html checkbox generating an additional hidden input
OP here. Problem looks like more complex.
Actually, when using #Html.CheckBoxFor, MVC5 generates 3 fields, in that order :
Your input, with type="checkbox", binded to your model property
An hidden field (see the Claudio's link for an explaination)
Your label, generated by #Html.LabelFor
Problem is Materialize expects that in another order to work.
In your browser's console, just move the <label> element between the input and the hidden field, and everything works fine !
I found this very useful link, where, basically, it is said that the order of the generated fields by #Html.checkBoxFor will change ... In MVC6 !
As I'm working with MVC5, I use this very ugly solution in my _Layout:
$(":checkbox").each(function () {
$(this).nextAll("label").before($(this))
})
If anyone has a better idea, please feel free to post an elegant solution.

data being passed from controller, is it encoded?

I want to ask in MVC3+; data passed from controller in form of modle or viewbag is the data encoded by default or I do have to ?
Do you mean HTML encoded? They're not, no.
This means that if you have a string property that contains the value "<b>some text</b>" then the property contents is exactly that.
If, however, you try to print this output to the View with #MyProperty then, by default, the string will be HTML-encoded by MVC. So the output would become <some text>.
You can escape this by using #Html.Raw(MyProperty).
Remember that the term 'encoded' is a bit vague. Try to be specific with what encoding you're referring to.
MVC proactively tries to encode values to prevent cross-site scripting.
#* This is HTML Encoded *#
#Model.Value
#* This value is not encoded *#
#Html.Raw(Model.Value)
#* The URL gets URL Encoded *#
<a href="#Url" />
This Razor code
# {
bool disabled = false;
bool readonly = true;
string className = null;
}
<input type="text" disabled="#disabled" readonly="#readonly" class="#className" />
Actually produces this output
<input type="text" readonly="readonly" />
A value of null or false causes the Razor view engine to not render the attribute at all.
If you did want something like this data-soldout="false" then you need to do:
data-soldout="#Html.Raw(isSoldout)" or data-soldout="#isSoldout.ToString()"

how to pass params using action button in grails

been having toruble with the button that has action. I have several btns which I want to know its paramaeter. In grails tutorial it says it should be like this:
<g:actionSubmit action="action" value="${message(code: 'default.button.edit.label', default: 'Edit')}" params="['actionTaken':editPhone]"/>
I tried using remotelink, submitButton, submitToRemote tags but none works. I always get null when I try parsing it in my controller:
def action=
{
def actionTaken = params.actionTaken
def employeeId= params.employeeId
MySession session = MySession.getMySession(request, params.employeeId)
profileInstance = session.profileInstance
switch(actionTaken)
{
case "editPhone" :
isEditPhone=true
break
case "editEmail" :
isEditEmail=true
break
}
render(view:"profile", model:[profileInstance:session.profileInstance, isEditPhone:isEditPhone, isEditEmail:isEditEmail])
}
What am I missing? is my params code wrong? Is my code in parsing params wrong? this just gets me in circles with no progress. help. thanks.
The Grails documentation doesn't list params as one of the attributes accepted by actionSubmit.
It is possible to inject the value you want in your params list in the controller by exploiting what that tag actually does:
def editPhone = { forward(action:'action', params:[actionTaken: 'editPhone'])}
def editEmail = { forward(action:'action', params:[actionTaken: 'editEmail'])}
You may also just want to just code completely separate logic into the editPhone and editEmail actions if that makes your code cleaner.
Updated View Code:
<g:actionSubmit action="editPhone" value="${message(code: 'default.button.edit.label', default: 'Edit')}" />
The params attribute is parsed as a map, where the keys are treated a strings but the values are treated as expressions. Therefore
params="['actionTaken':editPhone]"
is trying to define the key named actionTaken to have the value from the variable named editPhone in the GSP model. Since there is no such variable you're getting null. So the fix is to move the quotes:
params="[actionTaken:'editPhone']"
which will set the value to the string "editPhone".
You could also pass the parameter inside the POST-data using
<input type="hidden" name="actionTaken" value="editPhone" />
inside the form. Then it is also accessible through the params variable.
It works for me.
I just had a similar problem (I needed to submit a delete together with an id) and found a solution using HTML5's "formaction" attribute for submit-inputs.
They can be given a value that can include a controller, action, additional parameters, etc.
In General, to add a parameter to a submit button such as a edit of a specific sub-object on a form would looks like this:
<input type="submit" formaction="/<controller>/<action>/<id>?additionalParam1=...&additionalParam2=..." value="Action" >
and in your example:
<input type="submit" formaction="action?actionTaken=editPhone" value="${message(code: 'default.button.edit.label', default: 'Edit')}" >
In my situation, I had a single form element with multiple actions on each row of a table (i.e. data table edit buttons). It was important to send the entire form data so I couldn't just use links. The quickest way I found to inject a parameter was with javascript. Not ideal, but it works:
function injectHiddenField(name, value) {
var control = $("input[type=hidden][name='" + name + "']");
if (control.size() == 0) {
console.log(name + " not found; adding...");
control = $("<input type=\"hidden\" id=\"" + name + "\" name=\"" + name + "\">");
$("form").append(control);
}
control.val(value);
}
Your button can look like this:
<g:each in="${objects}" var="object">
...
<g:actionSubmit value="edit" action="anotherAction"
onclick="injectHiddenField('myfield', ${object.id})"/>
</g:each>

ASP.NET MVC: Tri-state checkbox

I'm just now starting to learn ASP.NET MVC. How would I go about creating a reusable tri-state checbox? In WebForms this would be a control, but I don't know the MVC equivalent.
Add a TriStateCheckBox (or TriStateCheckBoxFor if you use the strongly typed overloads) extension method to HtmlHelper and add the namespace of that extension method class to the namespaces section of your web.config.
As for the implementation, I'd recommend having at look at the InputExtensions source on codeplex and using that to create your own.
Limitations:
View Rendering - When rendering HTML content, there is no attribute you can possibly place on an <input type="checkbox" /> that will give it the property indeterminate.
At some point, you'll have to use JavaScript to grab the element and set the indeterminate property:
// vanilla js
document.getElementById("myChk").indeterminate = true;
// jQuery
$("#myCheck).prop("indeterminate", true);
Form Data - model binding will always be limited to what values are actually sent in the request, either from the url or the data payload (on a POST).
In this simplified example, both unchecked and indeterminate checkboxes are treated identically:
And you can confirm that for yourself in this Stack Snippet:
label {
display: block;
margin-bottom: 3px;
}
<form action="#" method="post">
<label >
<input type="checkbox" name="chkEmpty">
Checkbox
</label>
<label >
<input type="checkbox" name="chkChecked" checked>
Checkbox with Checked
</label>
<label >
<input type="checkbox" name="chkIndeterminate" id="chkIndeterminate">
<script> document.getElementById("chkIndeterminate").indeterminate = true; </script>
Checkbox with Indeterminate
</label>
<label >
<input name="RegularBool" type="checkbox" value="true">
<input name="RegularBool" type="hidden" value="false">
RegularBool
</label>
<input type="submit" value="submit"/>
</form>
Model Binding - Further, model binding will only occur on properties that are actually sent. This actually poses a problem even for regular checkboxes, since they won't post a value when unchecked. Value types do always have a default value, however, if that's the only property in your model, MVC won't new up an entire class if it doesn't see any properties.
ASP.NET solves this problem by emitting two inputs per checkbox:
Note: The hidden input guarantees that a 'false' value will be sent even when the checkbox is not checked. When the checkbox is checked, HTTP is allowed to submit multiple values with the same name, but ASP.NET MVC will only take the first instance, so it will return true like we'd expect.
Render Only Solution
We can render a checkbox for a nullable boolean, however this really only works to guarantee a bool by converting null → false when rendering. It is still difficult to share the indeterminate state across server and client. If you don't need to ever post back indeterminate, this is probably the cleanest / easiest implementation.
Roundtrip Solution
As there are serious limitations to using a HTML checkbox to capture and post all 3 visible states, let's separate out the view of the control (checkbox) with the tri-state values that we want to persist, and then keep them synchronized via JavsScript. Since we already need JS anyway, this isn't really increasing our dependency chain.
Start with an Enum that will hold our value:
/// <summary> Specifies the state of a control, such as a check box, that can be checked, unchecked, or set to an indeterminate state.</summary>
/// <remarks> Adapted from System.Windows.Forms.CheckState, but duplicated to remove dependency on Forms.dll</remarks>
public enum CheckState
{
Checked,
Indeterminate,
Unchecked
}
Then add the following property to your Model instead of a boolean:
public CheckState OpenTasks { get; set; }
Then create an EditorTemplate for the property that will render the actual property we want to persist inside of a hidden input PLUS a checkbox control that we'll use to update that property
Views/Shared/EditorTemplates/CheckState.cshtml:
#model CheckState
#Html.HiddenFor(model => model, new { #class = "tri-state-hidden" })
#Html.CheckBox(name: "",
isChecked: (Model == CheckState.Checked),
htmlAttributes: new { #class = "tri-state-box" })
Note: We're using the same hack as ASP.NET MVC to submit two fields with the same name, and placing the HiddenFor value that we want to persist first so it wins. This just makes it easy to traverse the DOM and find the corresponding value, but you could use different names to prevent any possible overlap.
Then, in your view, you can render both the property + checkbox using the editor template the same way you would have used a checkbox, since it renders both. So just add this to your view:
#Html.EditorFor(model => model.OpenTasks)
The finally piece is to keep them synchronized via JavaScript on load and whenever the checkbox changes like this:
// on load, set indeterminate
$(".tri-state-hidden").each(function() {
var isIndeterminate = this.value === "#CheckState.Indeterminate";
if (isIndeterminate) {
var $box = $(".tri-state-box[name='" + this.name + "'][type='checkbox']");
$box.prop("indeterminate", true);
}
});
// on change, keep synchronized
$(".tri-state-box").change(function () {
var newValue = this.indeterminate ? "#CheckState.Indeterminate"
: this.checked ? "#CheckState.Checked"
: "#CheckState.Unchecked";
var $hidden = $(".tri-state-hidden[name='" + this.name + "'][type='hidden']");
$hidden.val(newValue);
});
Then you can use however you'd like in your business model. For example, if you wanted to map to a nullable boolean, you could use the CheckState property as a backing value and expose/modify via getters/setters in a bool? like this:
public bool? OpenTasksBool
{
get
{
if (OpenTasks == CheckState.Indeterminate) return null;
return OpenTasks == CheckState.Checked;
}
set
{
switch (value)
{
case null: OpenTasks = CheckState.Indeterminate; break;
case true: OpenTasks = CheckState.Checked; break;
case false: OpenTasks = CheckState.Unchecked; break;
}
}
}
Alternative Solution
Also, depending on your domain model, you could just use Yes, No, ⁿ/ₐ radio buttons
ASP.NET MVC certainly doesn't provide such component, actually it simply relies on the standard elements available in HTML but you may want to check out this solution.

Resources