Why won't the data in the model persist? - c#

I have a model, MyViewModel, with some members (not all are included).
namespace ANameSpace
{
public class MyViewModel
{
public int ID { get; set }
public EditTableObject EditTable { get; set }
public List<EditTableObject> EditTables { get; set }
// some other members
}
public class EditTableObject
{
public int ID { get; set }
// some other members
}
}
In the controller there are two ActionResult Index() methods
In The first, Index(int? id), I assign a value to model.ID and also add some EditTableObjects to an EditTables list.
Then, in the second, Index(ViewModel tmpModel), I am trying to use the members that I gave values to in the first index method. However, the ID member does not contain the value I assigned it; it's null. But strangely, the EditTables list does contain the values I assigned to it.
Here is some code from the controller:
namespace AnotherNamespace
{
public class MyController
{
public ActionResult Index(int? id)
{
if (id != null)
{
model.ID = (int)id;
}
else
{
model.ID = 1000;
}
model.EditTable = new EditTableObject();
model.EditTable.ID = model.ID;
model.EditTables.Add(model.EditTable);
return View(model);
}
public ActionResult(MyViewModel tmpModel)
{
return RedirectToAction("Index", new { id = tmpModel.ID });
}
}
}
If I set a break point on the second Index method I find that all the EditTables data persists but not the model.ID data.
Why is the EditTables data persisting but not the model.ID data?
Thanks for any suggestions.

Eventhough I don't quite get your question (posting some code that doesn't work will highly improve your chances of getting an answer) I'll give it a shot.
HttpRequests are stateless. So between the calls to the different methods you need to persist your data. Usually that's been done using a database.
This is a good source to get started with MVC: http://www.asp.net/mvc

Because you are not giving the entire model.
In order to receive the model you need to set your view based on that model and pass it to the controller on httppost.
Example
Controller
public ActionResult Create()
{
List<EditTableObject> myList = ....*YourList*....
ViewBag.EditTables = myList;
ViewModel vm = new ViewModel();
return View(vm);
}
[HttpPost]
public ActionResult Create(ViewModel myViewModel)
{
if(ModelState.IsValid)
{
// DO SOMETHING
}
else
{
return View(myViewModel);
}
}
View
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<namespace.ViewModel>" %>
<% using (Html.BeginForm()) { %>
<%: Html.AntiForgeryToken() %>
<%: Html.ValidationSummary(true) %>
<%: Html.HiddenFor(model => model.ID) %>
<div class="editor-field">
<%: Html.DropDownList("TableObject", IEnumerable<SelectListItem>)ViewBag.EditTable,String.Empty)%>
</div>
<% } %>

Okay, I figured it out.
In the the Razor View I added #Html.HiddenFor(modelItem => Model.ID) to a table row.
Here it is in more context:
#for (int i = 0; i < Model.EditTables.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(modelItem => modelItem.EditTables[i].ID)
#Html.HiddenFor(modelItem => Model.ID)
</td>
</tr>
}
Sorry for my lack of code in the beginning. I work for a company that would not be happy I shared code on the internet so I have to pick and choose and be careful what I post.

Related

Linking PartialView to View returns System.Web.Mvc.WebViewPage<TModel>.Model.get returned null

I looked up everywhere and tried multiple solutions but none of them would work. In my MVC website, I have a cart section and a checkout section. I want to make it so inside my checkout section, i have a small cart section to show the cart.
This is my partial view _CartItems.cshtml
#model IEnumerable<ArrowDefenseSystems.Models.Cart>
#{
ViewBag.Title = "Your Cart";
}
#if (Model != null)
{
foreach (var item in Model)
{
<div class="itemInfo row">
<img class="col-md-3" src="#Url.Content("~/Content/" + #Html.DisplayFor(modelItem => item.productImage))" height="100px">
<div class="CartItemText col-md-9">
<h3>#Html.DisplayFor(modelItem => item.productName)</h3>
<i>$#Html.DisplayFor(modelItem => item.productPrice)</i><br>
Quantity : #Html.DisplayFor(modelItem => item.quantityChosen)<br>
</div>
</div>
<hr />
}
}
When I launch the Partial View by itself, it shows the items fine and everything runs correctly.
This is how I'm linking the partial view to the view Checkout.cshtml
#model ArrowDefenseSystems.Models.ParentViewModel
...
...
#Html.Partial("_CartItems", Model.Cart)
When I run this code i get the following error on the code above:
System.NullReferenceException: 'Object reference not set to an instance of an object.'
System.Web.Mvc.WebViewPage<TModel>.Model.get returned null.
ParentViewModel:
public class ParentViewModel
{
public Checkout Checkout { get; set; }
public Cart Cart { get; set; }
}
Checkout Controller (theres more but its unnecessary):
[HttpGet]
public ActionResult _CartItems()
{
return PartialView(db.Carts.ToList());
}
public ActionResult Checkout()
{
return View();
}
I've tried many solutions but all return the same error. What am I missing?
I think there is a fundamental misunderstanding here: #Html.Partial("_CartItems", Model.Cart) will not call your _CartItems() action. Instead, it will create a new instance of the _CartItems.cshtml partial view, and use your Model.Cart parameter as it's Model.
In this case, there are two problems:
_CartItems.cshtml is expecting a model with a type of IEnumerable<ArrowDefenseSystems.Models.Cart>, whereas you are passing it a model with a type of Cart (via the Model.Cart parameter)
Model.Cart is not being instantiated in your Checkout action
So, to fix this, you should first change your ParentViewModel to be:
public class ParentViewModel
{
public Checkout Checkout { get; set; }
public IEnumerable<Cart> Carts { get; set; }
}
Then, update your Checkout action to be:
public ActionResult Checkout()
{
var viewModel = new ParentViewModel
{
Carts = db.Carts.ToList()
}
return View(viewModel);
}
And change your usage of Model.Cart to Model.Carts (due to the rename above)
After that, you can remove your _CartItems action as it is no longer used.

