Поиск на сайте: Расширенный поиск


Новые программы oszone.net Читать ленту новостей RSS
CheckBootSpeed - это диагностический пакет на основе скриптов PowerShell, создающий отчет о скорости загрузки Windows 7 ...
Вы когда-нибудь хотели создать установочный диск Windows, который бы автоматически установил систему, не задавая вопросо...
Если после установки Windows XP у вас перестала загружаться Windows Vista или Windows 7, вам необходимо восстановить заг...
Программа подготовки документов и ведения учетных и отчетных данных по командировкам. Используются формы, утвержденные п...
Red Button – это мощная утилита для оптимизации и очистки всех актуальных клиентских версий операционной системы Windows...
OSzone.net Microsoft Разработка приложений .NET Framework Переопределение исходных шаблонов scaffold RSS

Переопределение исходных шаблонов scaffold

Текущий рейтинг: 0 (проголосовало 0)
 Посетителей: 548 | Просмотров: 642 (сегодня 0)  Шрифт: - +

Повседневные задачи написания рутинных операций создания, чтения, обновления и удаления применительно к хранилищам данных точно передаются часто используемым акронимом CRUD. Microsoft предоставляет полезный механизм scaffolding, поддерживаемый шаблонами T4, который автоматизирует создание базовых CRUD-контроллеров и представлений для моделей в приложениях ASP.NET MVC, использующих Entity Framework. (В настоящее время также доступны WebAPI и MVC без Entity Framework шаблонов scaffold.)

Шаблоны scaffold генерируют готовые к использованию страницы с поддержкой навигации. Как правило, они избавляют вас от монотонной работы, связанной с конструированием CRUD-страниц. Однако их результаты предлагают такую ограниченную функциональность, что вскоре вы обнаружите, что вам приходится все время модифицировать сгенерированные логику контроллера и представления под свои потребности.

Однако, делая именно так, вы рискуете, потому что scaffolding — процесс односторонний. Вы не можете заново выполнить scaffolding ваших контроллера и представлений, чтобы отразить изменения в модели без потери внесенных вами модификаций. Поэтому вам придется упражняться в развитии внимательности, отслеживая, какие модули вы модифицировали, чтобы знать, какие модули можно безопасно подвергнуть повторному scaffolding, а какие — нет.

В среде групповой разработки это гораздо труднее. Вдобавок ко всему, контроллер редактирования отображает большинство свойств модели в представлении Edit, тем самым потенциально раскрывая конфиденциальную информацию. Он слепо связывает модель и сохраняет все переданные из представления новые значения свойств, что увеличивает риск атаки с массовым присваиванием (mass-assignment attack).

В этой статье я покажу, как создать специфичные для проекта адаптации шаблонов T4, которые обеспечивают работу CRUD-подсистемы scaffolding в Entity Framework для ASP.NET MVC. Попутно я продемонстрирую, как расширить обработчики обратной передачи Create и Edit контроллера, чтобы вы могли встраивать свой код между связыванием модели при обратной передаче (postback model binding) и сохранением данных в хранилище.

Чтобы устранить проблемы массового присваивания, я создам собственный атрибут, который даст вам полный контроль над свойствами модели: какие из них должны сохраняться, а какие — нет. Затем я добавлю другой собственный атрибут, который позволит вам отображать свойство как метку только для чтения в представлении Edit.

После этого вы получите беспрецедентный контроль над своими CRUD-страницами и тем, как отображаются и сохраняются ваши модели, в то же время уменьшив уязвимость вашего приложения к атакам. Но самое главное в том, что вы сможете использовать эти методики для всех моделей в ваших проектах ASP.NET MVC и безопасно заново генерировать контроллеры и представления при изменении моделей.

Подготовка проекта

Я разработал это решение, используя Visual Studio 2013 Ultimate, ASP.NET MVC 5, Entity Framework 6 и C# (обсуждаемые методики также совместимы с Visual Studio 2013 Professional, Premium и Express for Web и Visual Basic .NET). Я создал два решения, которые можно скачать: первое — это Baseline Solution (с его помощью вы можете начать с рабочего проекта и вручную реализовать все обсуждаемые методики), а второе — Complete Solution (оно включает все усовершенствования, о которых мы поговорим в этой статье).

