I have a ViewModel which has a property defined as an IList of type Division. The Division object is as follows:
public class DivisionViewModel :
{
public int Id { get; set; }
public int DivisionId { get; set; }
public string Name { get; set; }
}
In the past, I have just created the following in the View:
<div class="divisions">
#for (int i = 0; i < Model.Divisions.Count(); i++)
{
#Html.Hidden(Model.Divisions[i].DivisionId.ToString(), new { #name = "Divisions[" + i + "].DivisionId", #id = "Divisions_" + i + "__.DivisionId" })
#Html.Hidden(Model.Divisions[i].Id.ToString(), new { #name = "Divisions[" + i + "].Id", #id = "Divisions_" + i + "__.Id" })
#Html.Hidden(Model.Divisions[i].Name, new { #name = "Divisions[" + i + "].Name", #id = "Divisions_" + i + "__.Name" })
}
</div>
When I submit the page, on checking the returned ViewModel, I find that the DivisionId is populated, but both the Id and Name property is not.
I have commented out the line that sets the DivisionId, but it is still returned without the Id and Name properties
What have I missed?
The most common cause for model binding failing is that the name attributes of your inputs do not match the names of the properties in your model, and the first thing you should always do is inspect the html your code is actually generating. Assuming the value of the DivisionId in the first item is 1, what you are generating is
<input name="1" type="hidden" value="{ #name = Divisions[0].DivisionId, #id = "Divisions_0__.DivisionId }" />
which has no relationship to your model.
The 1st parameter is the property to bind to, and you are telling it
to bind to a property named 1 - the result of Model.Divisions[i].DivisionId.ToString(), which does not exist, and is illegal
anyway
The 2nd parameter of #Html.Hidden() is the value of the property,
so your telling it to use your anonymous object as the value
Even if you added the 2nd parameter (so that the html attributes
would be generated from the anonymous object), it would still not work since using
#name="..." fortunately does nothing at all - the purpose of using
the HtmlHelper methods is to correctly bind to your model)
Just use
#for (int i = 0; i < Model.Divisions.Count; i++)
{
#Html.HiddenFor(m => m.Divisions[i].DivisionId)
#Html.HiddenFor(m => m.Divisions[i].Id)
#Html.HiddenFor(m => m.Divisions[i].Name)
}
And the correct name and value attributes will be generated for binding to your collection.
Having said that, all you have are hidden inputs, so it makes no sense to generate all that extra html, send it to the browser and send it all back again unchanged. If you need that collection in the POST method, then just get it from your repository.
As a side note, your new { #id = ".." } makes no sense. The purpose of adding an id attribute is for use in css and javascript/jquery selectors, which do not really make sense for collection items, but in any case would not work because you have a . (dot) which would act as a class name selector
Related
What i want to do is have 2 listboxes (left, right) where left would have all products entities except the ones that are present in my Contract Entity and in the right the products in my Contract Entity.
I am having this issue that when i pass 1 parameter (string) I can receive them correctly in my controller but whenever i pass my model with it as a second parameter i loose my model completely. This is what i have:
Controller:
public ActionResult EditContract(ContractViewModel model, string selectedProducts)
View (Javascript/JQuery):
function GetSelectedProducts() {
var listbox = document.getElementById("productsForContractListbox");
var txt = "";
var i;
for (i = 0; i < listbox.length; i++) {
txt = txt + "\n" + listbox.options[i].text;
}
$('#SelectedProductForContracts').val(txt);
var selectedProducts = $('#SelectedProductForContracts').val();
var model = $('form').serialize;
$.post('#Url.Action("EditContract", "Contract")', { "model": model, "selectedProducts": selectedProducts});
}
Html helper listboxes:
// listbox for my contract products
#Html.ListBoxFor(c => c.Contract.Products, productsForContract, new { ID = "productsForContractListbox", #class = "form-control" })
// listbox where all products except the ones in my contract are loaded
#Html.ListBox("allProducts", allProducts, new { ID = "allProductsListbox", #class = "form-control" })
HiddenFor for the SelectedProductForContracts from my Model:
#Html.HiddenFor(c => c.SelectedProductForContracts, new { ID = "SelectedProductForContracts", name = "SelectedProductForContracts" })
Model:
[HiddenInput(DisplayValue = false)]
public List<SelectListItem> SelectedProductForContracts { get; set; }
When having the post data containing only the selectedProducts, i get my values but lose my model, when i add my model i get my model but lose my selectedProducts values.
I tried several things like THIS but couldn't get them to work in my case (i am doing something wrong probably but don't know what ...)
Can anyone help me towards the proper way of achieving this cause i see many ways but i hope there must be an elegant way of binding everything to 1 model without having to use javascript/Jquery ?
Kind regards!
List<SelectListItem> is not able to be defined in a single hidden variable
I'm trying to deal with radiobutton ids in asp MVC 5
I'm working like this
Example model
class A
{
public bool? radio { get; set; }
}
and in razor view
#Html.RadioButtonFor(x => x.NeutroBT, Model.NeutroBT, new { #id = "True"})
#Html.RadioButtonFor(x => x.NeutroBT, !Model.NeutroBT, new { #id = "False})
It doesn't cause problem, but I'm working in an editortemplate, and I want it to have generated id, to access in some way like #Html.IdFor(x => x.NeutroBT, true) and #Html.IdFor(x => x.NeutroBT, false) from the other views, just preventing changes in the future
Is some like this possible? I pass a lot of time searching and I didn't get anything similar
If is not possible, what is the best way to deal with it?
thanks!
There is no need to use an id attribute. Instead you can just use the name attribute to select or set the value via javascript (and in any case, #Html.IdFor() will only ever returnNeutroBT, not theidthat you override in theRadioButtonFor()` method so it cannot be used in your case)
In addition, the 2nd parameter of RadioButtonFor() should be true or false (not Model.NeutroBT and !Model.NeutroBT).
And to associate a label with the button, you can wrap it in a <label>, for example
<label>
#Html.RadioButtonFor(x => x.NeutroBT, true, new { id = ""})
<span>Yes</span>
</label>
<label>
#Html.RadioButtonFor(x => x.NeutroBT, false, new { id = ""})
<span>No</span>
</label>
Note that new { id = "" } removes the id attribute and prevents invalid html due to duplicate id attributes.
Then to access the selected value using jQuery
var selectedValue = $('input[name="' + #Html.NameFor(x => x.NeutroBT) + '"]:checked').val();
I am unable to get an array of a complex type in the model to store its value IsAssigned between two actions in a controller.
Once the model has been composed, which including the array of complex type (which include IsAssigned), the view engine begins to render the HTML/Razor script. In the view, there is a section of the script that handles the displaying of the controls which manipulates the complex type. It is a "For i" loop that cycles through the array of complex type.
During this loop, there is a razor command HiddenFor for property IsAssigned which is contained inside of the complex type array.
A second step is carried out in the loop that renders a partial view. It is in this view where there are two radio buttons linked to an array position's IsAssigned boolean? property. If the user select yes the property turns to true, and no false.
After the view has been completely rendered and the user has made their selections, the next action method in the controller is activated and the model is passed to it. It is here where I expect the user choices against the IsAssigned property to persist, but no. It does not.
Below is the code for looping and the code for the partial view.
Loop
#for (int i = 0; i < Model.KitTypeAssignment.Length; i++)
{
#Html.HiddenFor(m => m.KitTypeAssignment[i].IsKitTypeActiveForSubject)
#Html.HiddenFor(m => m.KitTypeAssignment[i].KitType.Code)
#Html.HiddenFor(m => m.KitTypeAssignment[i].KitType.Description)
#Html.HiddenFor(m => m.KitTypeAssignment[i].KitType.KitTypeId)
#Html.HiddenFor(m => m.KitTypeAssignment[i].IsAssigned, new { #Name = "IsAssigned" + Model.KitTypeAssignment[i].KitType.Code })
#Html.Partial("_KitTypeDispensing", Model.KitTypeAssignment[i])
}
PartialView
<div class="field inline spacer20">
#{
var kitTypeAssign = String.Format("{0}{1}", Model.KitType.Code, "Assign");
var identityOfAssigned = new { id = #kitTypeAssign, #Name = "IsAssigned" + Model.KitType.Code };
var kitTypeNoAssign = String.Format("{0}{1}", Model.KitType.Code, "NoAssign");
var identityOfNoAssigned = new { id = #kitTypeNoAssign, #Name = "IsAssigned" + Model.KitType.Code };
var sectionTitle = string.Format("{0} {1}", PromptText.Assign_185B7133DB22230701A857C059360CC2.ToLocalizableString(), Model.KitType.Description);
#Html.Label(sectionTitle, new { #class = "colon" })
#Html.RadioButtonFor(m => m.IsAssigned, true, #identityOfAssigned)
<label for=#kitTypeAssign>#Html.PromptFor(PromptText.Yes_93CBA07454F06A4A960172BBD6E2A435)</label>
#Html.RadioButtonFor(m => m.IsAssigned, false, #identityOfNoAssigned)
<label for=#kitTypeNoAssign>#Html.PromptFor(PromptText.No_BAFD7322C6E97D25B6299B5D6FE8920B)</label>
}
</div>
I think the problem is that the partial view does not prefix the names of its HTML input elements correctly, therefore the ModelBinder does not know how to bind them back to your array. Try to pass the correct prefix explicitly to the partial:
#Html.Partial(
"_KitTypeDispensing",
Model.KitTypeAssignment[i],
new ViewDataDictionary { TemplateInfo = new TemplateInfo {
HtmlFieldPrefix = Html.NameFor(m => m.KitTypeAssignment[i]).ToString() }
}
)
Further reading: ASP.NET MVC Partial Views with Partial Models
I used the following tutorial:
http://msdn.microsoft.com/en-us/library/gg508808%28VS.98%29.aspx
And everything seemed fine, but in my case, string Username always comes back null. After tonnes of research, I found everyone discovered BIND Prefixes. That would be great in many circumstances, but not this one. I should note all properties and names line up, however in my for loop, the EditorFor creates a [i].Username field and this doesn't map to any model property.
QUESTION: I think I want to map [i].Username to Username where i is any number from 0-infinity, so when it GETS, the value is passed to the Action properly. How do I do this? If this is wrong, what do I do validate this for a specific row in a table?
#for (var i = 0; i < Model.Count; i++)
{
BLAH BLAH BLAH CODE FOR BUILDING TABLE ROWS
<td>
#Html.EditorFor(modelItem => Model[i].Username)
</td>
}
Since I could technically have HUNDREDS if not THOUSANDS of records, I would rather not had a binding PREFIX for all 1000. Am I fundamentally missing something here? I am new to ASP.NET MVC and am used to WebForms so I feel like sometimes I am mixing concepts and mashing up something that is entirely wrong.
EDIT:
I fixed it by doing the following, but not sure if this is the best idea. I set the parameter equal to the FieldName without [i] prefix, but still retrieve the element with the [i] prefix. Javascript isn't my Forte so please let me know if it is horrible.
adapters.add("remote", ["url", "type", "additionalfields"], function (options) {
var value = {
url: options.params.url,
type: options.params.type || "GET",
data: {}
},
prefix = getModelPrefix(options.element.name);
$.each(splitAndTrim(options.params.additionalfields || options.element.name), function (i, fieldName) {
var paramName = fieldName.substr(fieldName.lastIndexOf(".") + 1);
var actualFieldName = appendModelPrefix(fieldName, prefix)
value.data[paramName] = function () {
return $(options.form).find(":input").filter("[name='" + escapeAttributeValue(actualFieldName) + "']").val();
};
});
setValidationValues(options, "remote", value);
});
You have not posted your code for the model or controller, but assuming you have a RemoteAttribute applied to property Username, for example
public class MyModel
{
[Remote("IsValidUserName", "Person")]
public string Username { get; set; }
}
with a method in PersonController
public JsonResult IsValidUserName(string Username)
{
....
}
and the view
#model List<Person>
...
#for (var i = 0; i < Model.Count; i++)
{
#Html.EditorFor(m => m[i].Username)
}
This will generate html such as
<input name="[0].UserName" ... />
<input name="[1].UserName" ... />
Unfortunately the remote method in jquery-validate posts back the name and value of the element so that the ajax call looks like
$.ajax({
url: '/Person/IsValidUserName',
data: { [0].UserName: 'someone#somewhere.com' },
...
which will not bind.
I have reported this as an issue at Codeplex with a possible solution. In the meantime you can modify the remote method in jquery-validate.js file as follows
remote: function(value, element, param) {
....
var data = {};
// data[element.name] = value;
data[element.name.substr(element.name.lastIndexOf(".") + 1)] = value; // add this
This will strip the prefix so that the posted data is
data: { UserName: 'someone#somewhere.com' },
and will correctly bind to the method.
Assuming the code is formatted in the following way:
View:
#for(var i = 0; i<Model.Count; i++) {
<div class="row">
#Html.EditorFor(modelItem => Model[i].Username)
</div>
}
<style>
.valid{
background: lime;
}
</style>
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
Model:
public class MyModel {
[Remote("IsValidUserName", "Validation", HttpMethod = "POST")]
public string Username { get; set; }
}
It is possible to use the automatic modelbinding to bind to the remote validation. If you were to use a list or array for this, the binding would fail while a Dictionary can catch this error.
Be aware however that the Key in the dictionary will be consistent with the id in the view (e.g. [5].Username will map to {Key: 5, Value: MyModel{Username:...}}) and won't be a default 0, hence the use of a Linq query.
Controller:
[HttpPost]
public JsonResult IsValidUserName(Dictionary<int,MyModel> Users) {
return Json(Users.First().Value.Username.Contains("User"));
}
in an ASP.NET-MVC 5 application I have the following models
class Employee {
int EmployeeID {get;set;}
string FirstName {get;set;}
List<OfficeLocations> OfficeLocations {get;set;}
}
class OfficeLocations {
int OfficeLocationsID {get;set;}
//foreign key
int EmployeeID {get;set;}
string Value1 {get;set;}
string Value2 {get;set;}
}
I have an edit view for modifying or ADDING different office locations that an employee could belong to. It looks something like this:
#model List<Project.Models.OfficeLocations>
#for (int i = 0; i < Model.Count; i++) {
#Html.EditorFor(m => m[i].CitLocation, new { htmlAttributes = new { #class = "my_editor" } })
#Html.HiddenFor(m => m[i].OfficeLocationsID)
#Html.HiddenFor(m => m[i].EmployeeID)
}
//extra editor box for adding a new value
#Html.Editorfor(??????.Value)
I'm a little confused as to how to add new entries to my model (list) in the database table. What do I put in the parameter for the extra Editorfor box (where all the ???? are)
also, what would the controller action method look like?
change your viewmodel to have an officelocation and a list of officelocation... With that you can add the non list officelocation object in you extra editor box... Or you can just retain your viemodel like that and just manually create a model using jquery and pass it using an ajax jquery...
To fix this issue I came up with the following javascript:
<script>
$(document).ready(function () {
index = 0;
});
$('#add_button').click(function () {
var placeHolderNameAttribute = "List2[#].Value1";
var indexedNameAttribute = "List2[" + index + "].Value1";
var placeHolderIdAttribute = "new_lastName_input";
var indexedIdAttribute = "new_lastName_input" + index;
document.getElementById(placeHolderIdAttribute).name = indexedNameAttribute;
document.getElementById(placeHolderIdAttribute).id = indexedIdAttribute;
var clone1 = $("#new_lastName_input" + index).clone();
document.getElementById(indexedIdAttribute).name = placeHolderNameAttribute;
document.getElementById(indexedIdAttribute).id = placeHolderIdAttribute;
if (index == 0) {
$('#nlnPlaceHolder').remove();
}
$('#LN_editor_box').append(clone1);
index += 1;
});
</script>
and the following placeholder input field
<input id="new_lastName_input" class="my_editor" type="text" name="List2[#].Value1" value="New Last Name" />
and now my controller post method accepts two parameters, the original list of updated/edited values, and a new list of only new values.
ActionResult myMethod(List<OfficeLocations> list1, OfficeLocations[] list2)
and if the value is in list1 then it will update in the database, and if it's in list2 it will be added