Binding Model of PartialViews on a POST

I have a complex model class like:
public class Client
{
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string AddressLine { get; set; }
}
My View is made of several Partial's on which I pass parts of the model into them and dispose some fields for edition:
In Index.cshtml
#using (Html.BeginForm("Index"))
{
#Html.DisplayNameFor(modelItem => model.Name)
#Html.DisplayFor(modelItem => model.Name)
<div id="divAddress">
#Html.Action("_Address", model.Address)
</div>
<div>RESULT MESSAGE GOES HERE!</div>
<input type="submit" value="Submit" />
}
In _Address.cshtml
#Html.DisplayNameFor(modelItem => model.AdressLine)
#Html.EditorFor(modelItem => model.AdressLine)
On the code-behind my Actions consist of two simple ActionResults methods:
[HttpGet]
public ActionResult Index()
{
Client = new Client();
Client.Name = "António Fonseca"
return View(model);
}
[HttpPost]
public ActionResult Index(Client model)
{
return View(model);
}
public ActionResult _Address(Address model)
{
return View(model);
}
When I submit the form, I need to call a WebService with the full Client structure and display it's resulting message.
What happens is that when hitting Index(model) -> model.Address is null.
It's only bound back when it hits _Address(model) method.
Is there a way to bind the full class structure in main Action using PartialViews?
Change the model in _Address.cshtml to be the same as the model in your main view and use #Html.Action("_Address", model) so that the form controls are correctly named - i.e. name="Address.AdressLine" (its currently just name="AdressLine" but you model does not contain a property named AdressLine).
#model Client
#Html.DisplayNameFor(m => m.Address.AdressLine)
#Html.EditorFor( m=> m.Address.AdressLine)
However using #Html.Action() is not the correct approach for this. You should be using an EditorTemplate. Rename _Address.cshtml to Address.cshtml (to match the name of your class name) and place it in the /Views/Shared/EditorTemplates folder and then in the view use
#Html.EditorFor(m => m.Address)
which will correctly name your form controls.

Hidden fields have the wrong value after first POST

