Дочерние объекты Entity Framework TryUpdateModel?

#asp.net #asp.net-mvc #asp.net-mvc-3 #entity-framework #entity-framework-4.1

#asp.net #asp.net-mvc #asp.net-mvc-3 #entity-framework #entity-framework-4.1

Вопрос:

Я знаю, что большинство из вас предположит, что я должен использовать ViewModels, специфичные для формы, которую я использую, но мне любопытно, почему мой дочерний объект не привязан к TryUpdateModel.

 @using (Html.BeginForm()) {
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>User</legend>

        @Html.HiddenFor(model => model.UserId)
    @Html.HiddenFor(model => model.PrimaryAddress.AddressId)
    <div class="editor-label">
            @Html.LabelFor(model => model.PrimaryAddress.FirstName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.PrimaryAddress.FirstName)
            @Html.ValidationMessageFor(model => model.PrimaryAddress.FirstName)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.PrimaryAddress.LastName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.PrimaryAddress.LastName)
            @Html.ValidationMessageFor(model => model.PrimaryAddress.LastName)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.UserName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.UserName)
            @Html.ValidationMessageFor(model => model.UserName)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Email)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Email)
            @Html.ValidationMessageFor(model => model.Email)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.IsApproved)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.IsApproved)
            @Html.ValidationMessageFor(model => model.IsApproved)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.IsEmployee)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.IsEmployee)
            @Html.ValidationMessageFor(model => model.IsEmployee)
        </div>

        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}
  

И код контроллера:

 [HttpPost]
public ActionResult Edit(int id, FormCollection form)
{
    var user = Token.DB.Users.Include("PrimaryAddress").Single(x => x.UserId == id);
    if (TryUpdateModel(user, new string[] { "UserName", "Email", "IsApproved", "IsEmployee", "PrimaryAddress.FirstName", "PrimaryAddress.LastName" }))
    {
        try
        {
            Token.DB.SaveChanges();
            return RedirectToAction("index");
        }
        catch (Exception ex)
        {
            while (ex.InnerException != null)
                ex = ex.InnerException;
            if (ex.Message.ToLowerInvariant().Contains("unique"))
                ModelState.AddModelError("UserName", "UserName already exists");
        }
    }
    return View(User);
}
  

Код не генерирует никаких исключений, он просто не заполняет пользователя.PrimaryAddress.Имя пользователя или пользователя.PrimaryAddress.Фамилия из формы. Я хотел бы знать, почему?

Я уже знаю, что могу исправить проблему с помощью определенной ViewModel и отображения информации в фоновом режиме. Я также могу сделать что-то вроде этого:

 <!-- Edit.cshtml -->
<div class="editor-field">
    @Html.EditorFor(model => model.PrimaryAddress.FirstName, null, "FirstName")
    @Html.ValidationMessageFor(model => model.PrimaryAddress.FirstName)
</div>

<div class="editor-label">
    @Html.LabelFor(model => model.PrimaryAddress.LastName)
</div>
<div class="editor-field">
    @Html.EditorFor(model => model.PrimaryAddress.LastName, null, "LastName")
    @Html.ValidationMessageFor(model => model.PrimaryAddress.LastName)
</div>

// UsersController.cs
if (TryUpdateModel(user, new string[] { "UserName", "Email", "IsApproved", "IsEmployee"})
amp;amp; TryUpdateModel(user.PrimaryAddress, new string[] {"FirstName", "LastName" }))
  

Итак, реальный вопрос в том, почему он не является обязательным в первом примере?

Комментарии:

1. Не уверен, почему -1, четко сформулировал мой вопрос и привел примеры. Должен ли я просто слепо следовать девизу MVC purist о просмотре конкретных моделей для всего, не понимая, что происходит под капотом? Спасибо, Дарин, за объяснение, что TryUpdateModel не поддерживает «вложенные свойства».

Ответ №1:

Итак, реальный вопрос в том, почему он не является обязательным в первом примере?

