ASP.NET MVC

Substituir os modelos do scaffold padrão

Jonathan Waldman

Baixar o código de exemplo

As tarefas mundanas da rotina de escrever criam, recuperam, atualizam e excluem operações em relação ao armazenamento da dados é apropriadamente transmitido pelo acrônimo CRUD usado com frequência. A Microsoft fornece um mecanismo de scaffolding útil, acionado pelos modelos T4, que automatiza a criação dos controladores de CRUD básicos e modos de exibição para modelos nos aplicativos ASP.NET MVC que usam o Entity Framework. (Também há a API da WebAPI e MVC sem os scaffolders do Entity Framework atualmente disponíveis.)

Página geradas por scaffolds que são navegáveis e utilizáveis. Eles geralmente poupam você da monotonia que envolve a construção de páginas de CRUD. No entanto, os resultados gerados automaticamente oferecem uma funcionalidade bastante limitada que logo você se encontrará ajustando a lógica do controlador gerado e os modos de exibição para se adequar as suas necessidades.

O perigo de fazer isto é que o scaffolding é um processo unidirecional. Você não pode realizar o scaffold novamente no seu controlador e nos modos de exibição para refletir alterações em um modelo sem substituir seus ajustes. No entanto, você tem que exercitar a vigilância ao rastrear quais módulos você personalizou, para que você possa saber quais modelos você pode realizar o scaffold novamente com segurança e quais você não pode.

Em um ambiente de equipe, esta vigilância é difícil de impor. Para fechar, o controlador de edição exibe a maioria das propriedades do modelo no modo de exibição Editar, com isso possivelmente expondo informações confidenciais. Seu modelo liga cegamente todas as propriedades enviadas pelo modo de exibição, o que aumenta o risco de uma ataque de atribuição em massa.

Neste artigo, vou mostrar a você como criar personalizações específicas de projeto dos modelos T4 que ativam o subsistema de scaffolding de CRUD do ASP.NET MVC Entity Framework. Ao longo do caminho, vou mostrar a você como estender os manipuladores de postback Criar e Editar do controlador para que você possa inserir seu próprio código entre o modelo de ligação postback e a persistência de armazenamento de dados.

Para abordar preocupações de atribuições em massa, eu criarei um atributo personalizado que lhe fornece controle total sobre as propriedades do modelo que devem persistir e quais não devem. Depois adicionarei outro atributo personalizado que permite que você exiba uma propriedade como um rótulo somente leitura no modo de exibição Editar.

Depois disso, você terá controle sem precedentes sobre suas páginas de CRUD e como seus modelos são exibidos e persistidos para reduzir a exposição a ataques de seus aplicativos. E o melhor de tudo, você poderá aproveitar estas técnicas para todos os modelos em seus projetos do ASP.NET MVC e regenerar com segurança os controladores e os modos de exibição conforme seus modelos se modificam.

Definição do projeto

Eu desenvolvi esta solução usando o Visual Studio 2013 Ultimate, ASP.NET MVC 5, Entity Framework 6 e C# (as técnicas discutidas são também compatíveis com o Visual Studio 2013 Professional, Premium e Express for Web e Visual Basic .NET). Eu criei duas soluções para download: A primeira é a Baseline Solution, que pode ser usada para começar com um projeto em funcionamento e implementar manualmente estas técnicas. A segunda é a Complete Solution, que inclui todas as melhorias discutidas neste artigo.

Cada solução contém três projetos: um para o site do ASP.NET MVC, um para os modelos de entidade e funções de scaffold do T4 e um para o contexto de dados. O contexto de dados de soluções aponta para um banco de dados do SQL Server Express. Além das dependências já mencionadas, eu adicionei o bootstrap usando o NuGet para ser o tema dos modos de exibição de scaffold.

O subsistema scaffold é instalado durante a Instalação quando você verifica a opção de instalação do Microsoft Web Developer Tools. Service packs subsequentes do Visual Studio atualizarão os arquivos scaffold automaticamente. Você pode obter qualquer atualização para o subsistema scaffold lançado entre os service packs do Visual Studio no mais recente Microsoft Web Platform Installer, que você pode baixar a partir do bit.ly/1g42AhP.

Se você tiver algum problema ao usar o download de código que acompanha este artigo, verifique se você tem a versão mais recente e leia cuidadosamente o arquivo ReadMe.txt. Eu atualizarei isto conforme necessário.

