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"));
}
Related
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
I have some strings and I would like to get the Index Number out from them.
Here is the example.
var x = "FundList[10].Amount";
var y = "FundList[11].Amount";
var z = "FundList[15].Amount";
I simply want to get the 10,11,15 and put them into an array of Int.
Currently, I am using a stupid way, which is to replace "FundList[", "]" and ".Amout" with "".
I am wondering if there is a better way to do so.
Update
Here are some clarifications. Here is my code.
This is my part of my PartialView.
#{
var txtIndexName = "FundList[" + Model.Index + "].Index";
var txtAmountName = "FundList[" + Model.Index + "].Amount";
var dropFiscalYearName = "FundList[" + Model.Index + "].FiscalYear";
}
Amount: <input type="text" name="#txtAmountName" id="#txtAmountName" />
Here is the JavaScript to call the PartialView. Each time when user click on a anchor link, the PartialView will be called.
function LoadContractOptionFundingPlanBlockByIndexNumber(indexNumber) {
$.ajax({
type: "POST",
url: "/Json/LoadContractOptionFundingPlanPartialView",
data: JSON.stringify({ index: indexNumber }),
contentType: "application/json; charset=utf-8"
}).success(function (result) {
$("#ContractOptionFundingPlanBlock").append(result);
});
}
function GenerateOptionFundingPlanBlock() {
$("#lnkAddFundingBlock").click(function () {
LoadContractOptionFundingPlanBlockByIndexNumber(currentIndexNumber);
currentIndexNumber++;
});
}
$(document).ready(function () {
GenerateOptionFundingPlanBlock();
});
var currentIndexNumber = 10;
Here is my View:
<form action="#" method="post" name="formCreateContracOption" id="formCreateContracOption">
#Html.AntiForgeryToken()
#Html.LabelForRequired(x=>x.ThisContractOption.OptionName)
#Html.ValidationMessageFor(x=>x.ThisContractOption.OptionName)
#Html.TextBoxFor(x=>x.ThisContractOption.OptionName) <br/><br/>
Period of Performance
#Html.TextBoxFor(x=>x.ThisContractOption.OptionStartDate)
#Html.TextBoxFor(x=>x.ThisContractOption.OptionEndDate) <br /><br />
<a id="lnkAddFundingBlock" href="#">Add Funding Plan</a> <br/><br/>
<div id="ContractOptionFundingPlanBlock"></div>
<input type="submit" id="btnCreateContractOption" name="btnCreateContractOption" value="Submit" />
</form>
After all, when user clicks on the Submit button, the whole thing will be posted to the controller.
Here is my Controller.
[HttpPost]
public ActionResult CreateContractOption(int contractId, ContractOptionCreateEditViewModel viewModel, FormCollection form)
{
var fundList = new List<OptionFundingPlanObject>();
var allOptionAmountKeyList = form.AllKeys.Where(x => x.Contains("FundList") && x.Contains("Index")).ToList();
var indexNumberList = new List<int>();
foreach(var thisKey in allOptionAmountKeyList)
{
var convertedIndex = Convert.ToInt32(Regex.Match(thisKey, #"\d+").Value);
indexNumberList.Add(convertedIndex);
}
return View();
}
The reason I am asking is because it is not simply a How to Post a List to the controller question.
When the List starts with a ZERO index, and every other index is in a sequence, it is pretty easy to handle.
In my case, user may generate a new Option, by calling my Partial View. User will have the ability to remove the generated option, and create a new one. The index then changed. In this case, I have to find another way to solve the problem.
var x = "FundList[10].Amount";
int xIndex = Convert.ToInt32(Regex.Match(x,#"\d+").Value); //10
This is a werid question though. What are you doing? :)
If you are always going to have the strings in the form you provided you can split it on the brackets and get the item from the returned string array:
var x = "FundList[10].Amount";
var num = x.Split('[', ']')[1];
int res = Convert.ToInt32(num);
As its fixed as FundList simply:
int i = int.Parse(x.Substring(9, x.IndexOf(']') - 9));
Maybe something like: Online Demo
using System;
using System.Text.RegularExpressions;
public class Program
{
public static void Main()
{
string text = "FundList[15].Amount";
var result = Convert.ToInt32(Regex.Replace(text, #"[^\d]+",""));
Console.WriteLine("Result is = {0}", result);
}
}
If you prefer LINQ syntax to Regex:
var x = "FundList[10].Amount";
int n = Int32.Parse(new string(x.Where(c => Char.IsDigit(c)).ToArray())); //10
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
I am using the Telerik MVC Grid with Ajax binding, and am having a problem passing an object to the controller to be used to filter data. I am able to pass simple data (string, int), but not a more complex object.
For instance, I can to this no problem:
.DataBinding(dataBinding => dataBinding.Ajax().Select("_CasesAjaxBinding", "Home", new {orderId = "12345"} ))
And then in my controller, handle the orderId like this:
public ActionResult _CasesAjaxBinding(string orderId)
The problem I am having is when I try to pass a more complex object (in this case the #Model) to the controller, like this (The #Model is type CaseFilterModel):
.DataBinding(dataBinding => dataBinding.Ajax().Select("_CasesAjaxBinding", "Home", new {filterSpec = #Model} ))
And then trying to handle the object, like this:
public ActionResult _CasesAjaxBinding(CaseFilterModel filterSpec)
The filterSpec parameter is always null.
Thanks in advance for any ideas!
As far as I can find on the Telerik forums this cannot be done this way. There was a similar question on there, which described exactly the same problem. When passing the model it was always null in the controller's action method.
However there is a workaround if you want to pass multiple parameters to the select method in order to filter the data, but it requires some client side coding.
I'll include a summary of that work around here, so that the answer is complete. A solemn link doesn't say much.
Assume we have a grid which displays orders items (articles) from all the orders. First make sure to hook up the client side onDataBinding event:
<%= Html.Telerik().Grid<Order>()
.Name("Grid")
.ClientEvents(events => events.OnDataBinding("onDataBinding"))
.DataBinding(dataBinding => dataBinding.Ajax()
.Select("_AjaxBinding", "Grid"))
%>
In the client side event handler you need to compose your select URL. Here I'll pass two parameters, an order id (int) and a description of an article (string).
<script type="text/javascript">
function onDataBinding(e) {
var orderId = 100;
var searchText = "test";
var params = { OrderId: orderId, ArticleDescription: searchText };
var paramsStr = $.param(params);
var selectUrl = "<%= #Url.Action("_AjaxFilterBinding", "Grid") %>"
+ "?" + paramsStr;
var grid = $('#Grid').data('tGrid');
grid.ajax.selectUrl = selectUrl;
}
</script>
Then in your controller you can declare the select method as follows:
[GridAction]
public ActionResult _AjaxFilterBinding(AjaxFilterBindingModel model)
{
// Retrieve data here and filter it based upon the data present in the model.
var data = ...;
return View(new GridModel<Order> { Data = data });
}
The model looks like:
public class AjaxFilterBindingModel
{
public int OrderId { get; set; }
public string ArticleDescription { get; set; }
}
Passing a collection via the URL (GET) is also possible. Let's assume you want a collection of order IDs instead of just one.
The model would look like this:
public class AjaxFilterBindingModel
{
public IEnumerable<int> OrderIds { get; set; }
public string ArticleDescription { get; set; }
}
And the JavaScript would look like this:
function onDataBinding(e) {
jQuery.ajaxSettings.traditional = true;
var intArray = [1, 2, 3, 4, 5];
var params = { OrderIds: intArray, ArticleDescription: "Test" };
var paramsStr = $.param(params);
var selectUrl = "<%= #Url.Action("_AjaxFilterBinding", "Home") %>" + "?"
+ paramsStr;
var grid = $('#Grid').data('tGrid');
grid.ajax.selectUrl = selectUrl;
}
Remark: Don't forget to set "jQuery.ajaxSettings.traditional = true;" or the parameters will be serialized incorrectly and the ASP.NET MVC model binder will not be able to bind the integer array.
And to be complete here is the Telerik forum thread I mentioned:
http://www.telerik.com/community/forums/aspnet-mvc/grid/getting-the-model-object-in-a-custom-binding-grid-ajax-controller.aspx
And the suggested work around:
http://www.telerik.com/community/forums/aspnet-mvc/grid/code-sample-sending-additional-filters-with-ajax-binding.aspx
Also as a note, this solution is not in Razor syntax... took me forever to spot it but the line
var selectUrl = "<%= #Url.Action("_AjaxFilterBinding", "Home") %>" + "?" + paramsStr;
should be changed to
var selectUrl = "#Url.Action("_AjaxFilterBinding", "Home")" + "?" + paramsStr;
For Razor... I copied the code but couldn't figure out why my _AjaxFilterBinding was not being called. Just thought I'd point it out in case anyone else has this problem.
Thanks for the solution, it NOW works great :)
I' still newby to this so I'll try to explain what I'm doing. Basically what I want is to load a dropdownlist depending on the value of a previous one, and I want it to load the data and appear when the other one is changed. This is the code I've written in my controller:
public ActionResult GetClassesSearch(bool ajax, string phylumID, string kingdom){
IList<TaxClass> lists = null;
int _phylumID = int.Parse(phylumID);
int _kingdom = int.Parse(kingdom);
lists = _taxon.getClassByPhylumSearch(_phylumID, _kingdom);
return Json(lists.count);
}
and this is how I call the method from the javascript function:
function loadClasses(_phylum) {
var phylum = _phylum.value;
$.getJSON("/Suspension/GetClassesSearch/",
{ ajax: true,
phylumID: phylum,
kingdom: kingdom
},
function(data) {
alert(data);
alert('no fallo')
document.getElementById("pClass").style.display = "block";
document.getElementById("sClass").options[0] = new Option("-select-", "0", true, true);
//for (i = 0; i < data.length; i++) {
// $('#sClass').addOption(data[i].classID, data[i].className);
//}
});
}
The HTML associated is:
<p id="pPhylum">
<%= Html.Label("Phylum: ") %>
<%= Html.DropDownList("sPhylum",
(SelectList)ViewData["PhyRecID"],
"--Select One--",
new { onchange = "loadClasses(this);" }
)%>
</p>
<p id="pClass">
<%= Html.Label("Class: ") %> <select id="sClass"></select>
</p>
The thing is that just like this it works, I pass the function the number of classes within a selected phylum, and it displays the pclass element, the problem gets when I try to populate the slist with data (which should contain the objects retrieved from the database), because when there is data returned by the database changing return Json(lists) instead of return Json(lists.count) I keep getting the same error:
A circular reference was detected while serializing an object of type 'SubSonic.Schema.DatabaseColumn'.
I've been going round and round debugging and making tests but I can't make it work, and it is suppossed to be a simple thing, but I'm missing something. I have commented the for loop because I'm not quite sure if that's the way you access the data, because I've not been able to make it work when it finds records. Can anyone help me?
Thanks in advance,
Victor
Finally I solved this problem. The problem was that JSON for some reason wasn't capable of displaying objects that are referenced to each other, that have problems with foreign keys and stuff. I created a new class just with the fields I needed from the database, and I populated it with a foreach statement. Then I passed that filled IList to the return Json and it worked...
Thank you all for your help,
Victor