Jorge Serrano
  • Home

No uses DataAnnotations en interfaces

  • By jorge
  • Oct-19-2014
  • Sin categoría
  • 2 Comments.
Disclaimer

Quizás el uso de DataAnnotations en una interfaz no sea precisamente una buena práctica, así que no me aticéis por ahí porque el objetivo de esta entrada no es discutir eso, sino saltarnos esa recomendación y demostrar algunas cosas. De hecho, el uso de DataAnnotations tiene como objetivo también, evitar su uso en interfaces, siendo precisamente en su implementación donde tengamos la libertad de adoptarlas o crear nuestras propias DataAnnotations personalizadas.

Introducción

El título dicho así, puede sonar un poco drástico, pero sí,… usar DataAnnotations en nuestras interfaces no es una buena idea, y en esta entrada lejos de aceptar los axiomas que algunos creen porque otros dicen que es así y punto, voy a explicarte de forma práctica porqué digo esto.

¿Qué es o que son las DataAnnotations?

Aunque mucha gente sabe situar perfectamente DataAnnotations, me veo en la obligación de hacer una pequeña parada para indicar que son por si te pilla de nuevas o no lo tienes claro del todo.

El atributo de validación System.ComponentModel.DataAnnotations tiene como misión la validación de los campos de un modelo de datos, e incluso de una entidad si queremos.

Gracias al atributo DataAnnotations, podemos validar nuestros modelos o incluso crear nuestras propias validaciones personalizadas.

Es bastante común encontrarnos con DataAnnotations en aplicaciones web ASP.NET MVC, aunque no se nos impide para nada el uso de DataAnnotations en aplicaciones Web API, de consola o cualquier otra. De hecho, el uso de validaciones de los campos con los que va a trabajar nuestra aplicación no lo considero una opción.

Una vez introducido de forma breve lo que son DataAnnotations, vamos al cubrir de lleno el objetivo de esta entrada, que es el de explicarte por qué a mi juicio no es una buena idea utilizarlas en nuestras interfaces (por si creías que sí).

Desarrollo de un ejemplo simple con DataAnnotations

Lo primero que vamos a hacer es crear una aplicación de consola, dentro de la cual crearemos un modelo o entidad llamada Employee.

Dicha entidad tendrá dos sencillos campos. Nombre (Name) y Edad (Age).

Sobre el campo Age agregaremos una DataAnnotation que nos indicará que el valor de esta propiedad tendrá que estar comprendido entre 18 y 150.

Nuestra entidad quedará de la siguiente manera:

Employee

using System.ComponentModel.DataAnnotations;

namespace ConsoleApplication1
{

    public class Employee : IEmployee
   
{

        public string Name { get; set; }

        [Range(18, 150)]

        public byte Age { get; set; }

    }

}

La propiedad Age de nuestro modelo está decorado con una DataAnnotation de tipo Range.

Sin embargo, a la hora de validar nuestro modelo o entidad en nuestra aplicación de consola, deberemos hacer uso de una función que se encargará de validarlo entero, permitiéndonos mostrar por pantalla el campo o campos que no cumplen la condición de validación.

Este método que nos hemos creado podría bien ser el siguiente:

public static bool DataAnnotationsValidator(object @object, out List<ValidationResult> results)
{
   
var context = new ValidationContext(@object, serviceProvider: null, items: null);
   
results = new List<ValidationResult>();
   
return Validator.TryValidateObject(@object, 
                                                  
context,  
                                                  
results, 
                                                  
validateAllProperties: true);
}

Y la llamada a este método desde nuestra aplicación de consola para forzar el error de validación sería:

        static void Main(string[] args)
       
{
           
Console.WriteLine("Ejecución iniciada");
           
Employee employee = new Employee();
           
employee.Name = "John";
           
employee.Age = 17;
           
var results = new List<ValidationResult>();
           
var modelIsValid = DataAnnotationsValidator(employee, out results);
           
Console.WriteLine(String.Format("¿Es el modelo válido? {0}", modelIsValid.ToString()));
           
if (!modelIsValid)
           
{
               
foreach (var validationResult in results)
               
{
                   
Console.WriteLine(validationResult.ErrorMessage);
               
}
           
}
           
Console.WriteLine("Ejecución finalizada");
           
Console.ReadLine();
       
}