Defina as regras corporativas

Para ilustrar o fluxo de trabalho completo envolvido na geração dos modos de exibição de CRUD e para reduzir desvios, vou trabalhar com um modelo de entidade bem simples chamado Product.

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

Por convenção, o MVC entende que o ProductId é a chave primária, mas não tem ideia que tenho requisitos especiais em relação as propriedades CreatedDate e ModifiedDate. Como seus nomes implicam, eu desejo que o CreatedDate transmita quando o produto em questão (representado pelo ProductId) foi inserido no banco de dados. Também quero que o ModifiedDate transmita quando foi modificado pela última vez (Usarei os valores de data e hora UTC).

Gostaria de exibir o valor ModifiedDate no modo de exibição Editar como texto somente leitura (se o registro nunca foi modificado, o ModifiedDate será igual ao CreatedDate). Eu não quero exibir o CreatedDate em nenhum dos modos de exibição. Eu também não quero que o usuário forneça ou modifique estes valores de data, então eu não quero processar nenhum controle de formulário que coleta informações deles nos modos de exibição Editar ou Criar.

Em um esforço para tornar estes valores à prova de ataque, quero ter certeza que seus valores não são persistidos no postback, mesmo de um hacker inteligente é capaz de fornecê-los como campos de formulário ou valores de cadeias de caracteres de consulta. Porque eu considero estas regras da camada da lógica comercial, não quero que o banco de dados tenha qualquer responsabilidade por manter estes valores da coluna (por exemplo, não quero criar nenhum acionador ou incorporar nenhuma lógica de definição de coluna de tabela).

Explorar o fluxo de trabalho do scaffold de CRUD

Vamos primeiro examinar a função do scaffold padrão. Vou adicionar um controlador clicando com o botão direito do mouse na pasta de controladores de projeto da Web e escolhendo Adicionar controlador. Isto inicia a caixa de diálogo Adicionar Scaffold (veja a Figura 1).

The MVC 5 Add Scaffold Dialog
Figura 1 A caixa de diálogo Adicionar Scaffold do MVC 5

A entrada “Controlador MVC 5 com modos de exibição, usando o Entity Framework” é um que usarei, pois ele usa o scaffold do controlador CRUD e os modos de exibição para um modelo. Selecione aquela entrada e clique em Adicionar. A próxima caixa de diálogo fornece a você um número de opções que terminam como parâmetros para os modelos T4 que ele subsequentemente transforma (veja a Figura 2).

The Add Controller Dialog
Figura 2 A caixa de diálogo Adicionar controlador

Insira ProductController como o nome do controlador. Mantenha a caixa de seleção “Usar as ações assíncronas do controlador” desmarcada por causa desta discussão (as operações assíncronas estão além do escopo deste artigo). Depois, escolha a classe de modelo Product. Porque você está usando o Entity Framework, você precisará de uma classe de contexto de dados. As classes que herdam do System.Data.Entity.DbContext aparecem na lista suspensa, então selecione a correta se a sua solução usa mais de um contexto de banco de dados. Com relação as opções do modo de exibição, marque “Gerar exibições” e “Use uma página de layout.” Deixe a caixa de texto da página de layout vazia.

Quando você clica em Adicionar, vários modelos T4 são transformados para fornecer os resultados gerados automaticamente. Este processo gera um código para um controlador (ProductController.cs), que é escrito para a pasta de controladores do projeto da Web, e cinco exibições (Create.cshtml, Delete.cshtml, Details.cshtml, Edit.cshtml e Index.cshtml), que são escritas para a pasta de exibição do projeto da Web. Neste ponto, você tem um controlador em funcionamento e todos os modos de exibição de CRUD que precisará para gerenciar os dados na entidade Product. Você pode começar usando estas páginas da Web imediatamente, começando pela exibição de índice.

Você provavelmente desejará que suas páginas CRUD pareçam e se comportem de maneira semelhante para todos os modelos em seu projeto. Ao usar os modelos T4 para gerar automaticamente as páginas CRUD ajuda a impor esta consistência. Isto significa que você deveria resistir a tentação de modificar diretamente os controladores e os modos de exibição. Ao invés você deve modificar os modelos T4 que geram eles. Siga esta prática para assegurar que os arquivos gerados automaticamente estão pronto para o uso sem modificações adicionais.

