Compartir a través de


ASP.NET MVC

Reemplazar las plantillas para scaffold predeterminadas

Jonathan Waldman

Descargar el código de ejemplo

El acrónimo de uso frecuente CRUD abarca de manera adecuada las tareas mundanas de escribir operaciones de rutina de creación, recuperación, actualización y eliminación en un almacén de datos. Microsoft proporciona un motor de scaffolding útil, procesado por plantillas T4, que automatiza la creación de controladores CRUD y vistas básicos para modelos en aplicaciones ASP.NET MVC que usan Entity Framework. (También hay disponibles actualmente scaffolders de WebAPI y MVC sin Entity Framework).

Los scaffolds generan páginas que se pueden usar y por las que se puede navegar. En general, lo alivian a uno de la monotonía que implica la construcción de páginas CRUD. Pero los resultados a los que se le aplica la técnica scaffolding ofrecen una funcionalidad tan limitada que pronto te encontrarás retocando la lógica del controlador y las vistas generadas para adaptarlas a tus necesidades.

El riesgo está en que la técnica scaffolding es un proceso de un solo sentido. No es posible volver a aplicar la técnica scaffolding en los controladores y las vistas para reflejar un modelo sin sobrescribir los retoques. Por lo tanto, se debe ejercer una vigilancia de los módulos que se han personalizado para saber a qué modelos se les puede volver a aplicar la técnica scaffolding y a cuáles no.

En un entorno de equipo, esta vigilancia es difícil de hacer cumplir. Para colmo, el controlador de edición muestra la mayoría de las propiedades del modelo en la vista Edit, por lo que cierta información confidencial puede quedar expuesta. Enlaza modelos y conserva ciegamente todas las propiedades enviadas a las vistas, lo que aumenta el riesgo de un ataque por asignación masiva.

En este artículo, mostraré cómo crear personalizaciones de plantillas T4 específicas de proyectos que potencien el subsistema de scaffolding CRUD de Entity Framework para ASP.NET MVC. En el camino, mostraré cómo ampliar los controladores de postback de Create y Edit del controlador para que puedas incluir tu propio código entre el enlace de modelos de postback y la persistencia del almacén de datos.

Para abordar los problemas de asignación masiva, crearé una atributo personalizado que te dé control total de las propiedades del modelo que deben conservarse y de las que no. Luego voy a agregar otro atributo personalizado que te permita mostrar una propiedad como etiqueta de solo lectura en la vista Edit.

Después de eso, tendrás un control sin precedentes de las páginas CRUD y de cómo se muestran y conservan los modelos, a la vez que reduces la exposición de tu aplicación a los ataques. Lo mejor de todo es que podrás aprovechar estas técnicas para todos los modelos en tus proyectos de ASP.NET MVC y volver a generar controladores y vistas con seguridad a medida que cambien tus modelos.

Configuración del proyecto

Desarrollé esta solución con Visual Studio 2013 Ultimate, ASP.NET MVC 5, Entity Framework 6 y C# (las técnicas que se analizan también son compatibles con Visual Studio 2013 Professional, Premium y Express para Web y Visual Basic .NET). Creé dos soluciones para descarga: La primera es la solución inicial, que se puede usar para comenzar con un proyecto funcional e implementar manualmente estas técnicas. La segunda es la solución completa, que incluye todas las mejoras que se abordan aquí.

Cada solución contiene tres proyectos: uno para el sitio web ASP.NET MVC, uno para los modelos de entidad y funciones de scaffold T4, y uno para el contexto de datos. El contexto de datos de las soluciones apunta a una base de datos de SQL Server Express. Además de las dependencias ya mencionadas, agregué Bootstrap usando NuGet para aplicar temas en las vistas a las que apliqué la técnica scaffolding.

El subsistema de scaffold se instala durante la instalación cuando se marca la opción de instalación de Microsoft Web Developer Tools. Posteriormente, los Service Pack de Visual Studio actualizarán los archivos de scaffold automáticamente. Puedes obtener las actualizaciones del sistema de scaffold publicadas entre los Service Pack de Visual Studio en la versión más reciente del Instalador de plataforma web de Microsoft, que se puede descargar de bit.ly/1g42AhP.