Каждое решение содержит три проекта: по одному для веб-сайта ASP.NET MVC, для моделей сущностей и функций T4-scaffold и для контекста данных. Контекст данных в этих решениях указывает на базу данных SQL Server Express. В дополнение к уже упомянутым зависимостям я добавил Bootstrap, используя NuGet, чтобы придать согласованный внешний вид представлениям, созданным на основе scaffolding.

Подсистема scaffold устанавливается вместе с Microsoft Web Developer Tools. Последующие пакеты обновлений Visual Studio будут автоматически обновлять файлы scaffold. Вы можете получать любые обновления для подсистемы scaffold, выпущенные между пакетами обновлений Visual Studio, с помощью новейшего Microsoft Web Platform Installer, который можно скачать по ссылке bit.ly/1g42AhP.

Если у вас возникают какие-то проблемы в использовании кода, сопутствующего этой статье, убедитесь, что вы располагаете его новейшей версией, и внимательно прочтите файл ReadMe.txt. Я буду обновлять его по мере необходимости.

Определение бизнес-правил

Чтобы проиллюстрировать полный рабочий процесс генерации CRUD-представлений и поменьше отвлекаться на детали, я буду использовать очень простую модель сущности Product:

public class Product
{
  public int ProductId { get; set; }
  public string Description { get; set; }
  public DateTime? CreatedDate { get; set; }
  public DateTime? ModifiedDate { get; set; }
}

По соглашению, MVC понимает, что ProductId — это основной ключ, но не имеет никакого представления о том, что я предъявляю особые требования к свойствам CreatedDate и ModifiedDate. Как и предполагают их имена, я хочу, чтобы CreatedDate передавалась вместе с Product (представленным ProductId), вставляемым в базу данных. То же самое я хочу и в отношении ModifiedDate (я буду применять дату и время в формате UTC).

Я хотел бы отображать значение ModifiedDate в представлении Edit как текст только для чтения (если запись никогда не модифицировалась, ModifiedDate будет идентично CreatedDate). Отображать CreatedDate в каком-либо представлении мне не нужно. Я также не хочу, чтобы пользователь мог вводить или изменять эти значения, поэтому нет никакой нужды в элементах управления для сбора соответствующего ввода в представлениях Create и Edit.

Стремясь сделать эти значения защищенными от атак, я хочу быть уверенным, что они не сохраняются при обратной передаче (postback), даже если умный хакер сумеет предоставить их как поля формы или значения строки запроса. Поскольку я считаю важными эти правила уровня бизнес-логики, мне не нужно, чтобы база данных отвечала за поддержание значений этих столбцов (например, мне не за чем создавать какие-либо триггеры или встраивать логику определения полей таблицы).

Исследуем рабочий CRUD-процесс scaffold

Давайте сначала рассмотрим функцию исходного scaffold. Я добавлю контроллер, щелкнув правой кнопкой мыши папку Controllers веб-проекта и выбрав Add Controller. Это приведет к запуску диалога Add Scaffold (рис. 1).

*
Рис. 1. Диалог Add Scaffold в MVC 5

Я использую вариант «MVC 5 Controller with views, using Entity Framework», потому что он генерирует скелетные шаблоны (scaffold) CRUD-контроллера и представлений для модели. Выберите эту строку и щелкните Add. Следующий диалог дает вам ряд вариантов, которые в конечном счете будут параметрами для шаблонов T4 (рис. 2).

*
Рис. 2. Диалог Add Controller

Введите ProductController в качестве имени контроллера. Оставьте флажок Use the async controller actions неустановленным (асинхронные операции выходят за рамки этой статьи). Затем выберите класс модели Product. Поскольку мы используем Entity Framework, вам понадобится класс контекста данных. В раскрывающемся списке появляются производные от System.Data.Entity.DbContext классы, поэтому выберите подходящий, если в вашем решении используется более одного контекста базы данных. В параметрах представлений установите флажки Generate views и Use a layout page. Поле разметки страницы оставьте пустым.

