Как передать сложную объектную модель через 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 запросит QRCode в запросе POST. Затем я проверяю, существует ли этот код в базе данных AssetLabel и выполняется ли какая-то другая логика на стороне сервера. Затем мне нужно запросить контактные данные пользователя, чтобы создать новую запись Conversation. В настоящее время у меня есть GET для действия контроллера, которое возвращает первую форму для захвата кода. Если это действительно так, я создаю новый FinderViewModel, заполняю AssetLabel объектом для QRCode и возвращаю представление для использования виртуальной машины и отображения полей для Name, Mobile и Email. Моя проблема в том, что хотя AssetLabel передается в представление как часть FinderViewModel, и я могу отображать поля из AssetLabel; графический объект AssetLabel не передается обратно в POST. Я знаю, что могу изменить FinderViewModel так, чтобы он принимал Conversation как одно свойство и настраивал QRCode как отдельное свойство, которое могло бы быть скрытым полем в форме, а затем повторно находил AssetLabel как часть обработки второго форма, но это похоже на большую работу, поскольку я уже проверил ее один раз, чтобы перейти к созданию второй формы (вот почему я отхожу от фреймворков PHP MVC).

Первый вопрос — КАК? Второй вопрос — правильно ли я подхожу к этому шаблону проектирования. Есть ли более .NETty способ сохранения данных через несколько форм? На данный момент в моем обучении я действительно не хочу хранить информацию в файле cookie или использовать ajax.

Для справки остальная часть кода для 1-й формы POST, 2-го представления и 2-й формы 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 для AssetLabel

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

person Aaron Reese    schedule 12.11.2016    source источник


Ответы (2)


Вы не можете использовать @Html.HiddenFor() для создания скрытого вывода для сложного объекта. Внутри метод использует .ToString() для генерации value (в вашем случае вывод System.Data.Entity.DynamicProxies.AssetLabel_32653C4084FF0CBCFDBE520EA1FC5FE4F22B6D9CD6D5A87E7F1B7A198A59DBB3, который не может быть привязан к сложному объекту)

Вы можете сгенерировать элемент управления формы для каждого свойства AssetLabel, но это было бы нереалистично в вашем случае, потому что AssetLabel содержит свойство с набором Conversation, который, в свою очередь, содержит набор ConversationMessage, поэтому вам понадобятся вложенные for циклы для создания ввод для каждого свойства Conversation и ConversationMessage.

Но отправка большого количества дополнительных данных клиенту, а затем отправка их обратно без изменений снижает производительность, раскрывает ненужные сведения о ваших данных и структуре данных для злоумышленников, а злоумышленники могут изменить данные).

FinderViewModel должен просто содержать свойство для QRCode (или свойство ID для AssetLabel) и в представлении

@Html.HiddenFor(m => m.QRCode)

Затем в методе POST, если вам нужно AssetLabel, снова получите его из репозитория так же, как вы делаете это в методе GET (хотя непонятно, зачем вам нужно AssetLabel в методе POST).

В качестве примечания модель представления должна содержать только те свойства, которые необходимы в представлении, и не должна содержать свойства, которые являются моделями данных (в вашем случае наследуются от модели данных) при редактировании данных. См. Что такое ViewModel в MVC?. В соответствии с вашим представлением он должен содержать 4 свойства FinderName, FinderMobile, FinderEmail и QRCodeint? ID, если вы хотите использовать его для редактирования существующих объектов).

person Community    schedule 12.11.2016

Спасибо, Стивен. QRCode — это PK в AssetLabel и FK в Conversation, поэтому его необходимо отслеживать в рабочем процессе. Я пытался сохранить общую модель представления, чтобы ее можно было использовать для других форм, а не жестко связывать ее с этой конкретной формой, и я пытался передать AssetLabel, поскольку я уже провел значительную проверку его состояния, которое я не хотел повторяться. Я понял, что мне нужно сделать. Если вы используете @Html.Hidden(model => model.AssetLabel.QRCode), то имя поля формы становится AssetLabel_QRCode и автоматически сопоставляется с правильным местом в модели представления POST. Чтобы способствовать повторному использованию кода и избежать каких-либо переделок позже, я создал эту логику в шаблоне отображения с полями, определенными как скрытые, а затем @Html.Partial(), используя метод перегрузки, который позволяет мне определить расширение модели для имен форм.

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

Но вы абсолютно правы, это открывает дополнительные поля и структуру моего приложения. Я думаю, что я переработаю viewModel, чтобы выставить только необходимые поля и переместить проверку AssetLabel в отдельную приватную функцию, которую можно вызывать как из начального POST, так и из последующего сообщения. Это означает дополнительный код в контроллере, поскольку плоские поля vm необходимо вручную сопоставлять со сложным графом объектов.

person Aaron Reese    schedule 12.11.2016