Nuestra aplicación en ejecución queda de la siguiente manera:

Como podemos observar, nuestra aplicación funciona tal y como esperamos.

Ampliando nuestro ejemplo

Ahora bien, de repente nos llega un requisito funcional que debemos cubrir.

Supongamos que nos han encargado tratar también los empleados externos.

Un empleado externo según los requisitos que nos han hecho llegar, tendrá los mismos campos que un usuario más el campo empresa, que indicará la empresa a la que pertenece este trabajador externo.

Aquí tenemos muchas posibilidades, pero dentro del equipo de arquitectura y del equipo técnico, entre las opciones de crear una clase abstracta, una clase base o una interfaz, o modificar el modelo para que cubra ambas opciones, se ha pensado que la mejor forma de abordar esto sea con una interface.

Alguien ha pensado incluso y después de las reuniones mantenidas con la gente de negocio (básicamente RRHH en nuestro ejemplo ficticio), que es posible que en un futuro próximo haya además de los empleados internos y los empleados externos, otros empleados,… o que quizás las propiedades nombre y edad usadas en el modelo empleado, pueda ser reutilizado por otros modelos o entidades, así que no es mala idea hacer esto (aceptamos pulpo en nuestro ejemplo).

Así que hagamos un esfuerzo más e imaginemos que nuestro proyecto no sólo posee la entidad Employee, sino que hay cerca de 50 ó más entidades o partes del modelo, y que esta interfaz nos va a venir muy bien porque de un plumazo vamos a reutilizar bastante código, y poniendo una DataAnnotation en una interfaz, resolveremos el problema de tener que poner la validación de rango en todas y cada una de las entidades. No parece mala idea… a priori…

Es decir,… nuestra interfaz y clases quedarán ahora de la siguiente manera:

IEmployee

using System.ComponentModel.DataAnnotations;

namespace ConsoleApplication1
{
    
   
public interface IEmployee
   
{
       
byte Age { get; set; }
       
[Range(18, 150)]
       
string Name { get; set; }
   
}
}

Employee

using System.ComponentModel.DataAnnotations;

namespace ConsoleApplication1
{
   
public class Employee : IEmployee
   
{
       
public string Name { get; set; }
       
public byte Age { get; set; }
   
}
}

ExternEmployee

using System.ComponentModel.DataAnnotations;

namespace ConsoleApplication1
{
   
public class ExternEmployee : IEmployee
   
{
       
public string Company { get; set; }
       
public string Name { get; set; }
       
public byte Age { get; set; }
   
}
}

Bien… ahora ejecutemos nuevamente nuestra aplicación de consola.

Obtendremos una respuesta como la que se indica a continuación:

Como podemos apreciar en la respuesta de ejecución de nuestra aplicación, el modelo o entidad Employee nos indica que es correcta.

¿Cómo es posible?.

Es evidente que la interfaz nos está indicando que la propiedad Age debe estar dentro de un rango de valores que no cumple. Pero como podemos ver, a la hora de ejecutar la acción de validación no está funcionando como esperábamos.

Modificando nuestro ejemplo

Bueno… el equipo de desarrollo se ha reunido para tratar de entender un problema que está sucediendo y que se nos ha escapado en algún momento.

Se ha dedicado bastante tiempo, un tiempo perdido al fin y al cabo, así que después todo este camino recorrido se decide cambiar la interfaz por una clase abstracta.

Esta clase abstracta es la siguiente:

EmployeeAbstract

using System.ComponentModel.DataAnnotations;

namespace ConsoleApplication1
{
    
   
public abstract class EmployeeAbstract
   
{
       
byte Age { get; set; }
       
[Range(18, 150)]
       
string Name { get; set; }
   
}
}