Si tienes algún problema con el código descargado que se adjunta a este artículo, asegúrate de tener la versión más reciente y lee con cuidado el archivo Léame.txt. Lo actualizaré según sea necesario.

Definir las reglas empresariales

Para ilustrar el flujo de trabajo completo que involucra generar vistas CRUD y reducir las distracciones, voy a trabajar con un modelo de entidad muy sencillo llamado 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 convención, MVC entiende que ProductID es la clave principal, pero no tiene idea de que tengo requisitos especiales relativos a las propiedades CreatedDate y ModifiedDate. Como su nombre indica, quiero que CreatedDate especifique cuándo se insertó el producto en cuestión (representado por ProductID) en la base de datos. También quiero que ModifiedDate indique cuándo se modificó por última vez (usaré valores de fecha y hora UTC).

Quiero mostrar el valor ModifiedDate en la vista Edit como texto de solo lectura (si el registro nunca se modificó, ModifiedDate será igual a CreatedDate). No quiero mostrar CreatedDate en ninguna de las vistas. Tampoco quiero que el usuario pueda indicar ni modificar estos valores de fecha, por lo que no quiero darles ningún control de formularios que recoja información en las vistas Create o Edit.

En un intento por hacer que estos valores soporten ataques, quiero asegurarme de que sus valores se conserven en el postback, incluso si un hacker hábil es capaz de indicarlos como campos de formularios o valores de cadena de consulta. Al considerar estas reglas empresariales de capa lógica, no quiero que la base de datos tenga ninguna responsabilidad por el mantenimiento de estos valores de columnas (por ejemplo, no quiero crear ningún desencadenador ni incrustar ninguna lógica de definición de tabla o columna).

Explorar el flujo de trabajo de scaffold CRUD

Primero vamos a examinar la función predeterminada de scaffold. Voy a hacer clic en el botón secundario en la carpeta Controllers del proyecto web y elegir Add Controller (Agregar controlador) para agregar un controlador. Esto inicia el cuadro de diálogo Add Scaffold (Agregar scaffold) (ver figura 1).

The MVC 5 Add Scaffold Dialog
Figura 1 El cuadro de diálogo Add Scaffold de MVC 5

La entrada “MVC 5 Controller with views, using Entity Framework” (Controlador de MVC 5 con vistas usando Entity Framework) es la que voy a usar porque es la que aplica la técnica scaffolding a los controladores y vistas CRUD de un modelo. Selecciona esa entrada y haz clic en Add (Agregar). El siguiente cuadro de diálogo da varias opciones que serán parámetros de las plantillas T4 que se transformarán posteriormente (ver figura 2).

The Add Controller Dialog
Figura 2 El cuadro de diálogo Add Controller

Introduce ProductController como nombre del controlador. No marques la casilla “Use the async controller actions” (Usar las acciones del controlador asincrónico) a los efectos de esta discusión (las operaciones asincrónicas están fuera del ámbito de este artículo). Luego elije la clase del modelo de Product. Debido a que usas Entity Framework, vas a necesitar una clase de contexto de datos. Las clases que heredan de System.Data.Entity.DbContext aparecen en la lista desplegable; selecciona la correcta si tu solución usa más de un contexto de base de datos. Con respecto a las opciones de vista, marca “Generate views” (Generar vistas) y “Use a layout page” (Usar una página de diseño). Deja vacío el cuadro de texto de la página de diseño.

Cuando hagas clic en Add (Agregar), varias plantillas T4 se transforman para proporcionar los resultados tras la técnica scaffolding. Este proceso genera un código para un controlador (ProductController.cs), que se escribe en la carpeta Controllers del proyecto web, y cinco vistas (Create.cshtml, Delete.cshtml, Details.cshtml, Edit.cshtml e Index.cshtml), que se escriben en la carpeta Views del proyecto web. En este momento, tienes un controlador funcional y todas las vistas CRUD que necesitas para administrar los datos en la entidad Product. Puedes empezar a usar estas páginas web de inmediato a partir de la vista de índice.