Examine as limitações do controlador

Enquanto o subsistema scaffold deixa você pronto de forma rápida para a execução, o controlador que ele gera tem várias limitações. Mostrarei como fazer algumas melhorias. Observe os métodos de ação do controlador gerados automaticamente que manipula Criar e Editar na Figura 3.

Figura 3 Os métodos de ação do controlador gerados automaticamente para Criar e Editar

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); }

O atributo Bind para cada método inclui explicitamente cada propriedade no modelo Product. Quando um modelo do controlador MVC liga todas as propriedades do modelo após o postback, é chamado de atribuição em massa. Isto é também chamado de overposting e é uma vulnerabilidade de segurança séria. Os hackers podem explorar esta vulnerabilidade porque há uma invocação SaveChanges subsequente no contexto do banco de dados. Isto garante que o modelo fique mantido ao armazenamento de dados. O modelo do controlador que o sistema de scaffold de CRUD usa no MVC 5 gera um código de atribuição em massa por padrão para criar os métodos postbacks de ação Criar e Editar.

Outra consequência da atribuição em massa acontece se você escolher decorar determinadas propriedades em um modelo para que não sejam renderizados para o modo de exibição Criar ou Editar. Estas propriedades serão definidas como null após a ligação do modelo. (Veja “Usar atributos para suprimir propriedades nos modos de exibição de CRUD,” que contém atributos que você pode usar para especificar se você deve ou não renderizar as propriedades geradas automaticamente para os modos de exibição gerados. Para ilustrar, vou primeiro adicionar dois atributos ao modelo 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; } }

Quando eu executo novamente o processo de scaffold usando o Adicionar Controlador, conforme discutido anteriormente, o atributo [Scaffold(false)] assegura que o CreatedDate não aparecerá em nenhum modo de exibição. O atributo [Editable(false)] assegura que o ModifiedDate aparecerá nos modos de exibição Excluir, Detalhes e Índice, mas não nos modos de exibição Criar ou Editar. Quando as propriedades não são renderizadas para o modo de exibição Criar ou Editar, elas não aparecerão no fluxo de solicitação HTTP do postback.

Isto é problemático porque a última chance que você tem para atribuir valores para as propriedades do modelo nestas páginas CRUD acionadas pelo MVC é durante o postback. Então se um valor postback da propriedade é null, aquele valor null será ligado ao modelo. Então o modelo será persistido no armazenamento de dados quando o SaveChanges é executado no objeto do contexto de dados. Se isto for feito no método de ação Editar do postback, aquela propriedade será substituída com uma valor null. Isto exclui efetivamente o valor atual no armazenamento de dados.

No meu exemplo, o valor mantido pelo CreatedDate no armazenamento de dados estaria perdido. De fato, qualquer propriedade não renderizada para o modo de exibição Editar fará com que o valor do armazenamento de dados seja substituído por null. Se a propriedade do modelo ou o armazenamento de dados não permitir atribuição de um valor null, você receberá um erro no postback. Para superar estas limitações, eu modificarei o modelo T4 que é responsável por gerar o controlador. 

Substituir os modelos do scaffold

Para modificar como o controlador e os modos de exibição são gerados automaticamente, você tem que modificar os modelos T4 que geram eles. Você pode modificar os modelos originais, que globalmente irão afetar o scaffolding em todos os projetos do Visual Studio. Você também pode modificar as cópias específicas do projeto dos modelos T4, que somente afetará o projeto no qual as cópias são colocadas. Vou fazer o segundo.

Os modelos scaffold T4 originais estão localizados na pasta %programfiles%\Microsoft Visual Studio 12.0\Common7\IDE\Extensions\Microsoft\Web\Mvc\Scaffolding\Templates. (Estes modelos dependem de várias assemblies do .NET, localizadas na pasta %programfiles%\­Microsoft Visual Studio 12.0\Common7\IDE\Extensions\Microsoft\Web Tools\Scaffolding.) Vou focar nos modelos específicos que geram automaticamente os modos de exibição e controladores CRUD do Entity Framework. Estes são resumidos na Figura 4.

Figura 4 Os modelos T4 que geram automaticamente os modos de exibição e os controladores CRUD do Entity Framework

Nome da subpasta dos modelos scaffold