I have a view on which the user can log time spent on an Activity using an HTML form. So that view loops through a list of all Activities and generates a log time form (contained in the _LogTime partial view) for each one. The only piece of information passed to the partial view from the Index view is the ActivityId, which is placed in a hidden form. The rest of the required information is provided via the from by the user.
The problem I'm having is that once I submit one of the forms, the hidden field for all of the forms is set to the ActivityId of the form I just submitted. It's worth noting that when the page first loads (before I submit any forms), the hidden fields are correct, and when I submit a form for the first time, the correct Activity gets time logged to it (and none of the others erroneously get time logged). But any form submissions after that will only log time to the Activity I first submitted the form for.
Any idea what's going on here? Why are all of the hidden fields being set to the same ActivityId? And why only after the first POST? Let me know if you need any clarification of the problem.
Models:
public class Activity
{
public int ActivityId { get; set; }
public string Name { get; set; }
}
public class UserActivity
{
public int UserId { get; set; }
public int ActivityId { get; set; }
public int Duration { get; set; }
public DateTime Date { get; set; }
}
Views:
// Index View
#foreach (Activity activity in Model)
{
#Html.Partial("_LogTime", new UserActivity(activity.ActivityId))
}
// _LogTime Partial View
#using (Html.BeginForm())
{
<fieldset>
#Html.HiddenFor(model => model.ActivityId)
#Html.EditorFor(model => model.Duration)
#Html.EditorFor(model => model.Date)
<input type="submit" value="LOG TIME" />
</fieldset>
}
Controller:
public class ActivityController : Controller
{
private readonly DbContext _db = new DbContext();
public ActionResult Index()
{
return View(_db.Activities.ToList());
}
[HttpPost]
public ActionResult Index(UserActivity activity)
{
if (ModelState.IsValid)
{
_db.UserActivities.Add(activity);
_db.SaveChanges();
}
return View(_db.Activities.ToList());
}
}
What you are experiencing is due to the fact that the html helper methods automatically update form elements with post variables of the same name. The values are stored in ModelState. One way to fix this is to remove the offending entry from ModelState.
Another possible fix is to do a redirect instead.
[HttpPost]
public ActionResult Index(UserActivity activity)
{
if (ModelState.IsValid)
{
_db.UserActivities.Add(activity);
_db.SaveChanges();
}
// Remove the ActivityId from your ModelState before returning the View.
ModelState.Remove("ActivityId")
return View(_db.Activities.ToList());
}
As witnessed by the comments below, use of the Remove method can indicate a deeper issue with the flow of your application. I do agree with Erik on that point. As he points out, redesigning the flow of an application can be a time consuming task.
When encountering the behavior indicated by the question, if there is a way to solve the problem without modifying ModelState, that would be a preferred solution. A case in point might be where more than a single element were affected by this issue.
For completeness, here is an alternate solution:
[HttpPost]
public ActionResult Index(UserActivity activity)
{
if (ModelState.IsValid)
{
_db.UserActivities.Add(activity);
_db.SaveChanges();
}
return RedirectToAction("Index");
}
Towards the end of silencing my critic, here is the rewrite that he could not come up with.
// Index View
#using (Html.BeginForm())
{
#for (var i = 0; i < Model.Count; i++)
{
<div>
#Html.HiddenFor(model => model[i].ActivityId)
#Html.EditorFor(model => model[i].Duration)
#Html.EditorFor(model => model[i].Date)
</div>
}
<input type="submit" value="LOG TIME ENTRIES" />
}
// Controller Post Method
[HttpPost]
public ActionResult Index(List<UserActivity> activities)
{
if (ModelState.IsValid)
{
foreach( var activity in activities )
{
var first = _db.UserActivities
.FirstOrDefault(row => row.ActivityId == activity.ActivityId );
if ( first == null ) {
_db.UserActivities.Add(activity);
} else {
first.Duration = activity.Duration;
first.Date = activity.Date;
}
}
_db.SaveChanges();
return RedirectToAction("index");
}
// when the ModelState is invalid, we want to
// retain posted values and display errors.
return View(_db.Activities.ToList());
}
I never use global variables in my Controller.
I rather put all my hidden values, also those in the foreach partial view, in the form.
That way, you pass the entire list and add one after that.
Now I think that you pass an empty row and add the last one to that.
To be sure, you can put a breakpoint in the post function.
#using (Html.BeginForm())
{
// Index View
#foreach (Activity activity in Model)
{
#Html.Partial("_LogTime", new UserActivity(activity.ActivityId))
}
// _LogTime Partial View
<fieldset>
#Html.HiddenFor(model => model.ActivityId)
#Html.EditorFor(model => model.Duration)
#Html.EditorFor(model => model.Date)
<input type="submit" value="LOG TIME" />
</fieldset>
}

MVC 4 ViewModel not being sent back to Controller