Y nuestras clases anteriores quedarán de la siguiente manera:

Employee

using System.ComponentModel.DataAnnotations;

namespace ConsoleApplication1
{
   
public class Employee : EmployeeAbstract
   
{
       
public string Name { get; set; }
       
public byte Age { get; set; }
   
}
}

ExternEmployee

using System.ComponentModel.DataAnnotations;

namespace ConsoleApplication1
{
   
public class ExternEmployee : EmployeeAbstract
   
{
       
public string Company { get; set; }
       
public string Name { get; set; }
       
public byte Age { get; set; }
   
}
}

Si ejecutamos nuevamente nuestro código, obtendremos un resultado como el que se indica a continuación:

Y aunque no lo voy a hacer aquí, exactamente igual ocurrirá si creamos una clase base que sea heredada por Employee y ExternEmployee.

¿Y esto por qué ocurre?.

El porqué de las cosas

La explicación de este comportamiento tenemos que buscarlo en el CLR de .NET y como funciona por dentro de acuerdo a como quiere que se comporte el equipo de .NET. No obstante, y para comprenderlo mejor, vamos a hacer alguna reflexión previa.

Las clases base son heredadas, tal y como he comentado anteriormente, es decir, los métodos, propiedades y funciones de una clase base, estarán presentes en la clase que la hereda. Por esta razón, una DataAnnotation de una clase base, estará en la clase que lo hereda, en la clase derivada.

Las interfaces por otra parte, son contratos o implementaciones a llevar a cabo, y dichas implementaciones no están presentes en la clase que implementa la interfaz hasta que esta implementación se representa como tal. Esto significa por extensión, que un metadato presente en una interfaz, NO es agregado dentro de la clase que implementa dicha interfaz. Por esta razón, si una interfaz tiene una DataAnnotation, ésta no llega a la clase que implementa esa interfaz.

Pero podemos dar una vuelta de tuerca más a este asunto. Imaginemos que tenemos una clase que implementa dos interfaces, y que estas dos interfaces poseen ambas la misma propiedad, pero con la salvedad de que una de ellas tiene una DataAnnotation y otra no, o dos DataAnnotations diferentes… ¿cual de ellas es la que tenemos que usar?.

Sin embargo, las interfaces chocan en cuanto a su implementación y uso con las clases abstractas, que son otra cosa diferente y posee la herencia como parte fundamental, algo que hemos visto, sí está soportado de forma directa.

Comments

2 Responsesso far

  1. kiquenet dice:
    10 febrero, 2015 a las 11:12 am

    Muy interesante, Jorge. Saludos.

    Responder
  2. jorge dice:
    28 febrero, 2015 a las 3:40 pm

    Muchas gracias Kiquenet. 😀

    Responder

Deja un comentario Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

← Previous Post Next Post →

Jorge Serrano

MVP Reconnect


¡Subscríbete a mi canal!
YouTube

Donaciones
Donation

Entradas recientes

  • Go – Arrays
  • Go – Operators
  • Go – Constants
  • Go – Tipos de Datos
  • Go – Variables
  • Hello Go-rld!
  • Introducción a Go o Golang
  • JSON Patch en ASP.NET Core 5 Web API
  • Null Checking en C#
  • ¿Porqué mi página web por defecto de ASP.NET Core no se vé en mi Azure Web App y me da un 404?