Probablemente quieras que las páginas CRUD se vean y se comporten de manera similar para todos los modelos de tu proyecto. Usar las plantillas T4 para aplicar la técnica scaffolding a las páginas CRUD ayuda a reforzar esta uniformidad. Esto significa que hay que resistirse a la tentación de modificar directamente los controladores y las vistas. En su lugar, debes modificar las plantillas T4 que los generan. Sigue este consejo para asegurarte de que los archivos a los que apliques la técnica scaffolding estén listos para usar sin modificaciones adicionales.

Examinar los defectos del controlador

Si bien el subsistema de scaffold te pone en marcha bastante rápido, el controlador que genera tiene varios defectos. Te voy a mostrar cómo hacer algunas mejoras. Observa los métodos de acción del controlador a los que se ha aplicado la técnica de scaffolding y que controlan las vistas de creación y edición en la figura 3.

Figura 3 Los métodos de acción del controlador a los que se ha aplicado la técnica scaffolding para Create y 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); }

El atributo Bind para cada método incluye de manera explícita cada propiedad del modelo Product. Cuando un modelo de controlador de MVC enlaza todas las propiedades del modelo después del postback, se llama asignación masiva. También se llama sobrepublicación y es una vulnerabilidad grave de la seguridad. Los hackers pueden explotar esta vulnerabilidad porque hay una invocación SaveChanges posterior en el contexto de la base de datos. Esto asegura que el modelo se conserve en el almacén de datos. La plantilla del controlador que usa el sistema de scaffold CRUD en MVC5 genera un código de asignación masiva de modo predeterminado para los métodos de postback de las acciones Create y Edit.

Otra consecuencia de la asignación masiva es cuando eliges representar ciertas propiedades del modelo de modo que no se representen en la vista Create o Edit. Estas propiedades se configurarán con valor NULL después del enlace de modelos. (Consulta “Use Attributes to Supress Properties on CRUD Views” (Usar atributos para suprimir propiedades en las vistas CRUD), que contiene atributos que puedes usar para especificar si debes procesar las propiedades a las que se aplicó la técnica scaffolding en las vistas generadas). Para ilustrar, agregaré dos atributos al 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; } }

Cuando vuelvo a ejecutar el proceso de scaffold usando Add Controller (Agregar controlador), como se mencionó anteriormente, el atributo [Scaffold(false)] asegura que CreatedDate no aparecerá en ninguna vista. El atributo [Editable(false)] asegura que ModifiedDate aparecerá en las vistas Delete, Details e Index, pero no en las vistas Create o Edit. Cuando las propiedades no se procesan en la vista Create o Edit, no aparecerán en la cadena de solicitud HTTP de postback.

Esto es un problema porque la última oportunidad que tienes para asignar valores a las propiedades del modelo en estas páginas CRUD que usan MVC es durante el postback. Entonces, si el valor postback de una propiedad es nulo, ese valor nulo estará limitado por el modelo. Luego, el modelo se conservará en el almacén de datos cuando SaveChanges se ejecute en el objeto de contexto de datos. Si esto se hace en el método de acción Edit de postback, esa propiedad se sustituirá por un valor nulo. Esto elimina de manera eficaz el valor actual en el almacén de datos.

En mi ejemplo, el valor de CreatedDate en el almacén de datos se perdería. De hecho, cualquier propiedad que no se procese en la vista Edit provocará que el valor del almacén de datos se sobrescriba con un valor nulo. Si la propiedad del modelo o el almacén de datos no permiten la asignación de un valor nulo, recibirás un error en el postback. Para superar estos defectos, modificaré la plantilla T4, que es responsable de generar el controlador.

Reemplazar las plantillas para scaffold

Para modificar la técnica scaffolding del controlador y las vistas, debes modificar las plantillas T4 que los generan. Puedes modificar las plantillas originales, que afectan de manera global a la técnica scaffolding en todos los proyectos de Visual Studio. También puedes modificar las copias específicas del proyecto de las plantillas T4, que solo afectarán al proyecto donde se coloquen las copias. Lo haré más adelante.