I can't seem to figure out how to send back the entire ViewModel to the controller to the 'Validate and Save' function.
Here is my controller:
[HttpPost]
public ActionResult Send(BitcoinTransactionViewModel transaction)
{
}
Here is the form in the view:
<li class="check">
<h3>Transaction Id</h3>
<p>#Html.DisplayFor(m => m.Transaction.TransactionId)</p>
</li>
<li class="money">
<h3>Deposited Amount</h3>
<p>#Model.Transaction.Amount.ToString() BTC</p>
</li>
<li class="time">
<h3>Time</h3>
<p>#Model.Transaction.Time.ToString()</p>
</li>
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post, new { transaction = Model }))
{
#Html.HiddenFor(m => m.Token);
#Html.HiddenFor(m => m.Transaction.TransactionId);
#Html.TextBoxFor(m => m.WalletAddress, new { placeholder = "Wallet Address", maxlength = "34" })
<input type="submit" value="Send" />
#Html.ValidationMessage("walletAddress", new { #class = "validation" })
}
When i click on submit, the conroller contains the correct value of the walletAddress field but transaction.Transaction.Time, transaction.Transaction.Location, transaction.Transaction.TransactionId are empty.
Is there a way i could pass the entire Model back to the controller?
Edit:
When i dont even receive the walletAddress in the controller. Everything gets nulled!
When i remove this line alone: #Html.HiddenFor(m => m.Transaction.TransactionId);
it works and i get the Token property on the controller, but when i add it back, all the properties of the transaction object on the controller are NULL.
Here is the BitcoinTransactionViewModel:
public class BitcoinTransactionViewModel
{
public string Token { get; set; }
public string WalletAddress { get; set; }
public BitcoinTransaction Transaction { get; set; }
}
public class BitcoinTransaction
{
public int Id { get; set; }
public BitcoinTransactionStatusTypes Status { get; set; }
public int TransactionId { get; set; }
public decimal Amount { get; set; }
public DateTime Time { get; set; }
public string Location { get; set; }
}
Any ideas?
EDIT: I figured it out, its in the marked answer below...
OK, I've been working on something else and bumpend into the same issue all over again.
Only this time I figured out how to make it work!
Here's the answer for anyone who might be interested:
Apparently, there is a naming convention. Pay attention:
This doesn't work:
// Controller
[HttpPost]
public ActionResult Send(BitcoinTransactionViewModel transaction)
{
}
// View
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post, new { transaction = Model }))
{
#Html.HiddenFor(m => m.Token);
#Html.HiddenFor(m => m.Transaction.TransactionId);
.
.
This works:
// Controller
[HttpPost]
public ActionResult Send(BitcoinTransactionViewModel **RedeemTransaction**)
{
}
// View
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post, new { **RedeemTransaction** = Model }))
{
#Html.HiddenFor(m => m.Token);
#Html.HiddenFor(m => m.Transaction.TransactionId);
.
.
In other words - a naming convention error! There was a naming ambiguity between the Model.Transaction property and my transaction form field + controller parameter. Unvelievable.
If you're experiencing the same problems make sure that your controller parameter name is unique - try renaming it to MyTestParameter or something like this...
In addition, if you want to send form values to the controller, you'll need to include them as hidden fields, and you're good to go.
The signature of the Send method that the form is posting to has a parameter named transaction, which seems to be confusing the model binder. Change the name of the parameter to be something not matching the name of a property on your model:
[HttpPost]
public ActionResult Send(BitcoinTransactionViewModel model)
{
}
Also, remove the htmlAttributes parameter from your BeginForm call, since that's not doing anything useful. It becomes:
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post))
Any data coming back from the client could have been tampered with, so you should only post back the unique ID of the transaction and then retrieve any additional information about it from your data source to perform further processing. You'll also want to verify here that the user posting the data has access to the specified transaction ID since that could've been tampered with as well.
This isn't MVC specific. The HTML form will only post values contained within form elements inside the form. Your example is neither inside the form or in a form element (such as hidden inputs). You have to do this since MVC doesn't rely on View State. Put hidden fields inside the form:
#Html.HiddenFor(x => x.Transaction.Time)
// etc...
Ask yourself though.. if the user isn't updating these values.. does your action method require them?
Model binding hydrates your view model in your controller action via posted form values. I don't see any form controls for your aforementioned variables, so nothing would get posted back. Can you see if you have any joy with this?
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post, new { transaction = Model }))
{
#Html.TextBoxFor(m => m.WalletAddress, new { placeholder = "Wallet Address", maxlength = "34" })
#Html.Hidden("Time", Model.Transaction.Time)
#Html.Hidden("Location", Model.Transaction.Location)
#Html.Hidden("TransactionId", Model.Transaction.TransactionId)
<input type="submit" value="Send" />
#Html.ValidationMessage("walletAddress", new { #class = "validation" })
}
Try to loop with the folowing statement not with FOREACH
<table>
#for (var i = 0; i < Model.itemlist.Count; i++)
{
<tr>
<td>
#Html.HiddenFor(x => x.itemlist[i].Id)
#Html.HiddenFor(x => x.itemlist[i].Name)
#Html.DisplayFor(x => x.itemlist[i].Name)
</td>
</tr>
}
</table>
Try Form Collections and get the value as. I think this may work.
public ActionResult Send(FormCollection frm)
{
var time = frm['Transaction.Time'];
}
Put all fields inside the form
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post))
and make sure that the model
BitcoinTransactionViewModel
included in view or not?
Can you just combine those 2 models you have? Here's how I do it with one model per view...
1. I use Display Templates from view to view so I can pass the whole model as well as leave data encrypted..
2. Setup your main view like this...
#model IEnumerable<LecExamRes.Models.SelectionModel.GroupModel>
<div id="container">
<div class="selectLabel">Select a Location:</div><br />
#foreach (var item in Model)
{
#Html.DisplayFor(model=>item)
}
</div>
3. Create a DisplayTemplates folder in shared. Create a view, naming it like your model your want to pass because a DisplayFor looks for the display template named after the model your using, I call mine GroupModel. Think of a display template as an object instance of your enumeration. Groupmodel Looks like this, I'm simply assigning a group to a button.
#model LecExamRes.Models.SelectionModel.GroupModel
#using LecExamRes.Helpers
#using (Html.BeginForm("Index", "Home", null, FormMethod.Post))
{
<div class="mlink">
#Html.AntiForgeryToken()
#Html.EncryptedHiddenFor(model => model.GroupKey)
#Html.EncryptedHiddenFor(model => model.GroupName)
<p>
<input type="submit" name="gbtn" class="groovybutton" value=" #Model.GroupKey ">
</p>
</div>
}
4. Here's the Controller.
*GET & POST *
public ActionResult Index()
{
// Create a new Patron object upon user's first visit to the page.
_patron = new Patron((WindowsIdentity)User.Identity);
Session["patron"] = _patron;
var lstGroups = new List<SelectionModel.GroupModel>();
var rMgr = new DataStoreManager.ResourceManager();
// GetResourceGroups will return an empty list if no resource groups where found.
var resGroups = rMgr.GetResourceGroups();
// Add the available resource groups to list.
foreach (var resource in resGroups)
{
var group = new SelectionModel.GroupModel();
rMgr.GetResourcesByGroup(resource.Key);
group.GroupName = resource.Value;
group.GroupKey = resource.Key;
lstGroups.Add(group);
}
return View(lstGroups);
}
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult Index(SelectionModel.GroupModel item)
{
if (!ModelState.IsValid)
return View();
if (item.GroupKey != null && item.GroupName != null)
{
var rModel = new SelectionModel.ReserveModel
{
LocationKey = item.GroupKey,
Location = item.GroupName
};
Session["rModel"] = rModel;
}
//So now my date model will have Group info in session ready to use
return RedirectToAction("Date", "Home");
}
5. Now if I've got alot of Views with different models, I typically use a model related to the view and then a session obj that grabs data from each model so in the end I've got data to submit.
The action name to which the data will be posted should be same as the name of the action from which the data is being posted. The only difference should be that the second action where the data is bein posted should have [HttpPost] and the Posting method should serve only Get requests.