После щелчка кнопки Add происходит преобразование нескольких шаблонов T4 для получения результатов scaffolding. В этом процессе генерируется код для контроллера (ProductController.cs), записываемый в папку Controllers веб-проекта, и пять представлений (Create.cshtml, Delete.cshtml, Details.cshtml, Edit.cshtml, и Index.cshtml), записываемых в папку Views веб-проекта. К этому моменту у вас есть рабочий контроллер и все CRUD-представления, необходимые для управления данными в сущности Product. Вы можете прямо сейчас использовать эти веб-страницы, начав с представления Index.

Скорее всего вы захотите, чтобы ваши CRUD-страницы выглядели и вели себя схожим образом для всех моделей в вашем проекте. Используя шаблоны T4 для генерации скелетного кода CRUD-страниц, вы обеспечиваете эту согласованность. Это означает, что не следует поддаваться искушению напрямую модифицировать контроллеры и представления. Вместо этого нужно изменять шаблоны T4, которые генерируют их. Придерживайтесь этого правила.

Анализируем недостатки контроллера

Хотя подсистема scaffold позволяет довольно быстро приступить к работе, генерируемый ею контроллер имеет несколько недостатков. Я покажу, как внести некоторые усовершенствования. Посмотрите на методы операций сгенерированного контроллера, которые обрабатывают Create и Edit на рис. 3.

Рис. 3. Методы операций сгенерированного контроллера для Create и Edit

public ActionResult Create(
  [Bind(Include="ProductId,Description,CreatedDate,ModifiedDate")]
  Product product)
{
  if (ModelState.IsValid)
  {
    db.Products.Add(product);
    db.SaveChanges();
    return RedirectToAction("Index");
  }
  return View(product);
}
public ActionResult Edit(
  [Bind(Include="ProductId,Description,CreatedDate,ModifiedDate")]
  Product product)
{
  if (ModelState.IsValid)
  {
    db.Entry(product).State = EntityState.Modified;
    db.SaveChanges();
    return RedirectToAction("Index");
  }
  return View(product);
}

Атрибут Bind для каждого метода явным образом включает каждое свойство модели Product. Когда модель MVC-контроллера связывает все свойства после обратной передачи, это называется массовым присваиванием (mass assignment). Кроме того, это называют оверпостингом (overposting), и это серьезная уязвимость в защите. Хакеры могут использовать эту уязвимость, потому что следом вызывается SaveChanges контекста данных. Это обеспечивает сохранение модели в хранилище данных. Шаблон контроллера, используемый CRUD-подсистемой scaffolding в MVC 5, по умолчанию генерирует код массового присваивания для методов операций Create и Edit.

Скорее всего вы захотите, чтобы ваши CRUD-страницы выглядели и вели себя схожим образом для всех моделей в вашем проекте.

Другое следствие массового присваивания проявляется, когда вы решаете дополнить некоторые свойства модели, чтобы они не визуализировались в представлении Create или Edit. Эти свойства будут установлены в null после связывания модели. (См. врезку «Применение атрибутов для подавления свойств в CRUD-представлениях», где приведены атрибуты, с помощью которых можно указывать, следует ли визуализировать свойства, полученные через scaffolding, в сгенерированных представлениях.) Для иллюстрации я сначала добавлю два атрибута в модель Product:

public class Product
{
  public int ProductId { get; set; }
  public string Description { get; set; }
  [ScaffoldColumn(false)]
  public DateTime? CreatedDate { get; set; }
  [Editable(false)]
  public DateTime? ModifiedDate { get; set; }
}

Когда я повторно запускаю процесс scaffold, используя Add Controller, как было описано ранее, атрибут [Scaffold(false)] гарантирует, что CreatedDate не появится ни в одном представлении, а атрибут [Editable(false)] — что ModifiedDate появится в представлениях Delete, Details и Index, но не в представлениях Create и Edit. Если свойства не визуализируются в представлении Create или Edit, они не появляются в потоке HTTP-запроса обратной передачи.

