MVC 4 ViewModel没有被发送回Controller

29

我似乎无法弄清如何将整个ViewModel发送回控制器以进行“验证和保存”功能。

这是我的控制器:

[HttpPost]
public ActionResult Send(BitcoinTransactionViewModel transaction)
{
}

这是视图中的表单:

<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" })
}
当我点击提交时,控制器中包含walletAddress字段的正确值,但transaction.Transaction.Time、transaction.Transaction.Location和transaction.Transaction.TransactionId为空。
有没有办法将整个Model传回控制器呢?
编辑:
当我在控制器中甚至不接收walletAddress时。一切都被设置为null! 当我仅删除此行时:@Html.HiddenFor(m => m.Transaction.TransactionId); 它可以工作并且我在控制器上获得Token属性,但是当我将其添加回去时,控制器上的transaction对象的所有属性都为NULL。
这是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; }
    }
任何想法?
编辑:我找到答案了,在下面标记的答案中...
9个回答

38

好的,我一直在做其他事情,结果又遇到了同样的问题。但这次我想出了解决方法!

这里是对任何有兴趣的人的答案:

显然,有一种命名约定。请注意:

这个不起作用:

// 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);
.
.

这样做是有效的:

// 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);
.
.

换句话说,这是一个命名规范错误!Model.Transaction属性和我的transaction表单字段+控制器参数之间存在名称歧义。难以置信。

如果您遇到同样的问题,请确保您的控制器参数名称是唯一的 - 尝试将其重命名为MyTestParameter或类似的名称...

此外,如果您想将表单值发送到控制器,您需要将它们作为隐藏字段包含在内,然后就可以继续进行了。


2
我喜欢这个答案,它很有道理,但不幸的是对我没用。似乎我陷入了一个死胡同,在这里无论我尝试什么,子对象都无法传回控制器。真是让人沮丧的事情。 - Mike Rouse
我曾经遇到过同样的问题。我的ViewModel名字叫做FileViewModel,而我的action参数名字是"file"。我把它改成了"_file",然后问题就解决了。 - Deise Vicentin
伟大的人...我根本就没有想到!!只是想知道你是怎么知道的? - Milind Thakkar
你不需要在BeginForm上声明返回的ViewModel,但是你需要在Action方法中将其声明为参数。 - CyberNinja

23

表单提交的 Send 方法的签名有一个参数名为 transaction,这似乎让模型绑定器感到困惑。将参数名更改为不与您的模型上的属性名称匹配的名称:

[HttpPost]
public ActionResult Send(BitcoinTransactionViewModel model)
{
}

另外,从您的BeginForm调用中删除htmlAttributes参数,因为它没有任何有用的作用。变成:

@using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post))

客户端发送回来的数据都有可能被篡改,所以你应该只返回交易的唯一ID,然后从数据源检索任何额外的信息以执行进一步的处理。在此还需要验证提交数据的用户是否具有访问指定交易ID的权限,因为这也可能被篡改。


1
我的问题是如何发布整个对象,而不仅仅是它的唯一标识符。这是因为我不想在数据库中运行选择,也不想在服务器端管理数据缓存。 - Uri Abramson
要发布整个对象,您需要执行其他人选择的操作并为每个属性创建隐藏输入。 重命名Send方法的参数将解决整个模型为空的问题。 - Jonathan Little
警告:这是不区分大小写的。 - Stonedecroze

11

这不是MVC特定的。HTML表单只会发布表单元素内包含的值。您的示例既不在表单内部,也不在表单元素(如隐藏输入)中。你必须这样做,因为MVC不依赖于View State。将隐藏字段放置表单内:

@Html.HiddenFor(x => x.Transaction.Time)
// etc...

不过,你可以问自己...如果用户没有更新这些值...那么你的操作方法是否需要它们呢?


2
谢谢您的回答。我现在有一个不同的问题:当我为第一级别(x=>x.Token)中的属性创建隐藏字段时,它可以工作,但是当我为内部对象(X=>x.Transaction.InnerField)创建时,它就无法工作了!突然间控制器中事务对象的所有属性都为空了!您有任何想法为什么会发生这种情况吗? - Uri Abramson
1
它需要它们是因为当验证失败时,我希望使用相同的表单呈现验证错误,而不是从数据库中选择对象(而是使用我已经拥有的对象)。 - Uri Abramson
你能否在表单声明中删除 transaction = Model 部分并重试? - Simon Whitehead

10

通过提交的表单值,模型绑定可以在您的控制器操作中使您的视图模型生效。我没有看到任何针对您上述变量的表单控件,因此将不会有任何内容被提交回来。您能否查看您是否有任何解决方案呢?

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

当我为内部属性呈现隐藏字段时,接收控制器中的所有属性突然变为空! - Uri Abramson
更新了,你能再试一下吗? - Mister Epic
这个无法构建,因为Transaction不是一个数组。它只是一个对象,请参见更新的问题。 - Uri Abramson

4

尝试使用以下语句而不是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>

1
尝试使用表单集合并获取值。我认为这可能有效。
public ActionResult Send(FormCollection frm)
{
    var time = frm['Transaction.Time'];
}

0

你能否将你拥有的这两个模型合并吗?以下是我使用每个视图一个模型的方法: 1. 我使用显示模板从一个视图到另一个视图,这样我可以传递整个模型以及保留数据加密。 2. 设置你的主视图如下:

@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.在shared文件夹中创建一个DisplayTemplates文件夹。创建一个视图,命名为您想要传递的模型名称,因为DisplayFor会查找以模型名称命名的显示模板,我将其称为GroupModel。将显示模板视为您枚举的对象实例。GroupModel看起来像这样,我只是将一个组分配给一个按钮。
@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. 这是控制器。 *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. 现在如果我有很多不同模型的视图,我通常使用与视图相关的模型,然后使用一个会话对象从每个模型中获取数据,最终得到要提交的数据。


0
将所有字段放在表单内。
 @using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post))

并确保模型

 BitcoinTransactionViewModel

包含在视图中吗?


0

数据将被发布到的操作名称应与数据被发布的操作名称相同。唯一的区别是第二个操作,其中数据被发布应该有 [HttpPost],并且发布方法应仅服务于 Get 请求。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接