MVC 4 ViewModel not being sent back to Controller
Asked Answered
C

9

29

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...

Convector answered 18/8, 2013 at 13:37 Comment(0)
C
38

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.

Convector answered 4/11, 2013 at 10:4 Comment(4)
I like this answer and it makes sense, but unfortunately didn't work for me. It seems I'm stuck in a dead end where no matter what I try I can't get the sub-object to come back into the controller. Such a frustrating thing.Costume
I had the same issue. I had a ViewModel named FileViewModel and my action parameter was named "file". I changed it to "_file" and it worked for me.Fellowman
Great man...It wouldn't have occurred to me at all !! Just wanted to know how did you come to know ?Succussion
you dont declare the returning viewmodel on the beginform, but you have to declare it as a parameter on the action.Lithea
C
23

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.

Crittenden answered 23/8, 2013 at 16:7 Comment(3)
My question is how to post the entire object, not just its unique ID. That's because i DON'T want to run a select against my database nor manage data cache on server side.Convector
To post the entire object you need to do what the others have selected and create hidden inputs for each property. Renaming the parameter of the Send method will fix the issue with your entire model being null.Crittenden
Word of warning this is NOT case sensitive,Alternative
S
11

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?

Screwy answered 18/8, 2013 at 13:40 Comment(3)
Thanks for the answer. I have a different problem now: When i create a hidden field for a property in the first level (x=>x.Token) it works but when i do it for the inner object (X=>x.Transaction.InnerField) this doesnt work! suddenly all properties of the transaction object in the controller are null! Do you have an idea why this happens?Convector
It requires them because when validation fails, i want to render the same form with validation errors and not select the object from the database (but use the one i already have instead)Convector
Can you remove the transaction = Model part in the form declaration and retry?Screwy
A
10

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" })
}
Aureolin answered 18/8, 2013 at 13:45 Comment(3)
When i render a hidden field for an inner propery, suddenly all properties in receiving controller get nulled!Convector
Updated, can you give it another whirl?Aureolin
This doesn't Build becase Transaction is not an Array. It's just an object, please see in the updated question.Convector
O
4

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>
Octangle answered 11/11, 2015 at 18:3 Comment(0)
M
1

Try Form Collections and get the value as. I think this may work.

public ActionResult Send(FormCollection frm)
{
    var time = frm['Transaction.Time'];
}
Miseno answered 29/8, 2013 at 13:18 Comment(0)
W
0

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?

Woll answered 27/8, 2013 at 9:48 Comment(0)
P
0

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.

Purport answered 29/8, 2013 at 3:58 Comment(0)
T
0

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.

Tartar answered 5/2, 2016 at 8:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.