Ответ на ваш вопрос очень прост: ни UpdateModel, ни TryUpdateModel, ни [Bind] атрибут не поддерживают «вложенные свойства» в списке свойств включения / исключения. Так что делайте все правильно и используйте модели представления. Защита от атак массового присвоения свойств — это только одна из миллионов причин, по которым вы должны использовать модели представлений. Ну, вы, кажется, нашли обходной путь, выполнив второй TryUpdateModel , но если у вас много свойств для этого доменного объекта, код действия вашего контроллера может быстро превратиться в спагетти-код.

Комментарии:

1. Я не говорил, что не хочу использовать модели представлений; Я пытался выяснить, почему вложенные свойства не заполняются. Другие примеры, которые я видел в Интернете, казалось, намекали на то, что они должны быть. Мне также интересно ваше беспокойство по поводу «кода спагетти-сантехники»? Что вас беспокоит в приведенном выше коде? В связи с этим, есть ли у вас какие-либо ссылки на упомянутые вами «атаки с массовым внедрением свойств»? Я пробовал искать, и я продолжаю получать кучу несвязанных вещей при внедрении зависимостей.

2. @Сэм, извини, я имел в виду mass property assignment . Такие фреймворки, как RoR или ASP.NET MVC, которые используют какую-то привязку модели для сопоставления с объектами, уязвимы для такого рода атак. Например, у вас есть действие POST, которое создает новую модель пользователя в базе данных. Итак, вы создаете красивую форму, которая позволяет пользователю вводить свое имя и пароль и создавать кнопку. Но, к сожалению, вы не следуете рекомендациям по использованию моделей представлений, и ваше действие POST принимает непосредственно вашу модель пользователя домена в качестве аргумента. За исключением того, что эта модель домена имеет IsAdmin логическое свойство…

3. … Конечно, в форме нет соответствующего поля, позволяющего обычному пользователю устанавливать значение этого свойства, но злоумышленник может подделать запрос и установить isAdmin=true . Привязка модели по умолчанию просто назначит эту модель пользовательского домена, и вы сохраните ее в базе данных. Хакер только что создал учетную запись администратора в вашей системе. Итак, чтобы предотвратить такого рода атаки, вы используете параметр исключить / включить свойства…

4. … и вы включаете только те свойства, которые необходимо установить: имя пользователя и пароль в модели домена. Это именно то, что делает второй инструмент TryUpdateModel. За исключением того, что он не работает с вложенными свойствами, как вы уже обнаружили. Что касается кода, который меня беспокоит, действия контроллера well должны быть как можно более тонкими. Они не должны содержать такого кода привязки. Это не их ответственность. Я бы порекомендовал вам посмотреть видео Джимми Богарда о том, как поставить контроллеры на диету: viddler.com/explore/mvcconf/videos/1

5. Спасибо за ссылку на видео. Я посмотрю видео сегодня вечером. Разве программный код не должен куда-то идти? Если я использую модель представления, она должна быть где-то сопоставлена с объектом entity framework. Мне нужно добавить какое-то дополнение к контроллеру, чтобы передать модель представления в репозиторий. Мне нужно написать репозиторий для обработки всего сопоставления. Разве это не значительно больше работы, чем использование одной строки кода со свойствами включения / исключения?

Ответ №2:

Я нахожу несколько случаев, когда я хочу сделать то же самое. Я также обнаружил, что дочерние объекты не работают, но списки / коллекции дочерних объектов работают очень странно.

Я нашел хорошую ссылку, описывающую, как обойти это.

В итоге это выглядит примерно так :

 [AcceptVerbs(HttpVerbs.Post)]
public ActionResult Save(int id, FormCollection collection)
{
    User user = null;
    if (id == 0)
    {
        user = new User();
        UpdateModel(user, "User");
        user.Contact = new Contact();
        UpdateModel(user.Contact, "User.Contact");
        user.Contact.Addresses = new EntitySet<Address>();
        UpdateModel(user.Contact.Addresses, "User.Contact.Addresses");
    }
    else
    {
        // get current user object from DB, however you normally do this is fine.
        user = userRepository.GetById(id);
        UpdateModel(user, "User");
        UpdateModel(user.Contact, "User.Contact");
        UpdateModel(user.Contact.Addresses, "User.Contact.Addresses");
    }
    // at this point, model "user" and children would have been updated.
}
...