Categorías

  • .NET 5
  • .NET Core
  • .NET Core 3.0
  • .NET Core 3.1
  • .NET Framework 2.0
  • .NET Framework 3.0
  • .NET Framework 3.5
  • .NET Framework 4.0
  • .NET Framework 4.5
  • .NET Framework 4.6
  • .NET Framework 4.7
  • .NET Framework 4.8
  • .NET Standard 2.0
  • .NET Standard 2.1
  • AMQP
  • Android
  • Angular
  • API REST
  • Apple
  • Apple iOS
  • Apple macOs
  • Arquitectura
  • ASP.NET
  • ASP.NET Core
  • ASP.NET Core 3
  • ASP.NET Core 5
  • AWS
  • Azure App Service
  • Azure Application Insights
  • Azure Cosmos DB
  • Azure Database Migration Service
  • Azure Databricks
  • Azure DevOps
  • Azure Event Grid
  • Azure Functions
  • Azure IoT
  • Azure Portal
  • Azure PowerShell
  • Azure Queue Storage
  • Azure SQL
  • Azure Storage
  • Azure Virtual Datacenter
  • Azure WebApps
  • Big Data
  • Bing
  • Blazor
  • Blog
  • Bots
  • C#
  • C# 7.0
  • C# 7.1
  • C# 7.2
  • C# 7.3
  • C# 8.0
  • C# 9.0
  • Channel 9
  • Codeplex
  • Codespaces
  • Containers
  • Debugging
  • DevOps
  • Docker
  • Electron
  • Entity Framework
  • Entity Framework Core
  • Entity Framework Core 3.0
  • Entity Framework Core 5
  • Eventos
  • F#
  • FaaS
  • FeatureFlags
  • FeatureToggles
  • Feeds
  • Fluent Assertions
  • General
  • GIMP
  • Git
  • GitHub
  • Go
  • Google
  • Google Analytics
  • Gradle
  • gRPC
  • GSA
  • Historia de la Informática
  • HoloLens
  • HtmlAgilityPack
  • IdentityServer4
  • Inkscape
  • Ionic
  • iOS
  • IoT
  • Java
  • JavaScript
  • JDBC
  • JSON
  • Kubernetes
  • Lenguajes de Programación
  • Libros y Cursos
  • LINQ
  • Linux
  • LiteDB
  • Machine Learning
  • macOS
  • Microservices
  • Microsoft
  • Microsoft .NET Framework 4.5
  • Microsoft 365
  • Microsoft Azure
  • Microsoft Build
  • Microsoft Ignite
  • Microsoft Learn
  • Microsoft Orleans
  • Microsoft Surface Go
  • Microsoft Teams
  • ML.NET
  • MQTT
  • MRO
  • MS-DOS
  • MsCoders Madrid
  • MVP
  • NancyFx
  • Node.js
  • NoSQL
  • NuGet
  • NUnit
  • OData
  • ODP.NET Core
  • Office 2007
  • Office 2010
  • Office 2013
  • Office 2016
  • Office 2019
  • Office 365
  • Open Source
  • Open XML SDK
  • Opinión
  • Orchard CMS
  • OT
  • PaaS
  • Patterns
  • PdfSharpCore
  • Performance
  • PHP
  • Postman
  • Power BI
  • PowerShell
  • PowerShell Core
  • Productividad
  • Project Server 2019
  • R
  • Rendimiento
  • Scala
  • Scraper
  • Security
  • Serverless
  • Service Fabric
  • SharePoint Server 2019
  • SignalR
  • Sin categoría
  • Sistemas Distribuidos
  • Skype
  • Skype for Business Server 2019
  • Small Basic Online
  • SQL Server 2005
  • SQL Server 2008
  • SQL Server 2012
  • SQL Server 2014
  • SQL Server 2016
  • SQL Server 2017
  • SQL Server 2019
  • STOMP
  • Swagger
  • Testing
  • TFS 2017
  • TFS 2018
  • Tools
  • TypeScript
  • Unity
  • UWP
  • UX
  • Visio
  • Visual Basic
  • Visual Studio 2010
  • Visual Studio 2012
  • Visual Studio 2013
  • Visual Studio 2015
  • Visual Studio 2017
  • Visual Studio 2017 for Mac
  • Visual Studio 2019
  • Visual Studio 2019 for Mac
  • Visual Studio App Center
  • Visual Studio Code
  • Visual Studio IntelliCode
  • Visual Studio Live Share
  • Visual Studio Live Share Audio
  • Visual Studio Online
  • VS Anywhere
  • Vue.js
  • Web API
  • WebAssembly
  • WinDbg
  • Windows
  • Windows 10
  • Windows Compatibility Pack
  • Windows Phone 10
  • Windows Phone 7
  • Windows Phone 8
  • Windows Server 2008
  • Windows Server 2012
  • Windows Server 2016
  • Windows Server 2019
  • Windows Service
  • WinForms
  • WinUI
  • WPF
  • Xamarin
  • Xbox
  • Xcode
  • Xiaomi Mi Band 2
  • xUnit
  • YAML