Las plantillas T4 para scaffolding originales se encuentran en la carpeta %programfiles%\Microsoft Visual Studio 12.0\Common7\IDE\Extensions\Microsoft\Web\Mvc\Scaffolding\Templates. (Estas plantillas dependen de varios ensamblados .NET, ubicados en la carpeta %programfiles%\­Microsoft Visual Studio 12.0\Common7\IDE\Extensions\Microsoft\Web Tools\Scaffolding). Me concentraré en las plantillas específicas que aplican la técnica scaffolding a los controladores y las vistas CRUD en Entity Framework. Estos se encuentran resumidos en la figura 4.

Figura 4 Las plantillas T4 que aplican la técnica scaffolding a los controladores y las vistas CRUD en Entity Framework

Nombre de la subcarpeta de plantillas para scaffold

Nombre del archivo de plantilla

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

Genera este archivo

(.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 crear plantillas específicas del proyecto, copia los archivos que quieras reemplazar desde la carpeta de plantillas T4 para scaffold originales en una carpeta del proyecto web de ASP.NET MVC llamada CodeTemplates (debe tener este nombre exactamente). Por convención, el subsistema de scaffold primero busca una plantilla coincidente en la carpeta CodeTemplates del proyecto de MVC.

Para que funcione, debes replicar con exactitud los nombres de subcarpetas y los nombres de archivos específicos que ves en la carpeta de plantillas originales. Copié los archivos T4 que pienso reemplazar desde CRUD para el subsistema de scaffold en Entity Framework. Observa mi CodeTemplates del proyecto web en la figura 5.

The Web Project’s CodeTemplates
Figura 5 CodeTemplates del proyecto web

También copié Imports.include.t4 y ModelMetadataFunctions.cs.include.t4. El proyecto necesita estos archivos para aplicar la técnica scaffolding a las vistas. Además, copié solo las versiones de C# (.cs) de los archivos (si quieres usar Visual Basic .NET, deberás copiar los archivos que incluyen .vb en el nombre de archivo). El subsistema de scaffolding transformará estos archivos específicos del proyecto, en lugar de sus versiones globales.

Ampliar los métodos de acción de Create y Edit

Ahora que tengo plantillas T4 específicas del proyecto, puedo modificarlas según sea necesario. En primer lugar, voy a ampliar los métodos de acción Create y Edit del controlador para poder inspeccionar y modificar el modelo antes de conservarlo. Para mantener el código que genera la plantilla lo más genérico posible, no quiero agregar ninguna lógica específica del modelo a la plantilla. En su lugar, quiero llamar a una función externa ligada al modelo. De este modo, Create y Edit del controlador se amplían mientras simulan polimorfismos en el modelo. Para ello, crearé una interfaz y la llamaré IControllerHooks:

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

Luego, modificaré la plantilla Controller.cs.t4 (en la carpeta CodeTemplates\­MVCControllerWithContext) para que los métodos de acción Create y Edit de postback llamen a los métodos OnCreate y OnEdit, respectivamente, si el modelo ha implementado IControllerHooks. El método de acción Create de postback del controlador se muestra en la figura 6 y el método de acción Create de postback se muestra en la figura 7.

Figura 6 Versión ampliada del método de acción Create de postback del 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 Versión ampliada del método de acción Edit de postback del 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); }

Ahora, modificaré la clase Product para que implemente IController-Hooks. Luego agregaré el código que quiero ejecutar cuando el controlador llame a OnCreate y OnEdit. La nueva clase del modelo Product aparece en la figura 8.

Figura 8 Modelo Product que implementa IControllerHooks para ampliar el 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; } }

Hay que admitir que hay muchas maneras de implementar esta lógica de “ampliación”, pero con esta modificación de una línea de los métodos Create y Edit de la plantilla del controlador, puedo modificar la instancia del modelo Product después del enlace de modelos, pero antes de la persistencia. Incluso puedo definir el valor de las propiedades del modelo que no se publican en las vistas Create y Edit.

Verás que la función OnEdit del modelo no define un valor para CreatedDate. Si CreatedDate no se procesa en la vista Edit, se reemplazará por un valor nulo después de que el método de acción Edit del controlador persista el modelo cuando llame a SaveChanges. Para evitarlo, tendré que hacer algunas modificaciones más en la plantilla del controlador.

Mejorar el método de acción Edit