Это создает проблему, так как последний шанс присвоить значения свойствам модели в этих CRUD-страницах MVC-приложения предоставляется при обратной передаче. Поэтому, если значение свойства при обратной передаче равно null, это значение и будет связано с моделью. Затем модель будет сохранена в хранилище данных при выполнении SaveChanges объекта контекста данных. Если это делается в методе операции Edit при обратной передаче, такое свойство будет заменено значением null. Это равнозначно удалению текущего значения из хранилища данных.

В моем примере значение, содержащееся в CreatedDate в хранилище данных, было бы потеряно. По сути, любое свойство, не визуализируемое в представлении Edit, будет приводить к перезаписи значения в хранилище данных значением null. Если свойство модели или хранилище данных не разрешает присваивания значения null, вы получите ошибку при обратной передаче. Чтобы преодолеть эти трудности, я модифицирую шаблон T4, отвечающий за генерацию контроллера.

Переопределение шаблонов scaffold

Чтобы изменить scaffolding контроллера и представлений, нужно модифицировать шаблоны T4, которые их генерируют. Вы можете изменить исходные шаблоны, что окажет глобальное влияние на scaffolding во всех проектах Visual Studio. Кроме того, можно было бы модифицировать специфичные для проекта копии шаблонов T4, и тогда влияние будет оказано только на проект, в который помещаются эти копии. Я прибегну ко второму варианту.

Исходные шаблоны T4 scaffold расположены в папке %programfiles%\Microsoft Visual Studio 12.0\Common7\IDE\Extensions\Microsoft\Web\Mvc\Scaffolding\Templates. (Эти шаблоны зависят от нескольких .NET-сборок, находящихся в папке %programfiles%\Microsoft Visual Studio 12.0\Common7\IDE\Extensions\Microsoft\Web Tools\Scaffolding.) Я сосредоточусь на конкретных шаблонах, которые осуществляют scaffolding CRUD-контроллера и представлений при использовании Entity Framework. Они представлены в табл. 1.

Табл. 1. Шаблоны T4, которые осуществляют scaffolding CRUD-контроллера и представлений при использовании Entity Framework

Имя вложенной папки шаблонов scaffold

Имя файла шаблона