Archivos

  • enero 2021
  • diciembre 2020
  • noviembre 2020
  • octubre 2020
  • septiembre 2020
  • agosto 2020
  • julio 2020
  • junio 2020
  • mayo 2020
  • abril 2020
  • marzo 2020
  • febrero 2020
  • enero 2020
  • diciembre 2019
  • noviembre 2019
  • octubre 2019
  • septiembre 2019
  • agosto 2019
  • julio 2019
  • junio 2019
  • mayo 2019
  • abril 2019
  • marzo 2019
  • febrero 2019
  • enero 2019
  • diciembre 2018
  • noviembre 2018
  • octubre 2018
  • septiembre 2018
  • agosto 2018
  • julio 2018
  • junio 2018
  • mayo 2018
  • abril 2018
  • marzo 2018
  • febrero 2018
  • enero 2018
  • diciembre 2017
  • noviembre 2017
  • octubre 2017
  • septiembre 2017
  • agosto 2017
  • julio 2017
  • junio 2017
  • febrero 2015
  • octubre 2014
  • junio 2014
  • marzo 2014
  • febrero 2014
  • enero 2014
  • diciembre 2013
  • septiembre 2013
  • agosto 2013
  • julio 2013
  • junio 2013
  • abril 2013
  • febrero 2013
  • enero 2013
  • diciembre 2012
  • noviembre 2012
  • septiembre 2012
  • agosto 2012
  • junio 2012
  • mayo 2012
  • abril 2012
  • marzo 2012
  • febrero 2012
  • enero 2012
  • diciembre 2011
  • noviembre 2011
  • octubre 2011
  • septiembre 2011
  • agosto 2011
  • julio 2011
  • junio 2011
  • mayo 2011
  • abril 2011
  • marzo 2011
  • enero 2011
  • diciembre 2010
  • noviembre 2010
  • octubre 2010
  • septiembre 2010
  • agosto 2010
  • julio 2010
  • junio 2010
  • mayo 2010
  • abril 2010
  • marzo 2010
  • febrero 2010
  • enero 2010
  • diciembre 2009
  • noviembre 2009
  • octubre 2009
  • septiembre 2009
  • agosto 2009
  • julio 2009
  • junio 2009
  • mayo 2009
  • abril 2009
  • marzo 2009
  • febrero 2009
  • enero 2009
  • diciembre 2008
  • noviembre 2008
  • octubre 2008
  • septiembre 2008
  • agosto 2008
  • julio 2008
  • junio 2008
  • mayo 2008
  • abril 2008
  • marzo 2008
  • febrero 2008
  • enero 2008
  • diciembre 2007
  • noviembre 2007
  • octubre 2007
  • septiembre 2007
  • agosto 2007
  • julio 2007
  • junio 2007
  • mayo 2007
  • abril 2007
  • marzo 2007
  • febrero 2007
  • enero 2007
  • diciembre 2006
  • noviembre 2006
  • octubre 2006
  • septiembre 2006
  • agosto 2006
  • julio 2006
  • junio 2006
  • mayo 2006
About This Site

A cras tincidunt, ut tellus et. Gravida scel ipsum sed iaculis, nunc non nam. Placerat sed phase llus, purus purus elit.

Archives Widget
  • January 2010
  • December 2009
  • November 2009
  • October 2009
Categories
  • Entertainment
  • Technology
  • Sports & Recreation
  • Jobs & Lifestyle
Search
  • twitter

Powered by WordPress  |  Business Directory by InkThemes.

This site uses cookies: Find out more.