如何通过 POST 请求携带复杂的对象模型



我有以下实体模型:

public class AssetLabel
{
    public string QRCode { get; set; }
    public string asset { get; set; }
    public virtual IEnumerable<Conversation> Conversations { get; set; }
}
public class Conversation
{
    public int ID { get; set; }
    public virtual AssetLabel AssetLabel{ get; set; }
    public string FinderName { get; set; }
    public string FinderMobile { get; set; } 
    public string FinderEmail  { get; set; }
    public  ConversationStatus Status{ get; set; }
    public IEnumerable<ConversationMessage> Messages { get; set; }
}
public class ConversationMessage
{
    public int ID { get; set; }
    public DateTime MessageDateTime { get; set; }
    public bool IsFinderMessage { get; set; }
    public virtual Conversation Conversation { get; set; }
}
public enum ConversationStatus { open, closed };
public class FinderViewModel : Conversation
{/*used in Controllers->Found*/

}

我的 MVC 应用程序将提示对 POST 请求进行QRCode。 然后,我验证此代码是否存在于数据库AssetLabel,并且满足其他一些服务器端逻辑。然后,我需要请求用户联系人详细信息以创建新的Conversation记录。目前,我有一个 GET 到控制器操作,该操作返回捕获代码的第一个表单。 如果这是有效的,那么我创建一个新FinderViewModel,用QRCode的对象填充AssetLabel,并返回一个视图来使用虚拟机并显示NameMobileEmail的字段。我的问题是,尽管AssetLabel作为FinderViewModel的一部分传递给视图,但我可以显示AssetLabel中的字段;图形对象 AssetLabel不会在 POST 中传回。我知道我可以修改FinderViewModel,以便它将Conversation作为一个属性,并将QRCode设置为一个单独的属性,该属性可能是表单中的隐藏字段,然后重新找到AssetLabel作为处理的一部分第二个表单,但这感觉像是很多工作,因为我已经验证过一次才能达到目的创建第二种形式(这就是我远离PHP MVC框架的原因(。

第一个问题是如何?,第二个问题是我是否以错误的方式接近这种设计模式。 还有更多.通过多种形式持久化数据的方法是什么? 在我学习的这一点上,我真的不想将信息存储在cookie中或使用ajax。

下面显示了第一个形式的 POST、第二个视图和第二个窗体 POST 的其余代码(简化以消除不相关的逻辑(。

public class FoundController : Controller
{
    private ApplicationDbContext db = new ApplicationDbContext();
    // GET: Found
    public ActionResult Index()
    {
        AssetLabel lbl = new AssetLabel();
        return View(lbl);
    }
    [HttpPost]
    public ActionResult Index(string QRCode)
    {
        if (QRCode=="")
        {
            return Content("no value entered");
        }
        else
        {
            /*check to see if code is in database*/
            AssetLabel lbl = db.AssetLables.FirstOrDefault(q =>q.QRCode==QRCode);
            if (lbl != null)
            {
                var vm = new FinderViewModel();
                vm.AssetLabel = lbl;
                vm.Status = ConversationStatus.open;
                return View("FinderDetails", vm);
            }
            else
            {/*Label ID is not in the database*/
                return Content("Label Not Found");
            }
        }
    }
    [HttpPost]
    public ActionResult ProcessFinder(FinderViewModel vm)
    {
        /*
        THIS IS WHERE I AM STUCK! - vm.AssetLabel == NULL even though it
        was passed to the view with a fully populated object
        */
        return Content(vm.AssetLabel.QRCode.ToString());
        //return Content("Finder Details posted!");
    }

FinderView.cshtml

@model GMSB.ViewModels.FinderViewModel
@{
     ViewBag.Title = "TEST FINDER";
}
<h2>FinderDetails</h2>
@using (Html.BeginForm("ProcessFinder","Found",FormMethod.Post))
{
    @Html.AntiForgeryToken()
    <div class="form-horizontal">
    <h4>Finder Details</h4>
    <hr />
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })
    @Html.HiddenFor(model => model.ID)
    @Html.HiddenFor(model => model.AssetLabel)
    <div class="form-group">
        @Html.LabelFor(model => model.FinderName, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.FinderName, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.FinderName, "", new { @class = "text-danger" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(model => model.FinderMobile, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.FinderMobile, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.FinderMobile, "", new { @class = "text-danger" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(model => model.FinderEmail, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.FinderEmail, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.FinderEmail, "", new { @class = "text-danger" })
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" value="Save" class="btn btn-default" />
        </div>
    </div>
</div>

}

资产标签呈现的 HTML 代码段

<input id="AssetLabel" name="AssetLabel" type="hidden"       
value="System.Data.Entity.DynamicProxies.AssetLabel_32653C4084FF0CBCFDBE520EA1FC5FE4F22B6D9CD6D5A87E7F1B7A198A59DBB3" 
/>

不能使用 @Html.HiddenFor() 为复杂对象生成隐藏输出。在内部,该方法使用 .ToString() 生成value(在您的情况下,输出System.Data.Entity.DynamicProxies.AssetLabel_32653C4084FF0CBCFDBE520EA1FC5FE4F22B6D9CD6D5A87E7F1B7A198A59DBB3无法绑定回复杂对象(

您可以为AssetLabel的每个属性生成一个表单控件 - 但这在您的情况下是不现实的,因为AssetLabel包含一个属性,而 是Conversation的集合,而又包含ConversationMessage的集合,因此您需要嵌套的for循环来为ConversationConversationMessage的每个属性生成输入。

但是,向客户端发送大量额外数据,然后再次将其全部发送回原封不动会降低性能,向恶意用户公开有关数据和数据结构的不必要详细信息,恶意用户可能会更改数据(。

FinderViewModel应该只包含 QRCode 的属性(或 AssetLabel 的 ID 属性(和视图中

@Html.HiddenFor(m => m.QRCode)
然后在 POST 方法中,

如果您需要AssetLabel,就像您在 GET 方法中一样从存储库中再次获取它(尽管不清楚为什么您需要在 POST 方法中AssetLabel(。

作为旁注,在编辑数据时,视图模型应仅包含视图中所需的属性,而不应包含作为数据模型的属性(在您的情况下从数据模型继承(。请参阅什么是 MVC 中的视图模型?。根据您的视图,它应该包含 4 个属性FinderNameFinderMobileFinderEmailQRCode(如果您想使用它来编辑现有对象,则包含 int? ID(。

谢谢斯蒂芬。QRCode 是 AssetLabel 上的 PK 和对话中的 FK,因此需要通过工作流进行跟踪。 我试图保持 viewModel 通用,以便它可以用于其他表单,而不是将其紧密耦合到这个特定表单,并且我试图传递 AssetLabel,因为我已经对它的状态进行了大量的验证我不想重复。 我计算出了我需要做什么 - 如果你使用 @Html.Hidden(model => 模型。AssetLabel.QRCode(,则表单字段名称将变为AssetLabel_QRCode,并自动映射到 POST 视图模型中的正确位置。为了促进代码重用并避免以后进行任何返工,我在显示模板中创建了此逻辑,其中字段定义为隐藏,然后使用 overload 方法@Html.Partial((,该方法允许我将模型扩展定义为表单名称

@Html.Partial
(
    "./Templates/Assetlabel_hidden", 
    (GMSB.Models.AssetLabel)(Model.AssetLabel), 
    new ViewDataDictionary()
    {
        TemplateInfo = new TemplateInfo()
        {
            HtmlFieldPrefix = "AssetLabel"
        }
    }
)

但是您是绝对正确的,这公开了其他字段和我的应用程序结构。 我想我将重新起草 viewModel 以仅公开必要的字段并将 AssetLabel 验证移动到一个单独的私有函数,该函数可以从初始 POST 和后续帖子调用。 这确实意味着控制器中有额外的代码,因为平面 vm 字段需要手动映射到复杂对象图。

最新更新