(.cs для C#, .vb для Visual Basic .NET)

Генерирует этот файл

(.cs для C#, .vb для Visual Basic .NET)

MvcControllerWithContext

Controller.cs.t4

Controller.vb.t4

Controller.cs

Controller.vb

MvcView

Create.cs.t4

Create.vb.t4

Create.cshtml

Create.vbhtml

MvcView

Delete.cs.t4

Delete.vb.t4

Delete.cshtml

Delete.vbhtml

MvcView

Details.cs.t4

Details.vb.t4

Details.cshtml

Details.vbhtml

MvcView

Edit.cs.t4

Edit.vb.t4

Edit.cshtml

Edit.vbhtml

MvcView

Index.cshtml

Index.vbhtml

Index.cshtml

Index.vbhtml

Чтобы создать специфичные для проекта шаблоны, скопируйте файлы, которые вы хотите переопределить, из исходной папки T4 scaffold в папку CodeTemplates в веб-проекте ASP.NET MVC (у нее должно быть именно такое имя). По соглашению, подсистема scaffold сначала просматривает папку CodeTemplates в MVC-проекте.

Чтобы все это работало, вы должны точно продублировать имена специфических вложенных папок и имена файлов, которые вы видите в папке исходных шаблонов. Я скопировал файлы T4, которые намерен переопределить для подсистемы scaffold в Entity Framework. Посмотрите на мою папку CodeTemplates веб-проекта на рис. 4.

*

Рис. 4. CodeTemplates в веб-проекте

Я также скопировал Imports.include.t4 и ModelMetadataFunctions.cs.include.t4. Эти файлы нужны в проекте, чтобы выполнять scaffolding представлений. Кроме того, я копировал только C#-версии файлов (.cs) (если вы используете Visual Basic .NET, то должны скопировать файлы, имя которых включает «.vb»). Подсистема scaffold преобразует эти файлы, специфичные для проекта, а не их глобальные версии.

Расширение методов операций Create и Edit

Теперь, когда у меня есть специфичные для проекта шаблоны T4, я могу модифицировать их как угодно. Сначала я расширю методы операций Create и Edit контроллера, чтобы можно было анализировать и изменять модель перед сохранением. Чтобы сохранить код, генерируемый шаблонами, максимально универсальным, я не буду добавлять в шаблон никакую логику, специфичную для модели. Вместо этого мне нужно вызывать внешнюю функцию, связанную с моделью. Тем самым Create и Edit контроллера расширяются с имитацией полиморфизма в модели. В связи с этим я создам интерфейс и назову его IControllerHooks:

namespace JW_ScaffoldEnhancement.Models
{
  public interface IControllerHooks
  {
    void OnCreate();
    void OnEdit();
  }
}

Используя шаблоны T4 для генерации скелетного кода CRUD-страниц, вы обеспечиваете их согласованность.

Затем я модифицирую шаблон Controller.cs.t4 (в папке CodeTemplates\MVCControllerWithContext) так, чтобы его методы операций Create и Edit при обратной передаче вызывали методы OnCreate и OnEdit модели соответственно, если модель реализует IControllerHooks. Метод операции Create контроллера показан на рис. 5, а метод операции Create — на рис. 6.

Рис. 5. Расширенная версия метода операции Create контроллера

public ActionResult Create(
  [Bind(Include="ProductId,Description,CreatedDate,ModifiedDate")]
  Product product)
{
  if (ModelState.IsValid)
  {
    if (product is IControllerHooks) {
      ((IControllerHooks)product).OnCreate();
	}
    db.Products.Add(product);
    db.SaveChanges();
    return RedirectToAction("Index");
  }
  return View(product);
}

Рис. 6. Расширенная версия метода операции Edit контроллера

public ActionResult Edit(
  [Bind(Include="ProductId,Description,CreatedDate,ModifiedDate")]
  Product product)
{
  if (ModelState.IsValid)
  {
    if (product is IControllerHooks) {
      ((IControllerHooks)product).OnEdit();
	}
    db.Entry(product).State = EntityState.Modified;
    db.SaveChanges();
    return RedirectToAction("Index");
  }
  return View(product);
}

Теперь модифицируем класс Product, чтобы он реализовал IControllerHooks. Затем я добавлю код, который должен выполняться, когда контроллер вызывает OnCreate и OnEdit. Новый класс модели Product приведен на рис. 7.

Рис. 7. Модель Product, которая реализует IControllerHooks для расширения контроллера

public class Product : IControllerHooks
{
  public int ProductId { get; set; }
  public string Description { get; set; }
  public DateTime? CreatedDate { get; set; }
  public DateTime? ModifiedDate { get; set; }
  public void OnCreate()
  {
    this.CreatedDate = DateTime.UtcNow;
    this.ModifiedDate = this.CreatedDate;
  }
  public void OnEdit()
  {
    this.ModifiedDate = DateTime.UtcNow;
  }
}

Безусловно, реализовать эту логику «расширения» можно многими способами, однако, используя эту однострочную модификацию в методах Create и Edit шаблона контроллера, теперь можно изменять экземпляр модели Product после связывания модели, но перед сохранением. Я могу даже установить значения свойств модели, не публикуемых в представлениях Create и Edit.

Вы заметите, что функция OnEdit модели не задает значение для CreatedDate. Если CreatedDate не визуализируется в представлении Edit, оно будет перезаписано значением null после того, как метод операции Edit контроллера сохранит модель вызовом SaveChanges. Чтобы предотвратить это, мне придется внести в шаблон контроллера некоторые дополнительные изменения.

Расширение метода операции Edit

Я уже упоминал некоторые из проблем, связанные с массовым присваиванием. Один из способов модифицировать поведение связывания модели — изменить атрибут Bind так, чтобы он исключал свойства из процесса связывания. Но на практике этот подход все равно может привести к записи null-значений в хранилище данных. Более эффективная и надежная стратегия требует дополнительного программирования, но усилия окупятся сторицей.

Я намерен задействовать метод Attach из Entity Framework для подключения модели к контексту базы данных. Затем я буду отслеживать запись сущности и при необходимости устанавливать свойство IsModified. Для управления этой логикой я создам новый модуль класса — CustomAttributes.cs в проекте JW_ScaffoldEnhancement.Models (рис. 8).

Рис. 8. Новый модуль класса CustomAttributes.cs

using System;
namespace JW_ScaffoldEnhancement.Models
{ 
  public class PersistPropertyOnEdit : Attribute
  {
    public readonly bool PersistPostbackDataFlag;
    public PersistPropertyOnEdit(bool persistPostbackDataFlag)
    {
      this.PersistPostbackDataFlag = persistPostbackDataFlag;
    }
  }
}

Я буду использовать этот атрибут, чтобы указывать свойства, которые я не хочу сохранять в базе данных из представления Edit (свойства, не дополненные этим атрибутом, неявно получат атрибут [PersistPropertyOnEdit(true)]). Я заинтересован в том, чтобы предотвратить сохранение свойства CreatedDate, поэтому добавил новый атрибут только к свойству CreatedDate в своей модели Product. Только что дополненный класс модели выглядит так:

public class Product : IControllerHooks
{
  public int ProductId { get; set; }
  public string Description { get; set; }
  [PersistPropertyOnEdit(false)]
  public DateTime? CreatedDate { get; set; }
  public DateTime? ModifiedDate { get; set; }
}

Теперь нужно изменить шаблон Controller.cs.t4, чтобы он распознавал новый атрибут. Расширяя шаблон T4, вы можете внести изменения как внутренние для шаблона или как внешние. Если вы не используете один из сторонних редакторов шаблонов, советую помещать как можно больше кода во внешний модуль кода. Благодаря этому вы получаете чистый «холст» для написания C#-кода (не перемешанного с разметкой T4) и можете сосредоточиться на самом коде. Это также помогает в тестировании и позволяет охватывать все ваши функции при использовании более широкой системы тестирования. Наконец, у вас будет меньше проблем в связывании всего воедино из-за некоторых недостатков в том, как T4 scaffold ссылается на сборки.

Мой проект Models содержит открытую функцию GetPropertyIsModifiedList, которая возвращает List<String>. По этому списку я могу проходить и генерировать параметры IsModified для переданных сборки и типа. Этот код в шаблоне Controller.cs.t4 показан на рис. 9.

*
Рис. 9. Код шаблона T4 для генерации улучшенного обработчика обратной передачи Edit контроллера

Применение атрибутов для подавления свойств в CRUD-представлениях

ASP.NET MVC предлагает лишь три атрибута, которые обеспечивают некоторый контроль над тем, визуализируются ли свойства модели в сгенерированных представлениях (табл. A). Первые два атрибута делают одно и то же (хотя находятся в разных пространствах имен): [Editable(false)] и [ReadOnly(true)]. Они предотвратят визуализацию свойства с одним из этих атрибутов в представлениях Create и Edit. Третий атрибут, [ScaffoldColumn(false)], приводит к тому, что свойство с этим атрибутом не появляется ни в одном из визуализированных представлений.

Табл. A. Три атрибута, предотвращающих визуализацию свойств

Атрибут метаданных моделиПространство имен атрибутаЗатрагиваемые представленияЧто происходит
НетНетВ отсутствие дополнительных атрибутов все происходит, как обычно

[Editable(false)]

[ReadOnly(true)]

Editable:

System.ComponentModel.DataAnnotations

ReadOnly:

System.ComponentModel

Create

Edit

Свойство модели, дополненное этим атрибутом, не визуализируется
[ScaffoldColumn(false)]System.ComponentModel.DataAnnotations

Create

Delete

Details

Edit

Index

Свойство модели, дополненное этим атрибутом, не визуализируется

В GetPropertyIsModifiedList (рис. 10) я получаю доступ к предоставляемым моделью свойствам через механизм отражения (reflection). Затем перебираю их в цикле, чтобы определить, какие из них следует дополнить атрибутом PersistPropertyOnEdit. Скорее всего вы предпочтете сохранять большинство свойств в своих моделях, поэтому я сконструировал код шаблона так, чтобы значение IsModified свойства было по умолчанию равно true. Благодаря этому вам нужно лишь добавить [PersistPropertyOnEdit(false)] к тем свойствам, которые вы не хотите сохранять.

Рис. 10. Статическая функция ScaffoldFunctions.GetPropertyIsModifiedList в проекте модели

static public List<string> GetPropertyIsModifiedList(string ModelNamespace,
  string ModelTypeName, 
  string ModelVariable)
{
  List<string> OutputList = new List<string>();
  // Получаем свойства объекта модели
  string aqn = Assembly.CreateQualifiedName(ModelNamespace +
    ", Version=1.0.0.0,
    Culture=neutral, PublicKeyToken=null", ModelNamespace + "." +
    ModelTypeName);
  // Получаем объект Type на основе полного имени сборки
  Type typeModel = Type.GetType(aqn);
  // Получаем свойства типа
  PropertyInfo[] typeModelProperties = typeModel.GetProperties();
  PersistPropertyOnEdit persistPropertyOnEdit;
  foreach (PropertyInfo propertyInfo in typeModelProperties)
  {
    persistPropertyOnEdit =
      (PersistPropertyOnEdit)Attribute.GetCustomAttribute(
      typeModel.GetProperty(propertyInfo.Name), typeof(PersistPropertyOnEdit));
    if (persistPropertyOnEdit == null)
    {
    OutputList.Add(ModelVariable + "Entry.Property(e => e." +
      propertyInfo.Name + ").IsModified = true;");
    }
    else
    {
    OutputList.Add(ModelVariable + "Entry.Property(e => e." +
      propertyInfo.Name + ").IsModified = " +
      ((PersistPropertyOnEdit)persistPropertyOnEdit).
      PersistPostbackDataFlag.ToString().ToLower() + ";");
    }
  }
  return OutputList;
}

Модифицированный шаблон контроллера генерирует новый метод операции обратной передачи Edit (рис. 11). Моя функция GetPropertyIsModifiedList генерирует части этого исходного кода.

Рис. 11. Новый сгенерированный обработчик Edit контроллера

if (ModelState.IsValid)
{
  if (product is IControllerHooks)
  {
   ((IControllerHooks)product).OnEdit();
  }
  db.Products.Attach(product);
  var productEntry = db.Entry(product);
  productEntry.Property(e => e.ProductId).IsModified = true;
  productEntry.Property(e => e.Description).IsModified = true;
  productEntry.Property(e => e.CreatedDate).IsModified = false;
  productEntry.Property(e => e.ModifiedDate).IsModified = true;
  db.SaveChanges();
  return RedirectToAction("Index");
}

Настройка представления

Иногда нужно отображать какое-то значение в представлении Edit, которое пользователи не должны изменять. Атрибуты, предоставляемые ASP.NET MVC, этого не поддерживают. Я хотел бы видеть ModifiedDate в представлении Edit, но не желаю, чтобы пользователи думали, будто это редактируемое поле. Чтобы реализовать это, я создам еще один собственный атрибут, DisplayOnEditView, в модуле класса CustomAttributes.cs:

public class DisplayOnEditView : Attribute
{
  public readonly bool DisplayFlag;               
  public DisplayOnEditView(bool displayFlag)
  {
    this.DisplayFlag = displayFlag;
  }
}

Это позволяет мне дополнить свойство модели атрибутом, чтобы оно визуализировалось в представлении Edit как метка (надпись). Тогда я смогу отображать ModifiedDate в представлении Edit, не беспокоясь, что кто-то сможет подменить его значение при обратной передаче.

Теперь этот атрибут можно использовать для дальнейшего дополнения модели Product. Я помещу новый атрибут в свойство ModifiedDate. Я буду применять [Editable(false)], чтобы это свойство не появлялось в представлении Create, и [DisplayOnEditView(true)], чтобы это свойство появлялось в представлении Edit как метка:

public class Product : IControllerHooks
{
  public int ProductId { get; set; }
  public string Description { get; set; }
  [PersistPropertyOnEdit(false)]
  [ScaffoldColumn(false)]
  public DateTime? CreatedDate { get; set; }
  [Editable(false)]
  [DisplayOnEditView(true)]
  public DateTime? ModifiedDate { get; set; }
}

Наконец, я модифицирую шаблон T4, который генерирует представление Edit, чтобы он распознавал атрибут DisplayOnEditView:

HtmlForDisplayOnEditViewAttribute =
  JW_ScaffoldEnhancement.Models.ScaffoldFunctions.
  GetHtmlForDisplayOnEditViewAttribute(
  ViewDataTypeName, property.PropertyName,
  property.IsReadOnly);

И добавлю функцию GetHtmlForDisplayOnEditViewAttribute в класс ScaffoldFunctions, как показано на рис. 12.

Рис. 12. Статическая функция ScaffoldFunctions.GetHtmlForDisplayOnEditViewAttribute для поддержки собственного атрибута DisplayOnEditViewFlag

static public string GetHtmlForDisplayOnEditViewAttribute(
  string ViewDataTypeName, string PropertyName, bool IsReadOnly)
{
  string returnValue = String.Empty;
  Attribute displayOnEditView = null;
  Type typeModel = Type.GetType(ViewDataTypeName);
  if (typeModel != null)
  {
    displayOnEditView =
    (DisplayOnEditView)Attribute.GetCustomAttribute(typeModel.GetProperty(
    PropertyName), typeof(DisplayOnEditView));
    if (displayOnEditView == null)
    {
      if (IsReadOnly)
      { returnValue = String.Empty; }
      else
      { returnValue = "@Html.EditorFor(model => model." +
          PropertyName + ")"; }
    }
    else
    {                         
      if (((DisplayOnEditView)displayOnEditView).DisplayFlag == true)
      { returnValue = "@Html.DisplayTextFor(model => model." +
          PropertyName + ")"; }
      else
      { returnValue = "@Html.EditorFor(model => model." +
        PropertyName + ")"; }
    }
  }
  return returnValue;
}

Функция GetHtmlForDisplayOnEditViewAttribute возвращает Html.EditorFor, когда атрибут равен false, и Html.DisplayTextFor, когда он равен true. Представление Edit будет отображать ModifiedDate как метку, а все остальные не ключевые поля — как редактируемые текстовые поля (рис. 13).

Рис. 13. Представление Edit, отображающее поле ModifiedDate только для чтения

Заключение

Я лишь поверхностно затронул тематику, относящуюся к тому, что можно делать с помощью подсистемы scaffolding. Я сосредоточился на шаблонах scaffold, которые предоставляют CRUD-элементы и представления для Entity Framework, но есть и другие шаблоны scaffold, генерирующие код для прочих типов веб-страниц и операций Web API.

Если вы никогда не работали с шаблонами T4, лучший способ начать — адаптировать существующие шаблоны. Хотя обсуждаемые здесь шаблоны запускаются из меню Visual Studio IDE, вы можете создавать свои шаблоны T4 и преобразовывать их при необходимости. Microsoft предоставляет хорошую отправную точку на странице bit.ly/1coB616. Если вы ищете нечто более продвинутое, советую прочитать учебный курс Дастина Дэвиса (Dustin Davis) по ссылке bit.ly/1bNiVXU.

В данный момент в Visual Studio 2013 нет надежного редактора T4. По сути, она не предлагает подсветку синтаксиса или поддержку IntelliSense. К счастью, есть некоторые сторонние надстройки, которые все это делают. Проверьте такие редакторы, как Devart T4 (bit.ly/1cabzOE) и Tangible Engineering T4 Editor (bit.ly/1fswFbo).

Автор: Джонатан Уолдмен  •  Иcточник: msdn.microsoft.com  •  Опубликована: 26.12.2014
Нашли ошибку в тексте? Сообщите о ней автору: выделите мышкой и нажмите CTRL + ENTER
Теги:   scaffold.


Оценить статью:
Вверх
Комментарии посетителей
Комментарии отключены. С вопросами по статьям обращайтесь в форум.