2012-06-07 1 views
9

In Short: Как успешно редактировать запись БД без необходимости включать каждое отдельное поле для Модели внутри Редактирования вида?Успешная Редактирование Модели без кучи скрытых полей

UPDATE
Так у меня есть элемент в БД (статьи). Я хочу отредактировать статью. Статья, которую я редактирую, имеет много свойств (Id, CreatedBy, DateCreated, Title, Body). Некоторые из этих свойств никогда не должны меняться (например, Id, CreatedBy, DateCreated). Поэтому в моем Редактированном представлении мне нужны только поля ввода для полей, которые можно изменить (например, Title, Body). Когда я реализую Edit View, как это, сбой привязки модели. Любые поля, в которые я не подавал входные данные, получают значение «по умолчанию» (например, DateCreated устанавливается в 01/01/0001 12:00:00). Если I do подает входные данные для каждого поля, все работает нормально, и статья редактируется, как ожидалось. Я не знаю, правильно ли сказать, что «сбой привязки модели» обязательно, так как «система заполняет поля неверными данными, если в окне редактирования не было введено поле ввода».

Как создать редактируемый вид таким образом, чтобы мне нужно было вводить поля ввода для полей, которые могут/нуждаются в редактировании, так что, когда вызывается метод Edit в контроллере, поля, такие как DateCreated, заполняются правильно , и не установлено какое-либо значение по умолчанию, неправильное значение? Вот мой Изменить способ, как это в настоящее время составляет:

[HttpPost] 
    public ActionResult Edit(Article article) 
    { 
     // Get a list of categories for dropdownlist 
     ViewBag.Categories = GetDropDownList(); 


     if (article.CreatedBy == (string)CurrentSession.SamAccountName || (bool)CurrentSession.IsAdmin) 
     {     
      if (ModelState.IsValid) 
      { 
       article.LastUpdatedBy = MyHelpers.SessionBag.Current.SamAccountName; 
       article.LastUpdated = DateTime.Now; 
       article.Body = Sanitizer.GetSafeHtmlFragment(article.Body); 

       _db.Entry(article).State = EntityState.Modified; 
       _db.SaveChanges(); 
       return RedirectToAction("Index", "Home"); 
      } 
      return View(article); 
     } 

     // User not allowed to edit 
     return RedirectToAction("Index", "Home"); 
    } 

И Изменить вид, если это поможет:

. . . 
@using (Html.BeginForm()) { 
@Html.ValidationSummary(true) 

<fieldset> 
    <legend>Article</legend> 

    <p> 
     <input type="submit" value="Save" /> | @Html.ActionLink("Back to List", "Index") 
    </p> 

    @Html.Action("Details", "Article", new { id = Model.Id }) 

    @Html.HiddenFor(model => model.CreatedBy) 
    @Html.HiddenFor(model => model.DateCreated) 

    <div class="editor-field"> 
     <span> 
      @Html.LabelFor(model => model.Type) 
      @Html.DropDownListFor(model => model.Type, (SelectList)ViewBag.Categories) 
      @Html.ValidationMessageFor(model => model.Type) 
     </span> 
     <span> 
      @Html.LabelFor(model => model.Active) 
      @Html.CheckBoxFor(model => model.Active) 
      @Html.ValidationMessageFor(model => model.Active) 
     </span> 
     <span> 
      @Html.LabelFor(model => model.Stickied) 
      @Html.CheckBoxFor(model => model.Stickied) 
      @Html.ValidationMessageFor(model => model.Stickied) 
     </span>    
    </div> 

    <div class="editor-label"> 
     @Html.LabelFor(model => model.Title) 
    </div> 
    <div class="editor-field"> 
     @Html.EditorFor(model => model.Title) 
     @Html.ValidationMessageFor(model => model.Title) 
    </div> 
    <div class="editor-label"> 
     @Html.LabelFor(model => model.Body) 
    </div> 
    <div class="editor-field"> 
     @* We set the id of the TextArea to 'CKeditor' for the CKeditor script to change the TextArea into a WYSIWYG editor. *@ 
     @Html.TextAreaFor(model => model.Body, new { id = "CKeditor", @class = "text-editor" }) 
     @Html.ValidationMessageFor(model => model.Body) 
    </div> 
</fieldset> 
. . . 

Если бы я выйти из этих двух входов:

@Html.HiddenFor(model => model.CreatedBy) 
@Html.HiddenFor(model => model.DateCreated) 

когда вызывается метод Edit, они имеют значения по умолчанию. CreatedBy установлен в Null, Дата создания устанавливается в 01/01/0001 12:00:00 утра

Почему они не установлены в значения, так как они в настоящее время установлен в БД?

+0

Вы также можете использовать TryUpdateModel (existingArticle) вместо установки каждого поля. Некоторое решение может показаться не слишком изящным, но есть такие решения, как AutoMapper, которые вы можете использовать позже, что решит эту проблему, но сейчас вам не нужно это знать. – LukLed

+0

Но если это «правильный» способ, конечно, я хочу знать это сейчас;) Я делал немного копания, и похоже, да, использование этих вещей под названием «ViewModels» - это путь: a способ «показать фрагмент информации из одного объекта». По-видимому, это немного больше работы, но по мере того, как модели/представления становятся более сложными, они держатся сильными, в то время как эти другие методы «грубой силы» имеют тенденцию разрушаться. –

