Can't seem to get the list to remove the correct item.
I've put breakpoints in the code to test if the id is the correct index with shipment.shipmentfreight[id] within the Immediate Window and it shows the correct list item, but it always removes the last item.
In my view model I have a list of ShipmentFreightLine declared as:
public List<ShipmentFreightLine> shipmentfreight { get; set; }
I have a call back that removes the line that returns the new partial view:
public virtual ActionResult ShipmentNewFreightLineRemove(int id,NewShipment shipment)
{
int cnt = shipment.shipmentfreight.Count();
if (id >= 0 && cnt != 1 && id <= cnt)
{
shipment.shipmentfreight.RemoveAt(id);
//shipment.shipmentfreight.RemoveAt(shipment.shipmentfreight.IndexOf(shipment.shipmentfreight[id]));
//shipment.shipmentfreight.Remove(shipment.shipmentfreight[id]);
//shipment.shipmentfreight[id]
}
return PartialView("ShipmentNewFreightLineEdit", shipment);
}
As you can see I've commented out different variations that I have tried and each one removes the last item in the list.
What am I missing?
I am coding in VS2012 ASP.NET MVC using C#.
Any help would be aprreciated
As requested by Kirk the id field is from an anchor
Remove
Which calls this javascript function
function RemoveFreightLine(index) {
var ShipmentData = $('#fShipmentNew').serialize();
var url = "/Tracking/ShipmentNewFreightLineRemove/" + index
$.post(url, ShipmentData,
function (data) {
$("#dNewFreightLines").html(data);
});
}
These all have the correct id in them so i don't believe its the cause
I traced the view and found that it is removing the correct item in the list but the view is showing the old data
<table id="tFreight" class ="tblFreight">
<tbody>
#for (int i = 0; i < Model.shipmentfreight.Count; i++)
{
<tr>
<td>#(i+1)</td>
<td>#Html.TextBoxFor(m => m.shipmentfreight[i].Pieces, new { autocomplete = "off", #Class = "txtboxsmall",maxlength ="3"})</td>
<td>#Html.TextBoxFor(m => m.shipmentfreight[i].Description,new { autocomplete = "off", #Class = "txtareasmall"})</td>
<td>#Html.TextBoxFor(m => m.shipmentfreight[i].WeightPerPiece, new { autocomplete = "off", #Class = "txtboxsmall",maxlength ="3"})</td>
<td>#Html.TextBoxFor(m => m.shipmentfreight[i].ActualWeight, new { autocomplete = "off", #Class = "txtboxsmall",maxlength ="3"})</td>
<td>#Html.TextBoxFor(m => m.shipmentfreight[i].Length, new { autocomplete = "off", #Class = "txtboxsmall",maxlength ="3"})</td>
<td>#Html.TextBoxFor(m => m.shipmentfreight[i].Width, new { autocomplete = "off", #Class = "txtboxsmall",maxlength ="3"})</td>
<td>#Html.TextBoxFor(m => m.shipmentfreight[i].Height, new { autocomplete = "off", #Class = "txtboxsmall",maxlength ="3"})</td>
<td>Remove</td>
<td>#Model.shipmentfreight[i].Description</td>
</tr>
}
</tbody>
</table>
The last cell was for debugging purposes and does display the correct information.
I am thinking its some quirk with TextBoxFor that I don't know about.
Basically the last cell is showing the correct data from the new list but anything in the TextBoxFor is showing the old list data.
Any ideas on why I am getting this behavior?
Looks like this was a problem with the ViewData and not the List.
This seems to fix the problem:
ViewData = null;
or
ModelState.Clear();
Found the answer with Why won't a List of complex types bound to TextBoxes in a table show changes to the model in MVC 4? and TextBoxFor Helper retains previous value even when model value is empty
As with all things...Finding the real cause is key to finding the answer.
Related
In my MVC application I have a view where I will display different data from a SQL table. This will generate different amount of drop down lists and text boxes, depending on what is passed in from the Model.
My issue is if I want to then use that data I can't seem to figure out how I can relate control X to object Y in SQL. For example, if I have 2 textboxes that I want to do an update on, then when the Post happens in my application the FormCollection parameter will let me see the Value of the objects, but not their control name or any form of identifying factor.
I could set the Value to a combination of the entered value + a name, then split this, but it seems very much like a lazy workaround.
I've tried to assign an ID to each, for example:
#foreach (DataObject item in Model.AllDataObjects)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Data)
</td>
<td>
#if (item.Rule.Contains("Yes;No"))
{
#Html.DropDownListFor(model => item.Value, new List<SelectListItem>
{
new SelectListItem {Text="Yes", Value="Yes"},
new SelectListItem {Text="No", Value="No" }
}, new { #id = item.ObjectId });
}
else
{
#Html.TextAreaFor(model => item.Value, new { style = "width: 400px;", #rows = 5, #id = item.ObjectId })
}
</td>
</tr>
}
Edit: The following is my Post ActionResult method in the Controller, albeit it isn't complete as I can't figure out how to get an ID for the control from the FormCollection
[HttpPost]
[ValidateInput(false)]
public ActionResult UpdateData(FormCollection collection, int objectId=0)
{
try
{
int propertyTypeId = 0;
string propertyValue = string.Empty;
// Get all the control values from the collection
string[] allValues = new string[] { };
IValueProvider valueProvider = collection.ToValueProvider();
foreach(string key in collection.Keys)
{
ValueProviderResult result = valueProvider.GetValue(key);
allValues = result.RawValue as string[];
}
ObjectData objectData = _execution.GetObjectDetails(0);
UpdateDataResponse result = _execution.UpdateData(0, objectId,
objectValue, UserName);
return RedirectToAction("Details",
new { ObjectId = objectData.ObjectId, error = result.ErrorMessage });
}
catch (Exception ex)
{
// My exception handling here
}
}
So I can see in the mark-up that the controls are assigned the object ID as their own ID, but how can I get this back? When I check FormCollection I only see the values for each control, but no way of identifying which is which.
Edit: I'm using MVC version 4.
A form only submits the values of its successful controls (as name/value pairs based on the controls name and value attributes) so if you do not generate a control for the ObjectId properties, they will not be submitted.
However, you current use of foreach loop will not allow you to obtain any meaning information from the data which is posted because all your names are identical and there is no way to reliable match up which value belongs to which item in the collection. Instead use a for loop or EditorTemplate in the view and bind to your model, rather than using FormCollection.
The view should be
#for (int i = 0; i < Model.AllDataObjects.Count; i++)
{
<tr>
<td>#Html.DisplayFor(m => m.AllDataObjects[i].Name)</td>
<td>#Html.DisplayFor(m => m.AllDataObjects[i].Data)</td>
<td>
#Html.HiddenFor(m => m.AllDataObjects[i].ObjectId)
#if (Model.AllDataObjects[i].Rule.Contains("Yes;No"))
{
#Html.DropDownListFor(m => m.AllDataObjects[i].Value, new SelectList(new string[]{ "Yes", "No" }));
}
else
{
#Html.TextAreaFor(m => m.AllDataObjects[i].Value, new { style = "width: 400px;", #rows = 5 })
}
</td>
</tr>
}
And assuming the model in the view is #model MyModel, change the POST method to
[HttpPost]
[ValidateInput(false)]
public ActionResult UpdateData(MyModel model)
and the value of model.AllDataObjects will contain a collection with its ObjectId and Value properties correctly bound.
For more information on why using a foreach loop will not work, refer to this answer.
I have multiple dropdownlists which are rendered with a for loop and I'm having a problem getting them to post the selected value to the controller. In my query my selectlist is made like this:
model.CreateGroupForm.Genders = new List<SelectListItem>
{
new SelectListItem() {Text = "Either", Value = "Either"},
new SelectListItem() {Text = "Male", Value = "Male"},
new SelectListItem() {Text = "Female", Value = "Female"},
};
My first problem was even getting my dropdown to display the database value, even though I confirmed it was retrieving the correct value. It wouldn't work with this:
#for (var c = 0; c < Model.ExistingGroups.Count; c++)
{
#using (Html.BeginForm("EditGroup", "Group", new { id = Model.Id.StripCollectionName(), slug = Model.Slug, innerid = Model.ExistingGroups[c].Id }, FormMethod.Post, new { id = "editcommunityteamform" + c.ToString(CultureInfo.InvariantCulture), #class = "nomarginbottom" }))
{
...
#Html.DropDownListFor(x => x.ExistingGroups[c].Gender, Model.Createform.Genders)
<button type="submit" class="btn btn-primary" title="Update name and description of this group">Update</button>
}
}
After doing some digging on Stack, I discovered that each dropdown rendered needs it's own separate list. So I changed it to:
#Html.DropDownListFor(x => x.ExistingGroups[c].Gender,
new SelectList( Model.CreateGroupForm.Genders,"Value", "Text",Model.ExistingGroups[c].Gender))
This then correctly displays the queried value, however it just posts null to the controller when I submit the form. I'm having the same issue with a checkboxfor boolean within the for loop.
My ActionResult in the controller just expects a string value and looks like this:
public ActionResult EditGroup(EditGroupInput input)
{
var command = new EditGroupCommand(input.Gender);
....
My view model looks like this:
public IList<CommunityGroup> ExistingGroups { get; set; }
public CreateGroupInput CreateGroupForm { get; set; }
And then the above 2 classes have the properties mentioned in the code.
I've discovered the problem, which is that dropdownlistfor, checkboxlistfor etc do not like operating within a 'for' loop. I certainly don't have the technical know-how to understand why, but when I changed dropdownlistfor to dropdownlist it worked. So the solution looks like this:
#for (var c = 0; c < Model.ExistingGroups.Count; c++)
{
#using (Html.BeginForm("EditGroup", "Group", new { id = Model.Id.StripCollectionName(), slug = Model.Slug, innerid = Model.ExistingGroups[c].Id }, FormMethod.Post, new { id = "editcommunityteamform" + c.ToString(CultureInfo.InvariantCulture), #class = "nomarginbottom" }))
{
...
#Html.DropDownList("Gender", new SelectList(Model.CreateGroupForm.Genders, "Value", "Text", Model.ExistingGroups[c].Gender))
...
}
}
EditGroupInput should be a collection of ExistingGroups as controller action method are strongly binded with view.or use formcollection as parameter and see what are all the keys being posted from view to controller.
I have many DropDownLists on page
class BigViewModel
{
public List<SmallViewModel> SmallVM {get;set;}
public List<SelectListItem> Items {get;set;}
//some other properties
}
class SmallViewModel
{
public string ItemId {get;set;}
//some other properties
}
<table>
#for( var i = 0;i<Model.SmallVM.Count();i++)
{
<tr>
<td>
#Html.DropdownListFor(m=> m.SmallVM.ItemId, Model.Items)
</td>
</tr>
}
//display other properties
</table>
in controller
bigViewModel.Items = List<SelectListItem>
{
new SelectListItem{Value = "1", Text = "aaa"},
new SelectListItem{Value = "2", Text = "bbb"},
new SelectListItem{Value = "3", Text = "ccc"},
}
bigViewModel.SmallVM = new List<SmallViewModel>
{
new SmallViewModel{ItemId = 3},
new SmallViewModel{ItemId = 2},
}
In controller I set diffrent ItemId for every SmallVM and each DropDownList uses the same Items collection. I want to set default Value from SmallViewModel for each DropDownList. For example in this case there are two DropDownLists first should display default text "ccc" and second "bbb".
Should I put diffrent List<SelectedListItem> for every SmallViewModel and set them Selected property or there is other way?
This behavior has been reported as a bug on CodePlex but not yet fixed. Using DropDownListFor() in a for loop does not bind correctly and the first option is always selected despite the value of the property. In order for DropDownListFor() to work correctly when using a collection, you need to use an EditorTemplate for the model.
In /Views/Shared/EditorTemplates/SmallViewModel.cshtml
#model SmallViewModel
#Html.DropdownListFor(m => m.ItemId, (SelectList)ViewData["Items"])
Then in the main view
#model BigViewModel
#using(Html.BeginForm())
{
// Pass the select list to the EditorTemplate as addtionalViewData
#Html.EditorFor(m => m.SmallVM, new { Items = Model.Items })
<input type="submit" />
}
You should now have 2 <select> controls displaying "ccc" and "bbb" respectively.
Based on your code updates, I think you just need to modify your view code to:
#Html.DropdownListFor(m=> m.SmallVM[i].ItemId, Model.Items)
However, I have distinct feeling that this is very much an XY problem, and although this change will make everything wire up on post, you're still not going to be getting what you actually need for the true problem you're trying to solve.
I'm having the same issue as this here I believe, but the workaround is not working for me.
My issue is that I have a child collection of models inside my main view's ViewModel. They contain data to be displayed in two fields, a dropdownlist and a password field. Each dropdownlist selection must be unique. Everything is saving and being sent to the view properly, however the dropdownlist are not binding to the selected values when the view is called but the password field is. They all default to the first selection, even though the property they are suppose to bind to is unique and only one can be the first value. Any help or insight is appreciated. Thanks.
Here is the part in my view where the issue is occurring. I have commented out my efforts and tried the above link's workaround to no avail:
#functions {
private IEnumerable<SelectListItem> Mark(IEnumerable<SelectListItem> items, object Id)
{
foreach (var item in items)
if (string.CompareOrdinal(item.Value, Convert.ToString(Id)) == 0)
item.Selected = true;
return items;
}
}
#for (int j = 0; j < Model.PasswordResetQuestionUserAnswers.Count(); j++)
{
#Html.Hidden("PasswordResetQuestionUserAnswers.Index", j)
#Html.HiddenFor(p => Model.PasswordResetQuestionUserAnswers[j].Id)
#Html.HiddenFor(p => Model.PasswordResetQuestionUserAnswers[j].UserId)
<div class="form-group">
<label class="col-md-2 control-label">Password Reset Question #(j+1)</label>
<div class="col-md-6">
#*#Html.DropDownList("PasswordResetQuestionUserAnswers[" + j + "].PasswordResetQuestionId", Model.PasswordResetQuestionList, new { #class = "form-control passwordQuestion" })*#
#*#Html.DropDownListFor(x => Model.PasswordResetQuestionUserAnswers[j].PasswordResetQuestionId, Model.PasswordResetQuestionList, new { #class = "form-control passwordQuestion" })*#
#Html.DropDownListFor(x => Model.PasswordResetQuestionUserAnswers[j].PasswordResetQuestionId, Mark(Model.PasswordResetQuestionList, Model.PasswordResetQuestionUserAnswers[j].PasswordResetQuestionId))
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Password Reset Answer #(j+1)</label>
<div class="col-md-6">
#Html.Password("PasswordResetQuestionUserAnswers[" + j + "].Answer", Model.PasswordResetQuestionUserAnswers[j].Answer, new { #class = "form-control passwordQuestionUserAnswer" })
#*#Html.PasswordFor(x => Model.PasswordResetQuestionUserAnswers[j].Answer, new { #class = "form-control passwordQuestionUserAnswer" })*#
</div>
</div>
}
I just had this same problem. This syntax works for me:
#Html.DropDownListFor(x => x.ChildCollection[i].ChildID, new SelectList(ViewBag.ChildCollectionSelect as SelectList, "Value", "Text", Model.ChildCollection[i].ChildID))
Define the SelectList as new, then specifically set the selected value from the model.
This is an adaption to the example of above, for what worked for me in a similar scenario, where itm represents the child object in the collection. I'm not exactly sure what all is going on in that example -- too many "Questions", "Users", and "Answers", but say if you wanted a dropdown of users and it to be filled with the particular one that had been assigned to that child item:
foreach (var itm in Model.PasswordResetQuestionUserAnswers)
{
#Html.DropDownListFor(modelItem => itm.UserId,
new SelectList( (IEnumerable<SelectListItem>)ViewData["users"], "Value", "Text", itm.UserId),
htmlAttributes: new { #class = "form-control" }
)
}
Where you'd fill ViewData["users"] like this in the Controller method that renders the view:
var usersList = GetUsersList();
ViewData["users"] = usersList;
and have these supporting functions:
private static SelectListItem[] _UsersList;
/// <summary>
/// Returns a static category list that is cached
/// </summary>
/// <returns></returns>
public SelectListItem[] GetUsersList()
{
if (_UsersList == null)
{
var users = repository.GetAllUsers().Select(a => new SelectListItem()
{
Text = a.USER_NAME,
Value = a.USER_ID.ToString()
}).ToList();
users.Insert(0, new SelectListItem() { Value = "0", Text = "-- Please select your user --" });
_UsersList = users.ToArray();
}
// Have to create new instances via projection
// to avoid ModelBinding updates to affect this
// globally
return _UsersList
.Select(d => new SelectListItem()
{
Value = d.Value,
Text = d.Text
})
.ToArray();
}
Repository.cs
My Repository function GetAllUsers() for the function, above:
Model1 db = new Model1(); // Entity Framework context
// Users
public IList<USERS> GetAllUsers()
{
return db.USERS.OrderBy(e => e.USER_ID).ToList();
}
Users.cs
public partial class USERS
{
[Key]
public int USER_ID { get; set; }
[Required]
[StringLength(30)]
public string USER_NAME { get; set; }
}
Edit
After re-reading the question, it seems it was about posting password reset questions.
foreach (var itm in Model.PasswordResetQuestionUserAnswers)
{
#Html.DropDownListFor(modelItem => itm.PasswordResetQuestionId,
new SelectList( (IEnumerable<SelectListItem>)ViewData["pwordResetQuestions"], "Value", "Text", itm.PasswordResetQuestionId),
htmlAttributes: new { #class = "form-control" }
)
}
And you'd have to have a ViewData["pwordResetQuestions"] filled like this in the controller method that renders that view:
var questionsList = GetQuestionsList();
ViewData["questions"] = questionsList;
and these supporting functions/objects:
private SelectListItem[] _QuestionsList;
public SelectListItem[] GetQuestionsList()
{
if (_QuestionsList == null)
{
var questions = PasswordResetQuestionUserAnswers.Select(a => new SelectListItem()
{
Text = a.Answer, //? I didn't see a "PasswordResetQuestionText" call in your example, so...
Value = a.PasswordResetQuestionId.ToString()
}).ToList();
questions.Insert(1, new SelectListItem() { Value = "1", Text = "Mother's Maiden Name" });
questions.Insert(2, new SelectListItem() { Value = "2", Text = "Elementary school attended" });
_QuestionsList = questions.ToArray();
}
// Have to create new instances via projection
// to avoid ModelBinding updates to affect this
// globally
return _QuestionsList
.Select(d => new SelectListItem()
{
Value = d.Value,
Text = d.Text
})
.ToArray();
}
I hard-coded some questions in there - I kinda doubt you'd have a table for them, usually companies only have less than 10. But you could always do that database call like I did for the Users table if they were using a database table - which is why I left that example there.
I was having the same issue and fighting with it. The examples above got me over the hump, but I was able to simplify using the code the way you have it, with one modification:
Original Code
#Html.DropDownListFor(x => Model.PasswordResetQuestionUserAnswers[j].PasswordResetQuestionId, Model.PasswordResetQuestionList, new { #class = "form-control passwordQuestion" })
Update Code: (Wrap it with a new SelectList)
#Html.DropDownListFor(x => Model.PasswordResetQuestionUserAnswers[j].PasswordResetQuestionId, new SelectList(Model.PasswordResetQuestionList, "Value", "Text", Model.PasswordResetQuestionUserAnswers[j].PasswordResetQuestionId, new { #class = "form-control passwordQuestion" })
This eliminates the need for the ViewBag or ViewData.
Here is what my view looks like:
#model Affiliate
<div class="box paint color_16">
<div class="title">
<h4><i class="icon-tasks"></i><span>#Model.CompanyName's Commissions</span> </h4>
</div>
<div class="content top ">
<div class="subtitle">
#Html.ActionLink("Void", "DeleteInvoice", new { commList = "??", affId = Model.Id }, new { #class = "btn" })
#Html.ActionLink("Create Invoice", "CreateInvoice", new { commList = "??", affId = Model.Id }, new { #class = "btn" })
#Html.ActionLink("Pay", "PayInvoice", new { commList = "??", affId = Model.Id }, new { #class = "btn" })
</div>
<table class="table table-striped table-hover">
<tr>
<h3>Commissions</h3>
</tr>
<tr>
<td></td>
<td>Amount</td>
<td>Status</td>
<td>Action</td>
</tr>
#foreach (var item in Model.Commissions)
{
<tr>
#if (item.Status == ViewBag.PaymentStatus || ViewBag.PaymentStatus == "All")
{
<td>#Html.CheckBox("commId", new { value = item.Id })</td>
<td>#Html.DisplayFor(x => item.PayoutAmount)</td>
<td>#Html.DisplayFor(x => item.Status)</td>
}
</tr>
}
</table>
</div>
What I want to be able to do is when I hit an actionlink on the top, grab all the items from the table that are checked, and pass that list of id's to the controller logic. I am assuming a viewmodel may be the solution, something like this:
public Affiliate affilite { get; set; }
public List<int> selectedItems { get; set; }
etc.
But how to I get the selected Items into that VM selectedItems container?
Based off your comments, you don't seem to be looking for the most "correct" answer, but rather just a quick and dirty "how would I do this" answer. If you just want to pass the list, you could setup your controller action like this:
public ActionResult MyAction(int[] id)
{
...
}
Or, you seem to indicate it is strongly typed to a view model with a property that contains a List (I would shorten the name of the property, you'll see why in a second).
In javascript, the easiest thing to do would be to use jQuery to bind a click event on your hyperlink that gets the list of items that are checked and appends that to the query string.
$("#myLink").click(function()
{
var url = "site.com/action?";
var ids = $(".table").find("input:checked");
ids.each(function()
{
url += "id=" + $(this).val() + "&"
});
window.location = url;
});
Basically, you want to create one long query string with the action parameter's name repeated over and over, which identifies an array. It looks something like this (id is for int[] id in MyAction):
id=15&id=20&id=25&id=30&....
And then once the query string is built, redirect the user to that url. MVC should then be able to bind that to an array and you're all set.
That's basically the idea, anyway; the syntax and the javascript I wrote could be way off so don't copy my code and expect it to work as is - I wrote that off the top of my head. If your action is bound to a viewmodel, then you need to set the parameter in the query string to the name of the property of your model:
selectedids=1&selectedids=2&selectedids=3...
Or, if the array is a property of an object, which is a property of the model...
model.selectedids=1&model.selectedids=2&model.selectedids=3...
You'll just need to play around with it some.
Use html checks inside form tag ( you could use helpers too) and post the model to a post action.
MVC will serialize the model automatically