Why won't a List of complex types bound to TextBoxes in a table show changes to the model in MVC 4? - asp.net-mvc-4

I have run into an issue that seems pretty simple, but I have not been able to find a solution. I have created a ReportModel object that is the model in the view. The ReportModel contains a list of FinancialHistory objects. I populate the objects and display them in a table of textboxes within a form in the view using default binding (This works correctly). The user can then submit the form to refresh the FinancialHistory objects from a different datasource, replacing what was previously in the list with the new results. When the new results are returned, I can see that the model contains the expected new values, but when the HTML is rendered, the original amounts still appear. If the new results contains more objects than the original list (as shown in the example code), the added rows do appear with the correct values. So, if the original had 2 objects and the refreshed list has 3, the resulting HTML shows the first 2 rows with the old values and a 3rd row with the new values.
Here are the models:
public class ReportModel
{
public string AccountNumber { get; set; }
public IList<FinancialHistory> FinancialHistories { get; set; }
}
public class FinancialHistory
{
public FinancialHistory()
{
Id = Guid.Empty;
}
public Guid Id { get; set; }
public DateTime TransactionDate { get; set; }
public decimal TotalAmount { get; set; }
}
In the Home/Index view, I use HTML.TextBoxFor() to bind the properties of each FianancialHistory object in the list to textboxes in a table. Here is the Index view:
#model SimpleExample.Models.ReportModel
<form id="FormSave" method="post" name="FormSave" action="/Home/Refresh">
#Html.LabelFor(model => model.AccountNumber) #Html.TextBoxFor(model => model.AccountNumber)
<table class="table" style="width: 95%">
<tr>
<td >Date</td>
<td >Amount</td>
</tr>
#{
if (Model.FinancialHistories != null)
{
for (int index = 0; index <= Model.FinancialHistories.Count - 1; index++)
{
<tr>
<td>#Html.TextBoxFor(model => model.FinancialHistories [index].TransactionDate, "{0:MM/dd/yyyy}", new { #readonly = "true" })</td>
<td>#Html.TextBoxFor(model => model.FinancialHistories[index].TotalAmount, "{0:#,#.00}", new { #readonly = "true" })</td>
<td>#Html.HiddenFor(model => model.FinancialHistories[index].Id)</td>
</tr>
}
}
}
</table>
<input type="submit" id="submit" value="Refresh" class="submit" />
</form>
For this example, my action methods in the controller are very simple. Initially, the Index method populates the list with 2 FinancialHistory Objects. The Refresh method replaces the original 2 objects with 3 new objects, with different amounts.
public class HomeController : Controller
{
public ActionResult Index()
{
ReportModel reportModel = new ReportModel();
reportModel.AccountNumber = "123456789";
IList<FinancialHistory> financialHistories = new List<FinancialHistory>();
financialHistories.Add(new FinancialHistory
{
Id = Guid.NewGuid(),
TransactionDate = DateTime.Parse("3/1/2010"),
TotalAmount = 1000.00M
});
financialHistories.Add(new FinancialHistory
{
Id = Guid.NewGuid(),
TransactionDate = DateTime.Parse("4/1/2011"),
TotalAmount = 2000.00M
});
reportModel.FinancialHistories = financialHistories;
return View(reportModel);
}
public ActionResult Refresh(ReportModel reportModel)
{
FinancialHistoryRepository financialHistoryRepository = new FinancialHistoryRepository();
IList<FinancialHistory> financialHistories = new List<FinancialHistory>();
financialHistories.Add(new FinancialHistory
{
Id = Guid.Empty,
TransactionDate = DateTime.Parse("3/1/2010"),
TotalAmount = 1111.11M
});
financialHistories.Add(new FinancialHistory
{
Id = Guid.Empty,
TransactionDate = DateTime.Parse("4/1/2011"),
TotalAmount = 2222.22M
});
financialHistories.Add(new FinancialHistory
{
Id = Guid.Empty,
TransactionDate = DateTime.Parse("5/1/2012"),
TotalAmount = 3333.33M
});
reportModel.FinancialHistories = financialHistories;
return View("Index",reportModel);
}
}

That's how HTML helpers work and is by design. When rendering they are first looking in the ModelState for values and after that in the model. You are modifying the values of your model in the POST controller action, but the ModelState values still contain the old values which will be used. If you want to modify values of your model in a POST action you should remove the original values from the ModelState if you intend to redisplay the same view:
public ActionResult Refresh(ReportModel reportModel)
{
// clear the original posted values so that they don't get picked up
// by the helpers
ModelState.Clear();
FinancialHistoryRepository financialHistoryRepository = new FinancialHistoryRepository();
...
return View("Index",reportModel);
}