Nome do arquivo de modelo

(.cs para C#; .vb para Visual Basic .NET)

Gera este arquivo

(.cs para C#; .vb para 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

Para criar modelos específicos do projeto, copie os arquivos que deseja substituir da pasta scaffold T4 original para uma pasta no projeto da Web ASP.NET MVC denominado CodeTemplates (deve ter este nome exato). Por convenção, o subsistema scaffold primeiro olha na pasta CodeTemplates do projeto MVC para uma correspondência de modelo.

Para isto funcionar, você deve replicar precisamente os nomes das subpastas específicas e nomes de arquivo que você vê na pasta original de modelos. Eu copiei os arquivos T4 que planejei substituir do CRUD para o subsistema scaffold do Entity Framework. Veja o CodeTemplates do projeto da Web na Figura 5.

The Web Project’s CodeTemplates
Figura 5 O CodeTemplates do projeto da Web

Eu também copiei Imports.include.t4 e ModelMetadataFunctions.cs.include.t4. O projeto necessita desses arquivos para gerar automaticamente os modos de exibição. Além disso, eu copiei as versões C# (.cs) dos arquivos (se estiver usando o Visual Basic .NET, você desejará copiar os arquivos que incluem .vb no nome do arquivo). O subsistema scaffolding transformará estes arquivos específicos do projeto, aos invés de suas versões globais.

Estender os métodos da ação Criar e Editar

Agora que tenho os modelos T4 específicos do projeto, eu posso modificá-los conforme necessário. Primeiro, estenderei os métodos de ação Criar e Editar do controlador para que eu possa inspecionar e modificar o modelo antes de ele ser mantido. Para manter o código que o modelo gera o mais genérico possível, eu não quero adicionar nenhuma lógica específica do modelo ao modelo. Ao invés, eu quero criar uma função externa ligada ao modelo. Desta maneira, o método Criar e Editar do controlador são estendidos enquanto eles simulam polimorfismo ao modelo. Para fazer isto, eu criarei uma interface e a chamarei de IControllerHooks:

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

Depois, modificarei o modelo Controller.cs.t4 (na pasta CodeTemplates\­MVCControllerWithContext) para que seus métodos de ação Criar e Editar chamem os métodos OnCreate e OnEdit, respectivamente, se o modelo implementou o IControllerHooks. O método postback da ação Criar do controlador é mostrado na Figura 6 e seu método postback da ação Criar é mostrado na Figura 7.

Figura 6 Versão estendida do método postback da ação Criar do controlador

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); }

Figura 7 Versão estendida do método postback da ação Editar do controlador

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); }

Agora, eu modificarei a classe Product para que ela implemente o IController­Hooks. Depois adicionarei o código que desejo executar quando o controlador chama o OnCreate e o OnEdit. A nova classe do modelo Product é mostrada na Figura 8.

Figura 8 Modelo Product que implementa o IControllerHooks para estender o controlador

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; } }

Admito que há várias maneiras de implementar está lógica de “extensão”, mas ao usar esta modificação de uma linha para os métodos Criar e Editar do modelo do controlador, eu agora posso modificar a instância do modelo do produto após a ligação do modelo, mas antes da persistência. Eu posso até definir o valor das propriedades do modelo que não estão publicadas nos modos de exibição Criar e Editar.

Você observará que a função OnEdit do modelo não definirá um valor para o CreatedDate. Se o CreatedDate não for renderizado para o modo de exibição Editar, então ele será substituído por um valor null após o método de ação Editar do controlador ser mantido no modelo quando ele chama o SaveChanges. Para evitar que isto aconteça, vou precisar fazer mais algumas modificações ao modelo do controlador.

Aprimorar o método de ação Editar

Eu já mencionei alguns dos problemas associados à atribuição em massa. Uma maneira de modificar o comportamento de ligação do modelo é modificar o atributo Bind para que ele exclua as propriedades que não são para fazer a ligação. Na prática, no entanto, esta abordagem pode ainda resultar na escrita de valores null para o armazenamento de dados. Uma estratégia melhor envolve programação adicional, mas a compensação faz o esforço valer a pena.

Vou usar o método Attach do Entity Framework para anexar o modelo ao contexto do banco de dados. Eu posso então rastrear a entrada da entidade e definir a propriedade IsModified conforme necessário. Para seguir esta lógica, vou criar um novo módulo de classe denominado CustomAttributes.cs no projeto JW_Scaffold­Enhancement.Models (veja a Figura 9).