ответ

9

Еще несколько исследований, в которых я столкнулся с некоторыми инструментами, которые помогают в процессе ViewModel: один из них является AutoMapper & другим InjectValues. Я пошел с InjectValues ​​прежде всего потому, что он может не только «сгладить» объекты (объект карты a -> b), но он также может «unflatten» (объект карты b -> a) - то, что, к сожалению, у AutoMapper не хватает готового кода - что-то, что мне нужно сделать для обновления значений внутри БД.

Теперь, вместо того чтобы отправить мою статью модель со всеми ее свойствами моих взглядов, я создал ArticleViewModel, содержащий только следующие свойства:

public class ArticleViewModel 
{ 
    public int Id { get; set; } 

    [MaxLength(15)] 
    public string Type { get; set; } 

    public bool Active { get; set; } 
    public bool Stickied { get; set; } 

    [Required] 
    [MaxLength(200)] 
    public string Title { get; set; } 

    [Required] 
    [AllowHtml] 
    public string Body { get; set; } 
} 

Когда я Создать статью, вместо отправки объекта статьи (с каждый собственность) Я посылаю Смотреть «проще» модель - мой ArticleViewModel:

// 
// GET: /Article/Create 

public ActionResult Create() 
{ 
    return View(new ArticleViewModel()); 
} 

для метода POST мы берем на ViewModel мы послали к представлению и использовать свои данные для создания новой статьи в БД , Мы делаем это «unflattening» в ViewModel на объект статьи:

// 
// POST: /Article/Create 
public ActionResult Create(ArticleViewModel articleViewModel) 
{ 
    Article article = new Article();    // Create new Article object 
    article.InjectFrom(articleViewModel);   // unflatten data from ViewModel into article 

    // Fill in the missing pieces 
    article.CreatedBy = CurrentSession.SamAccountName; // Get current logged-in user 
    article.DateCreated = DateTime.Now; 

    if (ModelState.IsValid) 
    {    
     _db.Articles.Add(article); 
     _db.SaveChanges(); 
     return RedirectToAction("Index", "Home"); 
    } 

    ViewBag.Categories = GetDropDownList(); 
    return View(articleViewModel);    
} 

«отсутствующие куски», заполненные в статье свойства я не хочу, чтобы установить в представлении, и они не должны быть обновлены в Edit Edit (или вообще, если на то пошло).

Метод редактирования практически такой же, за исключением того, что вместо отправки новой ViewModel в представление мы отправляем ViewModel предварительно заполненную данными из нашей БД. Мы делаем это путем извлечения статьи из БД и сглаживания данных на ViewModel. Во-первых, метод GET:

// 
    // GET: /Article/Edit/5 
    public ActionResult Edit(int id) 
    { 
     var article = _db.Articles.Single(r => r.Id == id);  // Retrieve the Article to edit 
     ArticleViewModel viewModel = new ArticleViewModel(); // Create new ArticleViewModel to send to the view 
     viewModel.InjectFrom(article);       // Inject ArticleViewModel with data from DB for the Article to be edited. 

     return View(viewModel); 
    } 

Для метода POST мы хотим, чтобы данные, посланные от просмотра и обновления статьи, хранящийся в БД с ним. Чтобы сделать это, мы просто обратный процесс уплощения на «unflattening» в ViewModel на объект статьи - так же, как мы делали для версии POST нашего Создание метода:

// 
    // POST: /Article/Edit/5 
    [HttpPost] 
    public ActionResult Edit(ArticleViewModel viewModel) 
    { 
     var article = _db.Articles.Single(r => r.Id == viewModel.Id); // Grab the Article from the DB to update 

     article.InjectFrom(viewModel);  // Inject updated values from the viewModel into the Article stored in the DB 

     // Fill in missing pieces 
     article.LastUpdatedBy = MyHelpers.SessionBag.Current.SamAccountName; 
     article.LastUpdated = DateTime.Now; 

     if (ModelState.IsValid) 
     { 
      _db.Entry(article).State = EntityState.Modified; 
      _db.SaveChanges(); 
      return RedirectToAction("Index", "Home"); 
     } 

     return View(viewModel); // Something went wrong 
    } 

Нам также необходимо изменить строго типизированных Создать & просмотров Редактировать ожидать ArticleViewModel вместо статьи:

@model ProjectName.ViewModels.ArticleViewModel 

И это все!

Итак, вы можете реализовать ViewModels, чтобы передать только штук ваших моделей на свои представления. Затем вы можете обновить только эти фрагменты, передать ViewModel обратно в контроллер и использовать обновленную информацию в ViewModel для обновления модели .

