Uri Abramson
Uri Abramson

Reputation: 6175

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

Upvotes: 29

Views: 83732

Answers (9)

Saruchi
Saruchi

Reputation: 366

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.

Upvotes: 0

Valynk
Valynk

Reputation: 477

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>

Upvotes: 4

Uri Abramson
Uri Abramson

Reputation: 6175

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.

Upvotes: 38

Nowshath
Nowshath

Reputation: 842

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

public ActionResult Send(FormCollection frm)
{
    var time = frm['Transaction.Time'];
}

Upvotes: 1

foxtrotZulu
foxtrotZulu

Reputation: 1119

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.

Upvotes: 0

Boss
Boss

Reputation: 475

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?

Upvotes: 0

Jonathan Little
Jonathan Little

Reputation: 550

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.

Upvotes: 23

Mister Epic
Mister Epic

Reputation: 16723

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" })
}

Upvotes: 10

Simon Whitehead
Simon Whitehead

Reputation: 65079

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?

Upvotes: 11

Related Questions