Related

Why is my controller return wrong response data?

I`m using Ajax call to remove items from list. When user clicks on remove button (each button for row with id) it sends Ajax call (Post) to Order/RemoveFromCart. When backend is reached i remove the item from list. Im sure when returning data from controller the item i want to remove is truly removed. The problem is when i take my div and call html(data) from response it removes always the last item in the list.
Thank you for help.
I have tried update dotnet to latest version.
I have tried to remove Ajax call and reload whole view - that fixes the problem but i want to use an Ajax call.
#for (int i = 0; i < Model.CartItems.Count; i++)
{
<input asp-for="CartItems[i].ProductId" type="hidden">
<tr>
<td>
<input type="text" class="inputTextWithoutBorder" readonly="readonly" asp-for="CartItems[i].Product">
</td>
<td>
<select class="select-table" asp-for="#Model.CartItems[i].Packaging" asp-items="#Model.CartItems[i].PackageTypes"></select>
</td>
<td>
<input type="text" class="inputTextWithoutBorder" asp-for="#Model.CartItems[i].Amount" placeholder="Vyberte množství...">
</td>
<td>
<button id="removeFromCartId" onclick="removeFromCart(this);" type="button" value="#i" class="removeButton"></button>
</td>
</tr>
}
[HttpPost]
public IActionResult RemoveFromCart(OrderModel model, int itemToRemove)
{
if (model.CartItems[itemToRemove] != null)
{
model.CartItems.RemoveAt(itemToRemove);
}
return PartialView("_OrderCartWithCalendarPartial", model);
}
Model:
public class CartModel
{
public int ProductId { get; set; }
public int Amount { get; set; }
public string Packaging { get; set; }
public string Product { get; set; }
public List<SelectListItem> PackageTypes { get; } =
new List<SelectListItem>{
new SelectListItem {Value = "", Text = ""},
};
}
Javascript:
function removeFromCart(button) {
var index = button.value;
var placeholderElement = $('#orderFormId');
var myformdata = placeholderElement.serialize()
+ "&itemToRemove=" + index;
var result = $.post("/Order/RemoveFromCart", myformdata);
result.done(function (data) {
$("#cartWithCalendarDiv").html(data);
setDateFieldToZero();
});
}
Problem scenario:
Sending via Ajax list with two items (item0, item1). The controller receives exact list with two items. Remove item0 in controller and return partial view with that updated model. The page reloads only with item0. Why?

How can I link a DropDown and Textbox to my Model's data?

Here is the code for my Model. ListBuilder.DropDown is part of a common class of functions, which simply returns a List when provided the string name of a stored procedure that will be called on the database.
There is some more shared common class code (stored procedure related) with in the try statement, but that implementation is irrelevant to the problem I'm having. The data is successfully retrieved and stored into the model.
public class PositionViewModel
{
[Display(Name = "Series")]
public string series { get; set; }
public int seriesID { get; set; }
public List<SelectListItem> list_Series { get; set; }
}
public PositionViewModel(string id)
{
Get(id);
this.list_Series = ListBuilder.DropDown(AppConstants.StoredProc_GetSeries);
}
public Position Get(string id)
{
ExecStoredProcedure sp = null;
DataTable dt = new DataTable();
try
{
sp = new ExecStoredProcedure(AppConstants.SP_POSITION_GET, new ConnectionManager().GetConnection(), AppConstants.SP_POSITION_GET);
sp.SPAddParm("#PD_ID", SqlDbType.Char, id, ParameterDirection.Input);
dt = sp.SPselect();
if (dt.Rows.Count > 0)
{
this.pd_id = dt.Rows[0]["PD_ID"].ToString();
this.official_title = dt.Rows[0]["label"].ToString();
this.series = dt.Rows[0]["Series"].ToString();
this.grade = dt.Rows[0]["Grade"].ToString();
this.PDType = dt.Rows[0]["PDType"].ToString();
}
}
catch (Exception ex)
{
throw ex;
}
finally
{
sp.dbConnection.Close();
}
return this;
}
Here is the code for my Controller
[HttpGet]
public ActionResult PositionEdit(string id)
{
PositionViewModel model = new PositionViewModel(id);
return View("PositionEdit", model);
}
[HttpPost]
public ActionResult PositionEdit(PositionViewModel model)
{
if (ModelState.IsValid)
{
int rc = model.Update();
return RedirectToAction("PositionView");
}
else
{
return View("PositionEdit", model);
}
}
Here is the code for my view. What I'd like to have is a dropdownlist that contains the model.seriesID (a sequence number) but as the user selects an item, it will update the textbox with model.series (the name of the series)
#model Project.Models.PositionViewModel
#{
ViewBag.Title = "Edit Position Description";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div class="editor-label">
#Html.LabelFor(model => model.series)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => Model.seriesID, Model.list_Series, new { style = "width:550px" })
#Html.ValidationMessageFor(model => Model.seriesID)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.series, new { style = "width:250px;" })
#Html.ValidationMessageFor(model => model.series)
</div>
<div class="toppad20">
<input type="submit" value="Save" />
</div>
}
I am having trouble linking the dropdownlist with the textbox. Do I need some kind of onChange event? Thanks in advance for your help.
Your solution involves passing a string into your view model's constructor. However, on post, the model binder will be incapable of instantiating your view model with anything but the parameterless constructor. That's part of the reason, but not the only reason, that view models should not handle things like datastore access. That is the job of the controller.
On your view model, leave your list property as a auto-implemented property and then in your controller call ListBuilder.DropDown, which you can use data from your model to call, at that point.

Model is Null in Httppost mvc

My Model is
public class IssueEntryModel
{
public IEnumerable<SelectListItem> OrderNumbers { get; set; }
public string SelectedWorkOrder { get; set; }
public string MaterialCode
{
get; set;
}
public List<GroupedIssueData> MaterialData { get; set; }
}
And the view is
#model InventoryEasy15.Models.IssueEntryModel
#{
var issueData = Model.MaterialData;
var workorders = Model.SelectedWorkOrder;
}
#using (Html.BeginForm("SaveIssueEntry", "IssueMaster", FormMethod.Post, new { id = "issueEntryForm" }))
{
#for (int i = 0; i < issueData.Count(); i++)
{
<tr>
<td>#issueData[i].MaterialCode</td>
<td>#issueData[i].MaterialDescription</td>
<td>#issueData[i].Unit</td>
<td>#issueData[i].ReqQty</td>
<td>#Html.TextBoxFor(m => issueData[i].IssueQty, new { style = "width:70px" })#Html.ValidationMessageFor(m => issueData[i].IssueQty)</td>
<td class="text-center">#Html.CheckBoxFor(m => issueData[i].isSavings)</td>
</tr>
}
And I have post method as
public ActionResult SaveIssueEntry(IssueEntryModel model)
{
var result = new Dictionary<string, string>();
And the get contains the details to fill the view as
//Method Get the material details based on the work order id
public async Task<ActionResult> GetWorkOrderMaterialDetails(IssueEntryModel m)
{
During post to a new method , the model is becomes null, Any thoughts?
Razor uses the expression passed to the HTML helpers in order to build the proper name for the inputs that will allow the modelbinder to bind them properly on post. That means the expression needs to match the access method of the property exactly. By saving Model.MaterialData to the issueData variable and utilizing that, you're disrupting this. In other words, you're ending up with inputs named like issueData[0].IssueQty, instead of MaterialData[0].IssueQty. The modelbinder doesn't know what to do with issueData on post, because nothing on your model matches that.
Long and short, your textbox needs to be declared like:
#Html.TextBoxFor(m => m.MaterialData[i].IssueQty, ...)
Similarly for your checkbox:
#Html.CheckBoxFor(m => m.MaterialData[i].isSavings)

Selected item not showing in dropdown list in Edit Form

Eg: I have both Add form and Edit Form. When I add details in dropdown List it successfully saved in database. When I go for Edit the selected item is not showing in the dropdown list.
Code I used in EditPartial.
#Html.DropDownListFor(m => m.Language_ID,(List<SelectListItem>)ViewBag.LanguageList,null, new { type = "text", Class = "validate[required] form-control smheight", style = " font-size:11px; padding-top:3px" })
Controller code
var _tableLanguage = db.LANGUAGES_TABLE.Select(_Log => new SelectListItem
{
Text = _Log.Languages,
Value = SqlFunctions.StringConvert((decimal)_Log.Language_ID) }
).ToList();
_tableLanguage.Insert(0, new SelectListItem { Text = "Select", Value = "0" });
ViewBag.LanguageList = _tableLanguage;
var items = new SelectList(db.LANGUAGES_TABLE.ToList(), "Language_ID", "lang");
ViewData["Language_ID"] = items;
Model
public partial class LANGUAGE_PROFFICIANCY
{
public int Language_Proficiency_ID { get; set; }
public Nullable<int> Candidate_ID { get; set; }
public Nullable<int> Language_ID { get; set; }
}
Change you controller code to
ViewBag.LanguageList = new SelectList(db.LANGUAGES_TABLE, "Language_ID", "Languages");
and delete the following
var items = new SelectList(db.LANGUAGES_TABLE.ToList(), "Language_ID", "lang");
ViewData["Language_ID"] = items;
and change the view to
#Html.DropDownListFor(m => m.Language_ID, (SelectList)ViewBag.LanguageList, "Select", new {...});
If the value of property Language_ID matches the value of one of your option values, then it will be selected in the view
Note also your html attributes are invalid. type = "text" makes no sense - its a <select> element, not a <input type="text" ..> element. Class = "validate[required] makes no sense either (no sure what you think this would do)

dropdownlist selection returning null # mvc4

I am trying to insert to database from view page which has dropdownlist , textbox's .. when i enter something and click on save means i am getting nothing from dropdown selection which is binded .
My code :
#model IEnumerable<iWiseapp.Models.LObmodel>
#using (#Html.BeginForm("stop", "Home", FormMethod.Post))
{
#Html.DropDownList("Data",ViewBag.Data as SelectList,"select a sdsd",new {id="LOB_ID"})
#Html.DropDownListFor("sss",new SelectList(Model,"lob_id","lob_name"))
,
#Html.DropDownList("LObmodel", new SelectList(ViewBag.data ,"Value","Text"))
#Html.DropDownListFor(model => model.lob_name, new SelectList(ViewBag.Titles,"Value","Text"))
I tried above all possibilities but nah i am confused nothing working out
ADDED MY CONTROLER CODE
[HttpGet]
public ActionResult stop()
{
ServiceReference1.Service1Client ser_obj = new ServiceReference1.Service1Client();
IEnumerable<LobList> obj = ser_obj.GetData(); //i am Getting list of data through WCF from BUSINESS LAYER WHERE i created entities via EF
List<SelectListItem> ls = new List<SelectListItem>();
foreach (var temp in obj)
{
ls.Add(new SelectListItem() { Text = temp.LOB_NAME, Value = temp.LOB_ID.ToString() });
}
//then create a view model on your controller and pass this value to it
ViewModel vm = new ViewModel();
vm.DropDown = ls; // where vm.DropDown = List<SelectListItem>();
THE COMMENTED CODE BELOW IS WHAT I AM DOING
//var mode_obj = new List<LObmodel>();
//Created LOBmodel class in model which is excat same of entities in Business class
//var jobList = new List<SelectListItem>();
//foreach (var job in obj)
//{
// var item = new SelectListItem();
// item.Value = job.LOB_ID.ToString(); //the property you want to display i.e. Title
// item.Text = job.LOB_NAME;
// jobList.Add(item);
//}
//ViewBag.Data = jobList;
return View(jobList); or return view (obj)
}
Any expert advice is appreciated
MY FIELDS , IS THESE PERFECT
public class template
{
public List<LobList> LOBs { get; set; } //LOBLIST FROM Entities in business layer
public int selectedLobId { get; set; }
public LobList SelectedLob
{
get { return LOBs.Single(u=>u.LOB_ID == selectedLobId) ;}
}
}
AND
public class LObmodel
{
public int LOB_ID { get; set; }
public string LOB_NAME { get; set; }
}
I would recommend putting the selectlist into your model instead of passing it through the view bag. The option you want is
#Html.DropDownListFor(model => model.lob_name, new SelectList(ViewBag.Titles,"Value","Text"))
you can set the selected item by setting model.lob_name on the controller and on post back that value will be set to the selected value of the dropdown
on your controller you can build the list like this
List<SelectListItem> ls = new List<SelectListItem>();
foreach(var temp in model){ //where model is your database
ls.Add(new SelectListItem() { Text = temp.Text, Value = temp.Value });
}
//then create a view model on your controller and pass this value to it
LObmodel vm = new LObmodel();
vm.DropDown = ls; // where vm.DropDown = List<SelectListItem>();
return View(vm);
then on your view you can reference that
#Html.DropDownListFor(x => x.lob_name, Model.DropDown)
with your model add the select list
public class LObmodel
{
public int LOB_ID { get; set; }
public string LOB_NAME { get; set; }
public List<SelectListItem> DropDown { get; set; }
}
then the top of your view would be
#model LObmodel
I had the same problem .
But i changed the view code of DDL using this code :
#Html.DropDownListFor(x => x.ClassID, (SelectList)ViewBag.ClassName);
The dropdownlist will bind to your model class called ClassID You will not be able to post the textual value of the ddl to the controller, only the ID behind the ddl.