Figura 9 O novo módulo da classe CustomAttributes.cs

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

Eu usarei este atributo para indicar as propriedades que eu não quero que sejam persistidas no armazenamento de dados do modo de exibição Editar (as propriedades não decoradas terão um atributo implícito [PersistPropertyOnEdit(true)]). Estou interessado em evitar que propriedade CreatedDate seja persistida, então adicionei o novo atributo para a propriedade CreatedDate somente do meu modelo Product. A classe do modelo recentemente decorado é mostrado aqui:

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; } }

Agora eu preciso modificar o modelo Controller.cs.t4 para que ele respeite o novo atributo. Ao aprimorar um modelo T4, você tem a opção de fazer alterações dentro do modelo ou fora do modelo. A menos que você esteja usando uma das ferramentas de editor de modelos de terceiros, eu defenderia colocar o máximo de códigos possível em um módulo de código externo. Fazendo isso fornece uma tela C# pura (ao invés de uma intercalada com marcação T4) na qual você pode focar no código. Isto também ajuda a testar e fornecer a você uma maneira de incorporar suas funções nos seus esforços mais amplos de agente de teste. Finalmente, devido as limitações em como as assemblies são referenciadas a partir de um scaffold T4, você enfrentará menos problemas técnicos ao ter tudo conectado.

Meu projeto Models contém um função pública denominada GetPropertyIsModifiedList, que retorna uma Lista<Cadeia de caracteres> que eu posso iterar para gerar as configurações IsModified para a assembly e tipo aprovados. A Figura 10 mostra este código no modelo Controller.cs.t4.

T4 Template Code Used To Generate an Improved Controller Edit Postback Handler
Figure 10 Código do modelo T4 usado para gerar um manipulador postback Editar do controlador

No GetPropertyIsModifiedList, mostrado na Figura 11, eu uso reflexão para ganhar acesso as propriedades do modelo fornecidas. Depois eu faço a iteração nelas para determinar quais são decoradas com o atributo PersistPropertyOnEdit. Você provavelmente desejará persistir a maioria das propriedades nos seus modelos, então eu construí o código do modelo para definir um valor IsModified da propriedade para true por padrão. Desta maneira, tudo que você precisa é adicionar [PersistPropertyOnEdit(false)] às propriedades que não deseja persistir.

Figura 11 A função estática do projeto modelo ScaffoldFunctions.GetPropertyIsModifiedList

static public List<string> GetPropertyIsModifiedList(string ModelNamespace, string ModelTypeName, string ModelVariable) { List<string> OutputList = new List<string>(); // Get the properties of the model object string aqn = Assembly.CreateQualifiedName(ModelNamespace + ", Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", ModelNamespace + "." + ModelTypeName); // Get a Type object based on the Assembly Qualified Name Type typeModel = Type.GetType(aqn); // Get the properties of the type 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; }

O modelo do controlador revisado gera um método de ação postback Editar recriado, mostrado na Figura 12. A minha função GetPropertyIsModifiedList gera partes deste código-fonte.

Figura 12 O manipulador Editar do controlador gerado automaticamente recém-criado

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"); }

Usar atributos para suprimir propriedades nos modos de exibição de CRUD

O ASP.NET MVC oferece somente três atributos que fornecem algum controle sobre se as propriedades do modelo são renderizadas para os modos de exibição gerados automaticamente (veja a Figura A). Os primeiros dois atributos fazem a mesma coisa (embora eles residam em namespaces diferentes): [Editable(false)] e [ReadOnly(true)]. Estes farão com que a propriedade decorada não seja renderizada para os modos de exibição Criar e Editar. O terceiro, [ScaffoldColumn(false)], faz com que a propriedade decorada não apareça em nenhum dos modos de exibição renderizados.

Figura A Os três atributos que impedem a renderização das propriedades

Atributos de metadados do modelo Namespace de atributo Modos de exibição afetados O que acontece
  Nenhum Nenhum Nenhum atributo adicional causa somente resultados normais.

[Editable(false)]

[ReadOnly(true)]

Editável:

System.ComponentModel.DataAnnotations

Somente leitura:

System.ComponentModel

Criar

Editar

Propriedade de modelo decorado não processado.
[ScaffoldColumn(false)] System.ComponentModel.DataAnnotations

Criar

Excluir

Detalhes

Editar

Índice

Propriedade de modelo decorado não processado.

Personalizar um modo de exibição

Às vezes você quer exibir um valor no modo de exibição editar que você não deseja que os usuários editem. Os atributos fornecidos pelo ASP.NET MVC não fornecem suporte a isto. Eu gostaria de ver o ModifiedDate no modo de exibição Editar, mas não quero que o usuário pense que se trata de um campo editável. Para implementar isso, eu criarei outro atributo personalizado chamado DisplayOnEditView no módulo de classe CustomAttributes.cs, exibido aqui:

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

Isto permite que eu decore uma propriedade do modelo para ela renderizar como um rótulo no modo de exibição Editar. Depois eu poderei mostrar o ModifiedDate no modo de exibição Editar sem me preocupar que alguém pode violar o seu valor durante o postback.

Agora eu posso usar aquele atributo para decorar mais o modelo Product. Eu colocarei o novo atributo na propriedade ModifiedDate. Eu usarei o [Editable(false)] para assegurar que ele não aparecerá no modo de exibição Criar e o [DisplayOnEditView(true)] para assegurar que ele apareça como um rótulo no modo de exibição Editar:

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; } }