Ya he mencionado algunos de los problemas asociados con la asignación masiva. Una manera de modificar el comportamiento de enlace de medios es modificar el atributo Bind para que las propiedades no se enlacen. Sin embargo, en la práctica este enfoque puede provocar que se escriban valores nulos en el almacén de datos. Una mejor estrategia implica programación adicional, pero el beneficio bien vale la pena.

Voy a usar el método Attach de Entity Framework para adjuntar el modelo al contexto de la base de datos. Luego puedo hacer un seguimiento de la entrada de la entidad y definir la propiedad IsModified según sea necesario. Para apoyar esta lógica, crearé un nuevo módulo de clase llamado CustomAttributes.cs en el proyecto JW_Scaffold­Enhancement.Models (ver figura 9).

Figura 9 El nuevo módulo de clase CustomAttributes.cs

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

Usaré este atributo para indicar las propiedades que no quiero que se conserven en el almacén de datos desde la vista Edit (las propiedades no representativas tendrán un atributo implícito [PersistPropertyOnEdit(true)]). Me interesa evitar que la propiedad CreatedDate se conserve, así que he agregado el nuevo atributo solo a la propiedad CreatedDate de mi modelo Product. La clase de modelo recién representado se puede ver aquí:

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

Ahora tengo que modificar la plantilla Controller.cs.t4 para que cumpla con el nuevo atributo. Al mejorar una plantilla T4, tienes la opción de hacer cambios dentro de la plantilla o fuera de la plantilla. A menos que uses una de las herramientas de edición de plantillas de otro fabricante, yo aconsejo escribir la mayor cantidad de código posible en un módulo de código externo. Esto permite un lienzo de C# puro (en lugar de uno intercalado con el marcado T4) dentro del cual puedes concentrarte en el código. También ayuda en las pruebas y te da una forma de incorporar tus funciones a los esfuerzos más amplios del agente de pruebas. Finalmente, debido a algunos defectos en cómo se hace referencia a los ensamblados desde un scaffold T4, experimentarás menos problemas técnicos al conectar todo.

El proyecto My Models contiene una función pública llamada GetPropertyIsModifiedList, que devuelve una List<String> que puedo iterar para generar la configuración de IsModified para el ensamblado y el tipo pasados. La figura 10 muestra este código en la plantilla Controller.cs.t4.

T4 Template Code Used To Generate an Improved Controller Edit Postback Handler
Figura 10 Código de plantilla T4 usado para generar un mejor control de Edit de postback del controlador

En GetPropertyIsModifiedList, que se muestra en la figura 11, uso reflexión para obtener acceso a las propiedades del modelo proporcionadas. Luego las itero para determinar cuáles se representan mediante el atributo PersistPropertyOnEdit. Muy probablemente quieras conservar la mayoría de las propiedades en tus modelos, así que construí el código de la plantilla para definir el valor IsModified de una propiedad en true de modo predeterminado. De este modo, lo único que hay que hacer es agregar [PersistPropertyOnEdit(false)] a las propiedades que no quieres que se conserven.

Figura 11 La función estática ScaffoldFunctions.GetPropertyIsModifiedList del proyecto del modelo

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

La plantilla corregida del controlador genera un método de acción Edit de postback reconcebido, según aparece en la figura 12. Mi función GetPropertyIsModifiedList genera porciones de este código de origen.

Figura 12 El control Edit del controlador donde recientemente se aplicó la técnica scaffolding

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 propiedades en vistas CRUD

ASP.NET MVC ofrece solo tres atributos que dan algo de control sobre si las propiedades de un modelo se procesan en las vistas donde se aplicó la técnica scaffolding (ver figura A). Los primeros dos atributos hacen lo mismo (aunque residen en espacios de nombres diferentes): [Editable(false)] y [ReadOnly(true)]. Esto hará que la propiedad representada no se procese en las vistas Create y Edit. El tercero, [ScaffoldColumn(false)], hace que la propiedad representada no aparezca en ninguna de las vistas procesadas.

Figura A Los tres atributos que evitan el procesamiento de propiedades

Atributo de metadatos del modelo Espacio de nombres del atributo Vistas afectadas Qué ocurre
  Ninguno Ninguno Ningún atributo adicional provoca solo resultados normales.