+0

Спасибо, что поддержали этот вопрос в актуальном состоянии. Наконец, убедитесь, что ваш идентификатор является скрытым полем в вашем представлении: '@ Html.HiddenFor (model => model.Id)' – Rocky

+0

+1 для поиска значения Инжектор из [Codeplex] (https: // valueinjecter .codeplex.com /). Это прекрасная библиотека. – Daz

2

Посмотреть модельный пример:

public class ArticleViewModel { 
    [Required] 
    public string Title { get; set; } 

    public string Content { get; set; } 
} 

Binding пример

public ActionResult Edit(int id, ArticleViewModel article) { 
    var existingArticle = db.Articles.Where(a => a.Id == id).First(); 
    existingArticle.Title = article.Title; 
    existingArticle.Content = article.Content; 
    db.SaveChanges(); 
} 

Это простой пример, но вы должны смотреть на ModelState, чтобы проверить, если модель не имеет ошибок, проверить авторизацию и переместить это код из контроллера для классов обслуживания, но это еще один урок.

Это исправляется метод Edit:

[HttpPost] 
public ActionResult Edit(Article article) 
{ 
    // Get a list of categories for dropdownlist 
    ViewBag.Categories = GetDropDownList(); 


    if (article.CreatedBy == (string)CurrentSession.SamAccountName || (bool)CurrentSession.IsAdmin) 
    {     
     if (ModelState.IsValid) 
     { 
      var existingArticle = _db.Articles.First(a => a.Id = article.Id); 
      existingArticle.LastUpdatedBy = MyHelpers.SessionBag.Current.SamAccountName; 
      existingArticle.LastUpdated = DateTime.Now; 
      existingArticle.Body = Sanitizer.GetSafeHtmlFragment(article.Body); 
      existingArticle.Stickied = article.Stickied; 

      _db.SaveChanges(); 
      return RedirectToAction("Index", "Home"); 
     } 
     return View(article); 
    } 

    // User not allowed to edit 
    return RedirectToAction("Index", "Home"); 
} 
+0

Итак, с помощью ViewModel вам все же необходимо предоставить (в представлении) вход для ** каждого ** поля, чтобы модель Binder (по крайней мере, по умолчанию) успешно привязывалась к модели, верно ли это? В вашем примере мне нужно будет предоставить вход для полей «Заголовок» и «Контент». В этом случае мне было бы проще просто делать то, что я делал, - предоставлять скрытые поля в представлении для неизменяемых полей (Id, DateCreated и т. Д.). Это избавит меня от необходимости создавать модель представления для каждой из моих моделей. –

+0

@cbergman: Именно так должен быть воспроизведен MVC. Скрытые поля можно легко манипулировать и изменять. Скрытие поля не защищает значения от изменений. Кто-то может изменить скрытое поле и изменить что-то, что вы не хотели изменять. Пожалуйста, прочитайте этот недавний вопрос: http://stackoverflow.com/questions/10928287/asp-net-mvc-database-entities-or-viewmodels/10928739#10928739 – LukLed

+0

Я думал, что модель Binder сможет найти определенную Статья с помощью своего идентификатора, заполнить каждое из полей (которые уже содержат значения) и изменить только каждое из полей, которые были отредактированы внутри представления. Но, по-видимому (как и в случае со мной), если он не найдет вход для этого поля, переданного View, он заполнит это поле значением «по умолчанию» (например, поле DateTime с параметром '01/01/0001 12:00:00 am '). Если поле DateTime было, скажем, поле DateCreated, очевидно, это было бы неправильно. –

0

Использование ViewModels.

Посредством моих дальнейших исследований по поиску решения этой проблемы я считаю, что использование этих вещей под названием «ViewModels» - это путь. Как объяснено в статье post Джимми Богардом, ViewModels - это способ «отобразить фрагмент информации из одного объекта."

asp.net-mvc-view-model-patterns получил меня возглавили на правильном пути,. Я до сих пор проверить некоторые из внешних ресурсов автор писал в целях дальнейшего понять концепцию ViewModel (запись в блоге Джимми является одним из них)

2

еще один хороший способ без ViewModel

// POST: /Article/Edit/5 
[HttpPost] 
public ActionResult Edit(Article article0) 
{ 
    var article = _db.Articles.Single(r => r.Id == viewModel.Id); // Grab the Article from the DB to update 

    article.Stickied = article0.Stickied; 

    // Fill in missing pieces 
    article.LastUpdatedBy = MyHelpers.SessionBag.Current.SamAccountName; 
    article.LastUpdated = DateTime.Now; 

    if (ModelState.IsValid) 
    { 
     _db.Entry(article0).State = EntityState.Unchanged; 
     _db.Entry(article).State = EntityState.Modified; 
     _db.SaveChanges(); 
     return RedirectToAction("Index", "Home"); 
    } 

    return View(article0); // Something went wrong 
}