List of models in Model in MVC

I have two models:
class ModelIn{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
}
class ModelOut{
public ModelOut(){ People = new List<ModelIn>();}
public List<ModelIn> People { get; private set;}
public string Country { get; set; }
}
And I have Controller editing ModelOut:
public ActionResult People()
{
...
return View(SomeModelOutInstanceWith3People);
}
[HttpPost]
public ActionResult(ModelOut items)
{
...
}
In view I have sth like:
<% using (Html.BeginForm()) { %>
<%: Html.EditorFor(m => Model.Country) %>
<% for(int i = 0; i < Model.People.Count; ++i){ %>
<%: Html.EditorFor(m => Model.People[i].FirstName) %>
<%: Html.EditorFor(m => Model.People[i].LastName) %>
<%: Html.EditorFor(m => Model.People[i].Address) %>
<% } %>
<input type="submit" />
<% } %>
It works all OK, but in post action I have empty ModelOut items.
I can see in logs that data is sent correctly.
I have tried everything, nothing works.
Have you tried simply <%: Html.EditorFor(m => m.People) %> ?
MVC should loop through the list by itself.
Also, watch out how you specify your lambdas, it should be m => m rather than m => Model.
PS. I use MVC3...
The reason for your problem could be a naming mismatch...from what I remember the default model binder does not do its job properly because of this naming mismatch....this means you need to specify more info to the model binder to do its job better... try updating your View code to use the below code for each property...
<%= Html.EditorFor(string.Format("People[{0}].FirstName", i), Model.People[i].FirstName) %>
The above view code will generate the following markup
<input id="People_0__FirstName" name="People[0].FirstName" type="text" />
I might have a syntactical problem above but I guess you can get it right with the help of Visual Studio
#Dai were right. MVC let me use items for model instance name when it is instance of List, but doesn't let me use it for ModelOut.
After renaming items to model it works fine.

Categories