Finalmente, eu modificarei o modelo T4 que gera o modo de exibição Editar para que ele respeite o atributo DisplayOnEditView:

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

E eu adicionarei a função GetHtmlForDisplayOnEditViewAttribute para a função ScaffoldFunctions conforme exibido na Figura 13.

A função GetHtmlForDisplayOnEditViewAttribute retorna Html.EditorFor quando o atributo é false e Html.Display­TextFor quando o atributo é true. O modo de exibição Editar exibirá o ModifiedDate como um rótulo e todos os outros campos que não são chaves como caixas de texto editáveis, mostrado na Figura 14.

Figura 13 Função estática ScaffoldFunctions.GetHtmlForDisplayOnEditViewAttribute para dar suporte ao atributo personalizado 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; }

Figura 14 Modo de exibição Editar mostrando um campo ModifiedDate somente leitura

Conclusão

eu apenas arranhei a superfície do que se pode obter com o subsistema scaffolding. Eu me concentrei nos scaffolds que fornecem modos de exibição e controle de CRUD para o Entity Framework, mas há outros scaffolds disponíveis para gerar códigos para outros tipos de páginas da Web e ações de API da Web.

Se você nunca trabalhou com modelos T4, personalizar os modelos existentes é uma ótima maneira para começar. Embora os modelos que discuti aqui são iniciados a partir de menus com o Visual Studio IDE, você pode criar modelos T4 personalizados e transformá-los sempre que necessário. A Microsoft fornece um bom ponto inicial em bit.ly/1coB616. Se estiver procurando por algo mais avançado, recomendo o curso de Dustin Davis bit.ly/1bNiVXU.

Neste momento, o Visual Studio 2013 não inclui um editor de T4 robusto. De fato, ele não oferece realce de sintaxe ou o IntelliSense. Felizmente, há alguns complementos que oferecem.  Confira o editor de T4 da Devart (bit.ly/1cabzOE) e o editor de T4 da Tangible Engineering (bit.ly/1fswFbo).

Jonathan Waldman é um desenvolvedor de software experiente e um arquiteto técnico. Ele tem trabalhado com a pilha de tecnologia da Microsoft desde sua concepção. Ele tem trabalhado em vários projetos corporativos de perfil alto e participado em todos os aspectos do ciclo de vida do desenvolvimento do software. Ele é um membro da equipe técnica do Pluralsight e continua a desenvolver soluções de software e materiais educativos. Entre em contato com ele pelo email jonathan.waldman@live.com.

Agradecemos ao seguinte especialista técnico da Microsoft pela revisão deste artigo: Joost de Nijs
Joost de Nijs é um Gerente de Programa da equipe de Experiência do Desenvolvedor do Azure na Microsoft, trabalhando no Web Developer Tooling.  Atualmente ele está focado nas áreas de recurso de gerenciamento do Web Scaffolding e NuGet Package, com trabalho prévio nas bibliotecas clientes de Java do Windows Azure e o Windows Azure Developer Center.