[Editable(false)]

[ReadOnly(true)]

Editable:

System.ComponentModel.DataAnnotations

ReadOnly:

System.ComponentModel

Creación

Editar

No se procesa la propiedad del modelo representada.
[ScaffoldColumn(false)] System.ComponentModel.DataAnnotations

Creación

Eliminar

Detalles

Editar

Índice

No se procesa la propiedad del modelo representada.

Personalizar una vista

En ocasiones, quieres mostrar un valor en la vista Edit que no quieres que los usuarios editen. Los atributos proporcionados por ASP.NET MVC no admiten esto. Me gustaría ver ModifiedDate en la vista Edit, pero no quiero que el usuario piense que es un campo editable. Para implementar esto, crearé otro atributo personalizado llamado DisplayOnEditView en el módulo de la clase CustomAttributes.cs como se ve aquí:

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

Esto me permite representar una propiedad del modelo para que se procese como una etiqueta en la vista Edit. Luego voy a poder mostrar ModifiedDate en la vista Edit sin preocuparme por que alguien pueda alterar este valor durante el postback.

Ahora puedo usar ese atributo para seguir representando el modelo Product. Voy a colocar el nuevo atributo en la propiedad ModifiedDate. Usaré [Editable(false)] para asegurar que no aparezca en la vista Create y [DisplayOnEditView(true)] para asegurar que aparezca como etiqueta en la vista 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; } }

Finalmente, modificaré la plantilla T4 que genera la vista Edit para que cumpla con el atributo DisplayOnEditView:

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

Y agregaré la función GetHtmlForDisplayOnEditViewAttribute a la clase ScaffoldFunctions como se muestra en la figura 13.

La función GetHtmlForDisplayOnEditViewAttribute devuelve Html.EditorFor cuando el atributo es false y Html.Display­TextFor cuando el atributo es true. La vista Edit mostrará ModifiedDate como etiqueta y todos los demás campos no clave como cuadros de texto editables, como se ve en la figura 14.

Figura 13 Función estática ScaffoldFunctions.GetHtmlForDisplayOnEditViewAttribute para admitir el 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 Vista Edit con un campo ModifiedDate de solo lectura

En resumen

Acabo de limar la superficie de lo que se puede lograr con el subsistema de scaffold. Me centré en los scaffolds que proporcionan un control y vistas CRUD para Entity Framework, pero hay otros scaffolds disponibles para generar código para otros tipos de páginas web y acciones de API web.

Si no has trabajado nunca con plantillas T4, personalizar plantillas existentes es una manera fantástica de comenzar. Si bien las plantillas analizadas aquí se inician desde menús con el IDE de Visual Studio, puedes crear plantillas T4 personalizadas y transformarlas cuando sea necesario. Microsoft proporciona un buen punto de partida en bit.ly/1coB616. Si buscas algo más avanzado, recomiendo el curso de Dustin Davis en bit.ly/1bNiVXU.

En este momento, Visual Studio 2013 no incluye un editor robusto de T4. De hecho, no ofrece sintaxis resaltada ni IntelliSense. Afortunadamente, hay algunos complementos que sí lo hacen. Comprueba Devart T4 Editor (bit.ly/1cabzOE) y Tangible Engineering T4 Editor (bit.ly/1fswFbo).

Jonathan Waldman es un desarrollador de software y arquitecto técnico experimentado. Ha trabajado con componentes tecnológicos de Microsoft desde sus comienzos. Ha trabajado en varios proyectos empresariales de alto perfil y ha participado en todos los aspectos del ciclo de vida del desarrollo de software. Es miembro del equipo técnico de Pluralsight y sigue desarrollando soluciones de software y materiales educativos. Puedes ponerte en contacto con él en jonathan.waldman@live.com.

Gracias al siguiente experto técnico de Microsoft por su ayuda en la revisión de este artículo: Joost de Nijs
Joost de Nijs es jefe de programas del equipo de Azure Developer Experience en Microsoft y trabaja con Web Developer Tooling. Actualmente, se centra en las áreas de administración de scaffolding web y paquetes NuGet, con trabajos previos en las Bibliotecas de cliente de Java para Azure y el Centro para desarrolladores de Azure.