JavaScript – ¿Qué es exactamente this?

Si vienes de un lenguaje orientado a objetos “clásico” como C# o Java, tendrás claro el concepto de this: Se refiere al propio objeto. Dado que todos los objetos son instancias de una clase en concreto y el código se define a nivel de clase el valor de this está claramente definido. Sólo leyendo el código puedes saber a que se refiere this en cada momento.

JavaScript también tiene la palabra this pero su significado es un poco más “complejo” que el de C#.

Antes que nada mencionar que this en JavaScript NO es opcional. Siempre que quieras acceder a una propiedad de this, debes usarla. Si no usas this, JavaScript asume siempre que estás accediendo a una variable local (o global).

El valor de this en JavaScript es parecido al de C#, pero a la vez es tan distinto que lo normal es que alguien que venga de C# termine sin entender nada.

Empecemos diciendo que el valor de this dentro de una función es el valor del objeto en el que se define esta función. Veamos un ejemplo:

  1. var persona = {
  2.     name: 'edu',
  3.     twitter: 'eiximenis',
  4.     twitterUrl: 'http://twitter.com/' + this.twitter
  5. };
  6.  
  7. console.log(persona.twitterUrl);

Este código lo que imprime por pantalla es… http://twitter.com/undefined

Pero… a ver: No valía this el valor del objeto sobre el que estaba definido el código? No está twitterUrl definido dentro del objeto persona? Qué narices ocurre?

Sencillo: Si relees mi definición de this, verás que empezaba diciendo “el valor de this dentro de una función…”. La propiedad twitterUrl no es una función, así que eso no se aplica. En este caso el valor de this es el contexto en el cual se esté ejecutando este código, que por defecto es el contexto global (window en los navegadores).

Para solucionarlo, debemos convertir twitterUrl a una función:

  1. var persona = {
  2.     name: 'edu',
  3.     twitter: 'eiximenis',
  4.     twitterUrl: function () {
  5.         return 'http://twitter.com/' + this.twitter;
  6.     }
  7. };
  8.  
  9. console.log(persona.twitterUrl());

Con este cambio, ahora si que el código imprime la respuesta esperada.

Funciones constructoras

Si leíste mi post sobre si JavaScript era orientado a objetos, ya sabrás que llamo función constructora a aquella función que se usa para crear objetos que compartan un mismo prototipo. Las funciones constructoras se invocan con new y es precisamente el uso de new lo que convierte a una función en constructora, no la función en sí.

El siguiente código también muestra el valor esperado:

Code Snippet
  1. var Persona = function (n, tw) {
  2.     this.name= n;
  3.     this.twitter = tw;
  4.     this.twitterUrl =  function () {
  5.         return 'http://twitter.com/' + this.twitter;
  6.     }
  7. };
  8.  
  9. var persona = new Persona('edu','eiximenis');
  10. console.log(persona.twitterUrl());

Insisto: Es el uso de new el que hace que this sea el objeto que se está creando. ¿Que pasaría si me olvidase el new al llamar a la función Persona?

  1. var foo = Persona('edu', 'eiximenis');
  2. console.log(foo.name);
  3. console.log(name);

Ahora llamo a la función Persona pero sin usar new. Ahora el valor de this dentro de Persona ya NO es el objeto que se está creando. Ahora el valor de this dentro de persona es el valor del objeto dentro del cual la función Persona está definido… Como Persona es una función global, pues ahora this es el contexto global.

De hecho el primer console.log dará error porque foo ahora vale undefined (¡la función Persona no devuelve nada!) y el segundo console.log mostrará “edu” porque la llamada a Persona ha creado la variable “name” en el contexto global.

Recuerda: No hay clases en JavaScript. Si quieres crear objetos a través de una función constructora asegúrate de usar siempre new. Si no obtendrás resultados imprevistos. Es el uso de new el que convierte una función en constructora.

Call / Apply

Vamos a ponernos un poco más serios. Este ejemplo anterior muestra una característica fundamental de this: Su valor depende de como se llame a una función. Hemos visto que dentro de Persona el valor de this dependía de si llamábamos a la función con new o sin new.

Pero oye… ya que el valor de this dentro de una función depende de como esta sea llamada, porque no explotar al máximo esta capacidad y permitir que el valor de this sea el que el desarrollador quiera? Hasta ahora hemos visto que el valor de this podía ser:

  1. El objeto dentro del cual estaba definida la función
  2. Un objeto nuevo que se crea en aquel momento (al usar new).

Pues bien call y apply añaden un tercer escenario: el valor de this puede ser cualquier objeto que el desarrollador desee… Veamos un ejemplo:

  1. var Persona = function (n, tw) {
  2.     this.name= n;
  3.     this.twitter = tw;
  4.     this.twitterUrl =  function () {
  5.         return 'http://twitter.com/' + this.twitter;
  6.     }
  7. };
  8.  
  9. var p = {
  10.     blog:'http://geeks.ms/blogs/etomas'
  11. };
  12.  
  13. Persona.call(p, 'edu','eiximenis');
  14. console.log(p.twitterUrl());
  15. console.log(p.blog);

La función Persona es exactamente la misma que teníamos antes. Luego definimos un objeto p con una propiedad blog. Y en la línea 13 tenemos la clave: Usamos call para llamar a la función Persona. La función call permite especificar el valor de this dentro de una función. El primer parámetro de call es el valor que tomará this, mientras que el resto de parámetros son los que se pasan a la función.

Por lo tanto, como el primer parámetro de Persona.call es el objeto p, el valor de this dentro de Persona será el objeto p… por lo tanto las propiedades name, twitter y twitterUrl serán añadidas al objeto p.

Apply hace exactamente lo mismo que call, salvo que los parámetros que se mandan a la función se especifican en un array:

  1. Persona.apply(p, ['edu','eiximenis']);

¿P: ej. no te has preguntado nunca porque si te suscribes usando jQuery a un evento, puedes usar “this” dentro de la función gestora del evento para acceder al elemento del DOM que ha lanzado el evento? Pues ahí tienes la respuesta: Los que crearon jQuery conocen call y apply 😉

El problema de la pérdida de this

Este es un problema bastante molesto y difícil de detectar si no lo conoces y se entiende mejor con un ejemplo:

  1. var Persona = function (n, tw) {
  2.     this.name= n;
  3.     this.twitter = tw;
  4.     this.twitterUrl =  function () {
  5.         return (function () {
  6.             return 'http://twitter.com/' + this.twitter;
  7.         }());
  8.     }
  9. };
  10.  
  11. var p = new Persona('edu', 'eiximenis');
  12. console.log(p.twitterUrl());

El ejemplo es un pelín forzado pero ilustra la problemática de la pérdida de this.

Primero, que sepas que este código imprime http://twitter.com/undefined en lugar de http://twitter.com/eiximenis como sería de esperar. Si entiendes porque entenderás la problemática de pérdida de this.

La propiedad twitterUrl sigue siendo una función, así que… ¿porque ahora this.twitter no tiene valor? Pues porque ahora estamos accediendo a this dentro de una función que está dentro de una función. Fíjate que ahora twitterUrl es una función que define internamente a otra función anónima, la invoca y devuelve su resultado. La función interna NO está definida dentro del mismo objeto en el cual se define la propiedad twitterUrl y por lo tanto el valor de this dentro de la función interna NO es el propio objeto…  De hecho en este caso this es el contexto global. Y claro el contexto global no tiene definida ninguna propiedad twitter.

Como digo el ejemplo está muy forzado porque nadie haría eso… pero tener una función dentro de otra función es muy habitual en el caso de callbacks, así que te terminarás encontrando con este caso. Y cuando te encuentres con él… te acordarás de mi! 😛

Hay dos soluciones para esto:

  1. Hacer que el valor de this dentro de la función interna sea el correcto (a través de call/apply). Algunas librerías de javascript se encargan de ello con las funciones de callback que gestionan ellos.
  2. Guardar el valor de this en una variable.

Veamos como quedaría el código para el primer caso:

  1. var Persona = function (n, tw) {
  2.     this.name= n;
  3.     this.twitter = tw;
  4.     this.twitterUrl =  function () {
  5.         return (function () {
  6.             return 'http://twitter.com/' + this.twitter;
  7.         }.apply(this));
  8.     }
  9. };
  10.  
  11. var p = new Persona('edu', 'eiximenis');
  12. console.log(p.twitterUrl());

Llamadme friki, pero a mi esto me encanta… Como puedes ver la única diferencia que hay respecto al código anterior es que ahora invocamos a la función anónima interna usando apply. Da igual que la función sea anónima y la defina justo en este lugar: puedo usar apply igualmente… Una maravilla. Si vienes de C# igual te cuesta entender esto, pero ten presente que en JavaScript las funciones son ciudadanas de primer orden. No necesitamos artificios raros como los delegates para poder pasar funciones como parámetro. Y las funciones tienen un prototipo común (de hecho apply está definido en el prototipo de Function).

El código para la segunda opción (guardar el valor de this) sería el siguiente:

  1. var Persona = function (n, tw) {
  2.     this.name= n;
  3.     this.twitter = tw;
  4.     this.twitterUrl = function () {
  5.         var self = this;
  6.         return (function () {
  7.             return 'http://twitter.com/' + self.twitter;
  8.         }());
  9.     }
  10. };
  11.  
  12. var p = new Persona('edu', 'eiximenis');
  13. console.log(p.twitterUrl());

El código sigue siendo muy parecido con la salvedad de que ahora en la línea 5 guardamos el valor de this en una variable llamada self. En la línea 5 el valor de this es el objeto que queremos (pues estamos dentro de la funcion twitterUrl y no dentro de la función interna). Y dentro de la función interna (línea 7) no usamos this si no la variable anterior (self). Es una convención usar los nombres that o self para esta variable que guarda el valor de this.

Bind

Bueno… vamos a ponernos ya en plan superlativo. Si creías que ya lo habíamos visto todo sobre JavaScript y this, todavía nos queda una sorpresa más: La función bind.

Bind es muy similar a call o apply, pero en lugar de devolver el resultado de la función que se llama, devuelve… otra función. ¿Y qué es esta otra función? Pues es la función original pero con el valor this por defecto establecido siempre al valor del parámetro pasado a bind.

Veamos un ejemplo:

  1. var Persona = function (n, tw) {
  2.     this.name= n;
  3.     this.twitter = tw;
  4. };
  5.  
  6. function getTwitterUri() {
  7.     return 'http://twitter.com/' + this.twitter;
  8. }
  9.  
  10. var p = new Persona('edu', 'eiximenis');
  11. console.log(getTwitterUri.apply(p));
  12.  
  13. var ftw = getTwitterUri.bind(p);
  14. console.log(ftw());

¿Cual es la salida de este código? ¡Exacto! Se imprime dos veces ‘http://twitter.com/eiximenis’:

  1. La primera vez es porque usamos apply para invocar a la función getTwitterUri con el objeto p, por lo que el valor de this dentro de getTwitterUri es el objeto p.
  2. La segunda es porque invocamos la funcion ftw. La función ftw es devuelta por la llamada a getTwitterUri.bind por lo que al invocar la función ftw el valor de this es el propio objeto p.

Ahora añadamos las siguientes dos líneas más de código:

  1. console.log(getTwitterUri.apply({ twitter: 'msdev_es' }));
  2. console.log(ftw.apply({ twitter: 'msdev_es' }));

¿Qué crees que imprimen esas dos líneas de código adicionales?

Si has respondido que ambas van a imprimir ‘http://twitter.com/msdev_es’ déjame darte una buena noticia y una mala:

  • La buena es que has entendido como funcionan call y apply! 🙂
  • La mala es que la respuesta no es correcta 😛

La primera línea SI que imprime ‘http://twitter.com/msdev_es’, ya que llamamos a getTwitterUri usando apply y pasándole un objeto cuya propiedad twitter es ‘msdev_es’…

… Pero la segunda línea NO imprime ‘http://twitter.com/msdev_es’. Debería hacerlo si ftw fuese una funcion “normal”. Pero ftw es una función obtenida a través de bind, por lo que sigue conservando su valor de this con independencia de call y apply.

Así que ya ves… Para saber el valor de this que usa una función ya ni te basta con mirar si es invocada usando call y apply: debes además saber si esta función ha sido generada a través de bind o no 😛

En fin… Creo que más o menos con esto cubrimos todos los aspectos de this en JavaScript. Como puedes ver, aunque su significado es parecido al de C# realmente es tan distinto que lo más normal es que si asumes que el this de JavaScript funciona como el de C# termines por no entender nada y maldecir a JavaScript… 😉

Un saludo! 😉

¿Es Javascript orientado a objetos?

Ayer tuve el placer de participar en un hangout de #JsIO junto a dos bestias pardas como Erick Ruiz y Tomás Corral discutiendo si JavaScript es o no es un lenguaje orientado a objetos.

Así que aprovecho para escribir este post y hacer algunas reflexiones más al respecto sin las prisas ni la improvisación del “directo”.

¿Qué es la orientación a objetos?

Es complicado definir que significa exactamente “orientación a objetos”. Seguro que si le preguntas a varia gente distinta obtendrás diferentes respuestas. Con puntos coincidentes, pero también puntos divergentes. Esto se debe a que el concepto es lo suficientemente vago como para admitir varias interpretaciones y además de que la gran mayoría de lenguajes no son 100% orientados a objetos.

Si yo hubiese de definir orientación a objetos diría básicamente que se trata de modelar el problema, y por lo tanto de diseñar un programa, basándose en la interacción entre distintos objetos que tienen determinadas propiedades y con los cuales se pueden realizar determinadas acciones. Los objetos tienen tres propiedades fundamentales:

  1. Comportamiento: Se define como “lo que es posible hacer con un objeto”. En terminología más purista, el conjunto de mensajes a los que este objeto responde (en OOP purista se usa la terminología mensaje para referirse al paso de información entre objetos).
  2. Estado: Los objetos tienen un estado en todo momento, definido por el conjunto de las variables o campos que contienen. El estado es pues la información contenida en un objeto.
  3. Identidad: Cada objeto existe independientemente del resto. Pueden haber dos objetos iguales pero no tienen porque ser el mismo (de igual forma que dos mellizos pueden ser idénticos, pero no por ello dejan de ser dos personas).

No iría mucho más allá, porque para mi esta definición contiene la clave que diferencia de la POO de la programación procedural: Los datos (estado) y funciones relacionadas (comportamiento) están agrupados en una sola entidad (objeto), en lugar de estar dispersos en el código. Es decir, el objeto encapsula los datos y el comportamiento. Como desarrollador debemos pensar en modelar nuestro sistema como un conjunto de objetos, en lugar de como un conjunto de funciones (procedimientos) invocadas una tras otra y pasándose datos más o menos arbitrarios para solucionar el problema.

El resto son detalles: herencia, polimorfismo, visibilidad… son detalles. No son menores, ciertamente, pero desde un punto de visto teórico, son meros detalles.

Y fíjate: he sido capaz de definir POO sin usar la palabra clase una sola vez. Y es que las clases son un mecanismo para implementar la POO, pero nada más. Y por supuesto, no son el único.

JavaScript tiene objetos, y estos objetos tienen comportamiento, estado e identidad. Así que desde punto de vista… Es orientado a objetos.

Pero como entiendo que esta respuesta puede no colmar a todos, especialmente a aquellos que vengan de Java, C# o algún otro lenguaje orientado a objetos “tradicional” (debería decir basado en clases) vayamos a ver como en JavaScript también tenemos esos detalles que mencionaba antes disponibles… 😉

¿Herencia en JavaScript?

Vale… Antes que nada: Si has aprendido POO con Java, C#, C++ o algún otro lenguaje basado en clases recuerda el concepto fundamental: No hay clases en JavaScript. Por lo tanto, hazte un favor y deja de pensar en clases cuando desarrolles en JavaScript. Te ahorrarás muchos dolores de cabeza.

La herencia en JavaScript es herencia entre objetos.

  • En un lenguaje basado en clases, la herencia es una relación definida entre clases. Si una clase Rectángulo deriva de la clase Figura, todos los objetos Rectángulo heredarán todo el comportamiento y estado definidos en la clase Figura.
  • En JavaScript la herencia es una relación definida entre objetos. Si un objeto deriva de otro, heredará todo el comportamiento y estado definido en el objeto base.

El mecanismo de herencia que se usa en JavaScript se conoce como herencia por prototipo y para resumirlo diré básicamente que cada objeto tiene asociado un prototipo. Si el desarrollador invoca un método sobre un objeto y este método NO está definido en el propio objeto, el motor de JavaScript buscará el método en el prototipo de este objeto. Por supuesto el prototipo es un objeto y puede tener otro prototipo y así sucesivamente, creando lo que se conoce como cadena de prototipado. La pregunta es pues como crear y asignar el prototipo de un objeto. Hay varias maneras de hacerlo, pero veamos una rápida y sencilla:

Herencia por prototipo (1)
  1. var figura = {
  2.     draw: function() {
  3.         console.log("figura::draw");
  4.     }
  5. };
  6. var rect = Object.create(figura);
  7. rect.draw();

Si ejecutas este código, la salida por la consola es “figura::draw”. La clave es el método Object.create. Este método crea un objeto vacío cuyo prototipo es el objeto pasado por parámetro. Esta es una forma sencilla de tener herencia en JavaScript.

Igual te preguntas si el objeto rect puede sobreescribir la implementación de draw (si vienes de Java sabrás que una clase hija puede redefinir los métodos de su clase base; si vienes de C# sabrás eso mismo siempre que los métodos sean virtuales). La respuesta es claro que sí:

Redefinicion de metodos
  1. var figura = {
  2.     draw: function() {
  3.         console.log("figura::draw");
  4.     }
  5. };
  6. var rect = Object.create(figura);
  7. rect.draw = function() {
  8.     console.log("rect::draw");
  9. };
  10. rect.draw();

Ahora la salida por pantalla es “rect::draw”. ¿Qué ha sucedido? Pues como hemos llamado al método draw del objeto rect y este ya lo tiene definido, JavaScript ya lo invoca y no lo busca por la cadena de prototipos.

¿Lo ves? Herencia sin clases. ¡Es posible! 😉

¿Polimorfismo en JavaScript?

Una de las preguntas que salieron en el hangout fue si había polimorfismo en JavaScript. Respondí diciendo que esta pregunta implica una forma de pensar “basada en clases” pero que sí que era posible simular el polimorfismo en JavaScript.

Pero es que una de las claves es que no es necesario el concepto de polimorfismo en JavaScript. Antes que nada aclaremos que es el polimorfismo: La posibilidad de enviar un mismo mensaje (es decir, de invocar a un método) a varios objetos de naturaleza homogénea (wikipedia). Me gusta especialmente esta definición porque define polimorfismo sin usar la palabra “clase”. Si habéis aprendido POO con un lenguaje basado en clases quizá tenéis una definición de polimorfismo redactada de forma ligeramente distinta, algo como: Que los objetos de una clase derivada pueden tratarse como objetos de la clase base, pero manteniendo su comportamiento distintivo.

Supón que estás en un lenguaje basado en clases, como C#, y tienes la clase Figura y su derivada la clase Rect. La clase Figura define un método virtual llamado Draw() que es redefinido en la clase Rect. El polimorfismo implica que puedo pasar un objeto de la clase Rect a un método que acepte como parámetro un objeto de la clase Figura. Y que si este método llama al método Draw del parámetro Figura que ha recibido… se ejecutará el método Draw de la clase Rect, porque aunque el parámetro es de tipo Figura, el objeto real es de tipo Rect.

¿Y en JavaScript? Veamos:

Polimorfismo
  1. var figura = {
  2.     draw: function() {
  3.         console.log("figura::draw");
  4.     }
  5. };
  6. var rect = Object.create(figura);
  7. rect.draw = function() {
  8.     console.log("rect::draw");
  9. };
  10.  
  11. function Polimorfismo(figura) {
  12.     figura.draw();
  13. }
  14.  
  15. Polimorfismo(rect);

La función Polimorfismo llama al método draw() del objeto que recibe. Como lo paso el objeto rect lo que se ejecuta es el método draw del objeto rect. Por lo tanto… tenemos polimorfismo automático en JavaScript. De hecho JavaScript tiene una característica conocida como Duck Typing, que bueno… es dificil de definir, pero una manera de hacerlo es: “Si camina como un pato, grazna como un pato y luce como un pato… es un pato”. O dicho de otro modo: Para el método Polimorfismo lo único que importa del parámetro figura es que tenga un método draw(). Nada más. Por lo tanto cualquier objeto que tenga un método draw es válido para ser usado como parámetro del método Polimorfismo.

El Duck Typing se suele asociar a los lenguajes dinámicos… pero no es exclusivo de ellos. Java y C# (sin usar dynamic) no tienen Duck Typing, pero p. ej. C++ lo ofrece a través de los templates.

¿Clases en JavaScript?

A esta altura del post ya debes tener claro que no. JavaScript no tiene clases, solo objetos y relaciones entre objetos. Pero existe un método de crear objetos a través de una función constructora y la palabra clave new que se parece mucho a como se hace en un lenguaje basado en clases. Eso que a priori es buena idea, ha ayudado mucho a la confusión de JavaScript… ¿Uso new para crear objetos pero no hay clases? ¿Y eso?

Déjame mostrarte un trozo de código de como crear un objeto basado en función constructora y el uso de new:

Funciones constructoras y new
  1. var Figura = function() {
  2.     this.draw = function() {
  3.         console.log("Figura::draw");
  4.     };
  5. };
  6.  
  7. var figura = new Figura();
  8. var rect = Object.create(figura);
  9.  
  10. rect.draw();

Ahora la variable Figura no es nada más que una función. Pero un tipo especial de función que llamamos función constructora. Pero ¡ojo! lo que convierte Figura en una función constructora no es nada que defina la propia función. Es como se invoca. Es el hecho de usar new lo que convierte Figura en una función constructora. El uso de new implica varias cosillas, cuyo ámbito se escapa de este post, pero la norma básica es que es la forma para invocar funciones cuya funcionalidad es crear objetos. En este ejemplo pues tengo:

  • Figura: Función que sirve para construir objetos que tienen un método draw.
  • figura (con la f minúscula): Objeto creado a partir de Figura
  • rect: Objeto cuyo prototipo es el objeto figura.

Lo interesante de usar funciones constructoras es que todos los objetos creados a través de ellas comparten el mismo prototipo. Si tenemos:

  1. var figura = new Figura();
  2. var figura2 = new Figura();

Ambos objetos comparten el mismo prototipo. Si ahora quiero añadir un método, p.ej. clear() que esté disponible para todos los objetos creados a partir de la función constructora Figura puedo añadirlo al prototipo. Y como hago esto? Pues así:

  1. var Figura = function() {
  2.     this.draw = function() {
  3.         console.log("Figura::draw");
  4.     };
  5. };
  6.  
  7. var figura = new Figura();
  8. var figura2 = new Figura();
  9.  
  10. Figura.prototype.clear = function() {
  11.     console.log("Figura::clear");
  12. };
  13.  
  14. var rect = Object.create(figura);
  15.  
  16. figura.clear();
  17. figura2.clear();
  18. rect.clear();

Figura.prototype es el nombre del prototipo de todos los objetos creados a través de new Figura. Este codigo imprime tres veces “Figura::clear”. Fíjate que funciona incluso cuando el método clear ha sido añadido al prototipo después de crear figura y figura2. Y es interesante el caso del objeto rect. Que ocurre cuando hacemos rect.clear()?

  • JavaScript busca el método clear() en el objeto rect. No lo encuentra y
  • Busca el método en el prototipo de rect que es figura. No lo encuentra y
  • Busca el método en el prototipo de figura que es Figura.prototype. Lo encuentra y lo ejecuta.

Aquí tienes a la cadena de prototipado en acción.

Fíjate que seguimos teniendo tan solo objetos. Figura no es una clase. Figura es una función. Una función para crear objetos que comparten un prototipo. Nada más.

¿Variables privadas en JavaScript?

JavaScript no incorpora de serie ningún mecanismo de visibilidad para los miembros de un objeto. Todo es público por defecto. Pero por supuesto no hay nada que no podamos conseguir… 🙂

Visibilidades en JavaScript
  1. var Figura = function (c, stroke) {
  2.     // Variable privada
  3.     var _color = c;
  4.     // Variable pblica
  5.     this.stroke = null;
  6.     // Necesario para poder acceder
  7.     // a this desde los mtodos privados
  8.     var self = this;
  9.     // Mtodo privado
  10.     var _setup = function(s) {
  11.         // Ah podemos acceder a mtodos pblicos
  12.         // a travs de self. Y a los privados directamente
  13.         self.stroke = s;
  14.     };
  15.     _setup(stroke);
  16.     // Mtodos pblicos
  17.     this.draw = function() {
  18.         console.log("Figura::draw in color " + _color  + " and stroke " + this.stroke );
  19.     };
  20.     this.getColor = function() {
  21.         return _color;
  22.     };
  23. };
  24.  
  25.  
  26. var f = new Figura("red", "thin");
  27. console.log(f._color); // undefined
  28. console.log(f.getColor()); // red
  29. console.log(f.stroke); // thin
  30. //f._setup("thick");  // Error: Object has no method _setup
  31.  
  32. var f2 = new Figura("blue","thick");
  33. console.log(f2._color); // undefined
  34. console.log(f2.getColor()); // blue
  35. console.log(f2.stroke); // thick
  36. //f._setup("thick");  // Error: Object has no method _setup
  37. f.draw();
  38. f2.draw();

Este ejemplo muestra como definir métodos y variables privadas. ¡Hay otras técnicas y alternativas!

Básicamente la regla es:

  • Usar la técnica de función constructora
  • Los métodos/variables privados son funciones o variables declaradas dentro de la función constructora.
  • Los métodos/variables públicas se asignan a this.
  • Guardar el valor de this en una variable privada (usualmente se usa that o self).
  • Desde las funciones privadas debes usar self para acceder a las variables públicas.
  • Desde las funciones públicas puedes usar self o this para acceder a las variables públicas.
  • En ambos casos puedes acceder a las variables privadas directamente con su nombre.

¿Herencia múltiple en JavaScript?

Si vienes de C# o Java igual alzas una ceja ahora… ¿No era peligrosa la herencia múltiple? C# y Java no incorporan este concepto debido a su posible peligrosidad y porque muchas veces da más problemas de los que puede solucionar. El peligro de la herencia múltiple es conocido como el problema de la herencia en diamante. Todos los lenguajes que soportan herencia múltiple se deben enfrentar a este problema, así que para evitarlo algunos lenguajes como Java o C# han decidido prescindir de ella.

JavaScript no soporta por defecto herencia múltiple pero si que soporta un tipo especial de herencia múltiple llamada mixin. Con los mixins ya entramos en un terreno pantanoso si vienes de C# o Java puesto que estos dos lenguajes no soportan este concepto.

Resumiendo, un Mixin es una clase que está pensada para ser incorporada dentro de otra clase ofreciendo funcionalidad adicional. Es como si tuvieras 3 clases A, B y C y las “combinases” todas ellas en una clase D (que además añadiría su propia funcionalidad). Algunos lenguajes como Lisp o Python soportan Mixins nativamente. Otros como C++ no, pero pueden imitarlos fácilmente debido al soporte de herencia múltiple (del que el uso de mixins es un tipo específico). Usar mixins en Java o C# es realmente complicado (aunque en Java8 el uso de default methods en interfaces lo hace posible). Si estás interesado en Mixins y C# echa un vistazo a heredar de José F. Romaniello.

¿Y como usar Mixins en C#? Aquí tienes un ejemplo:

Mixins
  1. var asCircle = function() {
  2.     this.area = function() {
  3.         return Math.PI * this.radius * this.radius;
  4.     };
  5.  
  6.     return this;
  7. };
  8.  
  9. var asButton = function() {
  10.     this.click = function() {
  11.         console.log("Button has been clicked");
  12.     };
  13.     return this;
  14. };
  15.  
  16. var a = { radius: 10 };
  17. asCircle.call(a);
  18. asButton.call(a);
  19. console.log(a.area());
  20. console.log(a.click());

En este cas asCircle y asButton son los dos Mixins. El primero añade una funcionalidad area a todos los objetos que tengan una propiedad llamada radius. El segundo añade un método click.

Para aplicar los Mixins sobre un objeto usamos la función call. No entraré en detalles de call ahora porque excede el objetivo de este post. Pero la clave está en que después de aplicar los Mixins, el objeto a tiene los métodos area y click.

Y más o menos… ¿con esto podemos dar por terminado el post no? Espero, que esto os haya ayudado a entender un poco mejor la POO bajo JavaScript y que bueno… os hayáis convencido de que JavaScript no tendrá clases pero orientado a objetos es 🙂

Saludos!

PD: Os paso el enlace del video de youtube donde podeis ver el hangout: http://www.desarrolloweb.com/en-directo/particularidades-programacion-orientada-objetos-poo-devio-8452.html

Divertimento: Cadenas de longitud máxima fija en C#

Aviso: Este post es un divertimento que ha surgido a raíz del siguiente tweet de Juan Quijano. En este tweet básicamente Juan preguntaba si había alguna manera de limitar la longitud de una cadena. Por supuesto todas las respuestas que le dan son correctísimas, a saber:

  1. Validarlo en el setter
  2. Usar DataAnnotations y validación con atributos
  3. Usar [StringLength] en el caso de ASP.NET MVC
  4. Y otras que se podrían dar aquí.

Pero me he preguntado cuan sencillo sería crear en C# una clase cadena de longitud fija pero que se comportase como una cadena. Es decir que desde el punto de vista del usuario no haya diferencia entre objetos de esta clase y las cadenas estándar.

En este post os cuento a la solución a la que he llegado, que no tiene porque ser la única ni la mejor, y los “problemas” que me he encontrado. Además la solución me da una excusa para contar una capacidad de C# que mucha gente no conoce ni usa que son las conversiones implícitas personalizadas 🙂

Conversiones implícitas personalizadas

Las conversiones implícitas personalizadas son uno de los dos puntos clave de la solución a la que he llegado (el otro son los genéricos).

Una conversión implícita personalizada es la capacidad de los valores de un tipo para convertirse automáticamente en valores de otro tipo cuando es necesario. P. ej. hay una conversión implícita de int a float:

  1. float f = 10;

En esta línea la constante 10 es de tipo int, pero en cambio f es de tipo float. La asignación funciona no porque 10 sea un float si no porque hay una conversión implícita entre int (10) y float.

En cambio no hay una conversión implícita de double a float y es por ello que esa línea no compila:

  1. float f = 100.0;

Esta línea no compila porque las constantes decimales son de tipo double. Y aunque 100.0 es un valor válido para un float (pues entra dentro de su rango y capacidad), para el compilador es un double y no puede transformar un double en un float porque no hay una transformación implícita.

Por supuesto podemos asignar 100.0 a un float, usando este código:

  1. float f = (float)100.0;

Ahora estamos usando una conversión explícita. Y dado que hay definida una conversión explícita entre double (100.0) y float, el código compila. Que la conversión sea explícita tiene su lógica: la conversión de double a float puede generar una pérdida de rango y/o precisión. Por ello no hay una conversión implícita (que sucedería automáticamente y podría generar errores). Por ello la conversión es explícita, obligando al desarrollador a indicar (mediante el casting) que quiere realizarla y que es consciente de los peligros que pueda haber.

El operador de casting en C# pues invoca a una conversión explícita, que debe estar definida. P. ej. el siguiente código no compila, por más que usemos el casting:

  1. float f = (float) "100.0";

Y no compila porque NO hay definida ninguna conversión explícita de string (“100.0”) a float.

Pues bien, en C# una clase puede definir conversiones explícitas e implícitas desde y a otros tipos.

Yo empecé mi solución con una clase muy simple: Una clase que contuviera nada más que una cadena pero que se convirtiese implícitamente desde y a string:

Code Snippet
  1. sealed class FixedLengthString
  2. {
  3.     private string _buffer;
  4.  
  5.     public FixedLengthString()
  6.     {
  7.         _buffer = string.Empty;
  8.     }
  9.  
  10.     public FixedLengthString(string initialValue)
  11.     {
  12.         _buffer = initialValue;
  13.     }
  14.  
  15.  
  16.     public override string ToString()
  17.     {
  18.         return _buffer;
  19.     }
  20.  
  21.     public static implicit operator string(FixedLengthString value)
  22.     {
  23.         return value._buffer;
  24.     }
  25.  
  26.     public static implicit operator FixedLengthString(string value)
  27.     {
  28.         return new FixedLengthString(value);
  29.     }
  30.  
  31.     public override bool Equals(object obj)
  32.     {
  33.         if (obj == null)
  34.         {
  35.             return false;
  36.         }
  37.  
  38.         if (obj is string)
  39.         {
  40.             return obj.Equals(_buffer);
  41.         }
  42.  
  43.         return (obj is FixedLengthString) ?
  44.             ((FixedLengthString)obj)._buffer == _buffer :
  45.             base.Equals(obj);
  46.     }
  47.  
  48.     public override int GetHashCode()
  49.     {
  50.         return (_buffer ?? string.Empty).GetHashCode();
  51.     }
  52. }

Fíjate que esta clase no es nada más que un contenedor para una cadena (_buffer), pero la clave está en los dos métodos estáticos:

Conversiones Implicitas
  1. public static implicit operator string(FixedLengthString value)
  2. {
  3.     return value._buffer;
  4. }
  5.  
  6. public static implicit operator FixedLengthString(string value)
  7. {
  8.     return new FixedLengthString(value);
  9. }

El primero de los dos define la conversion de FixedLengthString a cadena y el segundo la conversión desde cadena a FixedLengthString.

Fíjate la sintaxis:

  • El método es static
  • Se usa implicit operator para indicar que es una conversión implícita (usaríamos explicit operator para indicar una de explícita).
  • Como valor de retorno colocamos el del tipo al que nos convertimos
  • Recibimos un parámetro del tipo desde el que nos convertimos.

Gracias a estas conversiones implícitas, el siguiente código es válido:

  1. FixedLengthString str = "YYY";
  2. Console.WriteLine(str);

En la primera línea estamos invocando la conversión implícita de cadena (“YYY”) a FixedLengthString y en la segunda la conversión contraria (Console.WriteLine espera un parámetro de tipo string y str es un FixedLengthString).

Bien, ahora tenía una clase que envolvía una cadena y que para el desarrollador se comportaba como una cadena. Sólo había que añadir la longitud máxima y listos.

Pero no era tan fácil.

La primera solución que se nos puede ocurrir pasa por declarar un campo con la longitud máxima de la cadena y en el constructor de FixedLengthString pasar que longitud máxima queremos. Crear la clase FixedLengthString para que contenga cadenas de como máximo una longitud determinada es fácil y no tiene ningún secreto. El problema está en mantener las conversiones implícitas, especialmente la conversión implícita desde una cadena hacia una FixedLengthString.

Supongamos que definimos la clase FixedLengthString para que acepte un parámetro en el constructor que defina la longitud máxima. Entonces podríamos declarar una variable para contener el DNI así:

  1. var dni = new FixedLengthString(9);

Ahora si usáramos métodos definidos en la clase (p. ej. supongamos que la clase FixedLengthString definiese un método SetValue o algo así) podríamos controlar fácilmente que el valor del buffer interno no excediese nunca de 9 carácteres. Pero yo no quería eso: yo quería que la clase se pudiese usar como una cadena estándar se tratase. Es decir poder hacer:

  1. dni = "12345678A";

En esta línea se invoca la conversión implícita desde cadena hacia FixedLengthString… ¿Y cual es el problema? Que es estática. Mira de nuevo el código de dicha conversion:

  1. public static implicit operator FixedLengthString(string value)
  2. {
  3.     return new FixedLengthString(value);
  4. }

Dentro de la conversión no puedo saber cual es el valor de la longitud máxima porque la conversión es estática y el valor de la longitud máxima está en un campo de instancia (el valor de longitud máxima puede ser distinto en cada objeto). Hablando claro: La conversión implícita devuelve un nuevo objeto, y NO puede acceder a las propiedades del objeto anterior si lo hubiese (en mi caso el objeto anterior guardado en dni).

Parece que estamos en un callejon sin salida…

En este punto he empezado un proceso de pensamiento que ha discurrido más o menos así:

  1. El problema es que el campo de longitud máxima es un campo de objeto (no estático) y la conversión es estática.
  2. Entonces si guardo el campo de longitud máxima en una variable estática, podré acceder a dicho valor en la conversión…
  3. … Aunque claro, este enfoque tiene un problema: El valor de longitud máxima es compartido por todos los objetos de la clase. No puedo tener un objeto FixedLengthString de longitud máxima 9 (para un DNI p. ej.) y otro de longitud máxima 5 (p. ej. para un código postal).

Evidentemente el punto 3, parece descartar la idea pero… ¿Y si pudiésemos tener varias clases distintas pero todas con el mismo código, pero tan solo cambiando el valor de longitud máxima? Entonces… ¡todo funcionaría!

Y… qué mecanismo conocéis en C# que permite generar clases distintas con el mismo código? Exacto: Los genéricos.

Si había una solución pasaba por usar genéricos.

Genéricos al rescate

Pero había un pequeño temilla: el parámetro que yo quería generalizar era el valor de longitud máxima, que es un int y esto no está permitido en genéricos. En los templates de C++ (que vienen a ser como los genéricos de .NET pero hipervitaminados) es posible generalizar parámetros de un tipo específico, pero en .NET no. En .NET los parámetros de los genéricos definen siempre un tipo, no un valor. P. ej. en el genérico List<T> el parámetro T es siempre un tipo (si T vale int tienes List<int> y si T vale string tienes List<string>). Y lo mismo ocurre en cualquier genérico que definas en .NET.

En fin… no era perfecto pero ya tenía la idea montada en mi mente. No era perfecta porque me obligaba a crear un tipo específico, distinto, por cada valor de longitud máxima que quisiera, pero bueno, al menos serían tipos muy sencillos. De hecho, serían tipos vacíos, tan solo decorados con un atributo.

Empecé definiendo el atributo que usaría:

FixedLengthMaxAttribute
  1. public class FixedStringLengthMaxAttribute : Attribute
  2. {
  3.     public int Length { get; set; }
  4.  
  5.     public FixedStringLengthMaxAttribute(int len)
  6.     {
  7.         this.Length = len;
  8.     }
  9. }

La idea era la siguiente: Por cada valor de longitud máxima que se quisiera se crea una clase vacía y se decora con este atributo con el valor máximo deseado.

Luego se crea una instancia de la clase FixedLengthString<T> y se pasa como valor del tipo genérico T la clase creada y decorada con el atributo. Para declarar un DNI de 9 carácteres sería:

  1. [FixedStringLengthMaxAttribute(9)]
  2. internal class NifSize
  3. {
  4. }

  1. var nif = new FixedLengthString<NifSize>();

Una vez tenemos el objeto nif podemos convertirlo desde y a cadena sin ningún problema (como hemos visto antes) y se mantiene la longitud máxima de 9 carácteres (en mi implementación se trunca si la cadena desde la que convertimos es más larga).

Ah si… Y falta la implementación de la clase FixedLenghString<T>. Básicamente es la misma que la original FixedLengthString pero con un constructor estático que lee via reflection el atributo [FixedStringLength] aplicado al tipo T y guarda el valor de la propiedad Length de este atributo en un campo estático:

FixedLengthString<T>
  1. sealed class FixedLengthString<T>
  2. {
  3.     private string _buffer;
  4.     private static int _maxlen;
  5.  
  6.     static FixedLengthString()
  7.     {
  8.         var type = typeof (T);
  9.         var attr = type.GetCustomAttribute<FixedStringLengthMaxAttribute>();
  10.         if (attr == null)
  11.         {
  12.             _maxlen = Int32.MaxValue;
  13.         }
  14.         else
  15.         {
  16.             _maxlen = attr.Length;
  17.         }
  18.     }
  19.  
  20.  
  21.     public FixedLengthString()
  22.     {
  23.         _buffer = string.Empty;
  24.     }
  25.  
  26.     public FixedLengthString(string initialValue)
  27.     {
  28.         _buffer = initialValue.Length < _maxlen ? initialValue : initialValue.Substring(0, _maxlen);
  29.     }
  30.  
  31.      
  32.     public override string ToString()
  33.     {
  34.         return _buffer;
  35.     }
  36.  
  37.     public static implicit operator string(FixedLengthString<T> value)
  38.     {
  39.         return value._buffer;
  40.     }
  41.  
  42.     public static implicit operator FixedLengthString<T>(string value)
  43.     {
  44.         return new FixedLengthString<T>(value);
  45.     }
  46.  
  47.     public override bool Equals(object obj)
  48.     {
  49.         if (obj == null)
  50.         {
  51.             return false;
  52.         }
  53.  
  54.         if (obj is string)
  55.         {
  56.             return obj.Equals(_buffer);
  57.         }
  58.  
  59.         return (obj is FixedLengthString<T>) ?
  60.             ((FixedLengthString<T>)obj)._buffer == _buffer :
  61.             base.Equals(obj);
  62.     }
  63.  
  64.     public override int GetHashCode()
  65.     {
  66.  
  67.         return (_buffer ?? string.Empty).GetHashCode();
  68.     }
  69. }

¡Y listos!

Por supuesto puedo crear una clase con una propiedad FixedLengthString<T>:

  1. class Persona
  2. {
  3.     public string Nombre { get; set; }
  4.     public FixedLengthString<NifSize> NIF { get; set; }
  5.     public Persona()
  6.     {
  7.         NIF = new FixedLengthString<NifSize>();
  8.     }
  9. }

Y operar con el NIF de esas personas como si fuesen cadenas normales:

  1. Persona p = new Persona();
  2. Console.WriteLine(p.NIF);
  3. p.NIF = "12345678A";
  4. Console.WriteLine(p.NIF);
  5. p.NIF = "1234567890987654321Z";
  6. Console.WriteLine(p.NIF);

La salida de este programa es:

image

Se puede observar como la cadena se trunca a 9 caracteres.

Bueno… Llegamos al final de este post, espero que os haya resultado interesante. Por supuesto no digo que esta sea la solución para cadenas de tamaño máximo, si no que como he dicho al principio es un simple divertimento 😉

Saludos!

ASP.NET MVC – Custom model binders por propiedad

Muy buenas! En este post vamos a ver como habilitar un custom model binder para una propiedad en concreto de un viewmodel.

De serie es posible configurar un Custom Model Binder de dos maneras:

  1. Añadiéndolo a la colección Binders de la clase ModelBinders. Con esto conseguimos que todos los valores de un tipo concreto se enlacen usando nuestro ModelBinder.
  2. Usando el atributo [ModelBinder]. Con este atributo podemos especificar un Model Binder a usar para un viewmodel en concreto o para un parámetro en concreto de un controlador. Pero desgraciadamente no para una propiedad concreta de un viewmodel:

image

Por suerte conseguir usar un custom model binder para una propiedad en concreto de un viewmodel no es tan complicado 🙂

Lo primero es crearnos un atributo propio ya que el ModelBinderAttribute no podemos usarlo, así que vamos a ello:

Atributo [PropertyModelBinder]
  1. AttributeUsage(AttributeTargets.Property)]
  2.     public class PropertyModelBinderAttribute : CustomModelBinderAttribute
  3.     {
  4.         private readonly Type _typeToUse;
  5.  
  6.         public PropertyModelBinderAttribute(Type typeToUse)
  7.         {
  8.             _typeToUse = typeToUse;
  9.         }
  10.  
  11.         public override IModelBinder GetBinder()
  12.         {
  13.             var binder = DependencyResolver.Current.GetService(_typeToUse) as IModelBinder;
  14.             return binder ?? new DefaultModelBinder();
  15.         }
  16.     }

Una vez lo tenemos creado, lo aplicamos a nuestro viewmodel:

ViewModel de prueba
  1. public class SomeViewModel
  2. {
  3.     [PropertyModelBinder(typeof(RomanNumberModelBinder))]
  4.     [Range(1,21)]
  5.     public int Century { get; set; }
  6.     public string Name { get; set; }
  7. }

Ahora debemos implementar el custom model binder. En este caso he implementado un model binder que convierte cadenas en formato de número romano (tal como XX o VIII) al entero correspondiente.

Nota: El código de conversión lo he sacado de http://vadivel.blogspot.com.es/2011/09/how-to-convert-roman-numerals-to.html. Tan solo lo he corregido para usar un SortedDictionary y así asegurarme de que el orden por el que se itera sobre las claves es el esperado (en un Dictionary<,> el orden de iteración no está definido).

El código entero de mi ModelBinder es el siguiente:

RomanNumberModelBinder
  1. public class RomanNumberModelBinder : IModelBinder
  2. {
  3.     private static List<string> _romanTokens = new List<string>
  4.     {
  5.         "M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I",
  6.     };
  7.  
  8.     private class RomanKeyComparer : IComparer<string>
  9.     {
  10.         public int Compare(string x, string y)
  11.         {
  12.  
  13.             var idx = _romanTokens.IndexOf(x);
  14.             var idx2 = _romanTokens.IndexOf(y);
  15.             if (idx == idx2) return 0;
  16.             return idx < idx2 ? 1 : 1;
  17.  
  18.         }
  19.     }
  20.  
  21.     private static SortedDictionary<string, int> RomanNumbers =
  22.         new SortedDictionary<string, int>(new RomanKeyComparer());
  23.  
  24.     public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
  25.     {
  26.         if (bindingContext.ModelType != typeof (int)) return null;
  27.         var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue;
  28.  
  29.         return RomanToInt(value);
  30.     }
  31.  
  32.     private static int RomanToInt(string roman)
  33.     {
  34.         if (!RomanNumbers.Any())
  35.         {
  36.             RomanNumbers.Add(_romanTokens[0], 1000);
  37.             RomanNumbers.Add(_romanTokens[1], 900);
  38.             RomanNumbers.Add(_romanTokens[2], 500);
  39.             RomanNumbers.Add(_romanTokens[3], 400);
  40.             RomanNumbers.Add(_romanTokens[4], 100);
  41.             RomanNumbers.Add(_romanTokens[5], 90);
  42.             RomanNumbers.Add(_romanTokens[6], 50);
  43.             RomanNumbers.Add(_romanTokens[7], 40);
  44.             RomanNumbers.Add(_romanTokens[8], 10);
  45.             RomanNumbers.Add(_romanTokens[9], 9);
  46.             RomanNumbers.Add(_romanTokens[10], 5);
  47.             RomanNumbers.Add(_romanTokens[11], 4);
  48.             RomanNumbers.Add(_romanTokens[12], 1);
  49.         }
  50.         int result = 0;
  51.  
  52.  
  53.         foreach (var pair in RomanNumbers)
  54.         {
  55.             while (roman.IndexOf(pair.Key.ToString()) == 0)
  56.             {
  57.                 result += int.Parse(pair.Value.ToString());
  58.                 roman = roman.Substring(pair.Key.ToString().Length);
  59.             }
  60.         }
  61.         return result;
  62.     }
  63. }

Teóricamente lo tenemos todo montado. Pero si lo probamos veremos que NO funciona. Para la prueba me he generado una pequeña vista como la siguiente:

Vista de prueba
  1. @model WebApplication1.Models.SomeViewModel
  2.  
  3. @using (Html.BeginForm())
  4. {
  5.     <p>
  6.         @Html.LabelFor(m => m.Name)
  7.         @Html.EditorFor(m => m.Name)
  8.     </p>
  9.     <p>
  10.         @Html.LabelFor(m => m.Century)
  11.         @Html.TextBoxFor(m => m.Century, new {placeholder = "In Romna numbers, like XX or XIX"})
  12.     </p>
  13.     
  14.     <input type="submit" value="send"/>
  15. }

Y luego las acciones correspondientes en el controlador para mostrar la vista y recibir los resultados. Si lo probáis veréis que nuestro RomanNumberModelBinder no se invoca 🙁

El “culpable” de que no funcione es el ModelBinder por defecto (DefaultModelBinder). Dado que el atributo [ModelBinder] origina no puede aplicarse a propiedades, el DefaultModelBinder no tiene en cuenta la posibilidad de que una propiedad en concreto use un model binder distinto. Así pues nos toca reescribir parte del DefaultModelBinder.

Para reescribir parte del ModelBinder lo más sencillo es heredar de él y redefinir el método que necesitemos. En este caso el método necesario es BindProperty que enlaza una propiedad. Veamos como queda el código:

DefaultModelBinderEx
  1. public class DefaultModelBinderEx : DefaultModelBinder
  2. {
  3.     protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext,
  4.         PropertyDescriptor propertyDescriptor)
  5.     {
  6.  
  7.         var cmbattr = propertyDescriptor.Attributes.OfType<CustomModelBinderAttribute>().FirstOrDefault();
  8.         IModelBinder binder;
  9.         if (cmbattr != null && (binder = cmbattr.GetBinder()) != null)
  10.         {
  11.             var subPropertyName = DefaultModelBinder.CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);
  12.             var obj = propertyDescriptor.GetValue(bindingContext.Model);
  13.             var modelMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name];
  14.             modelMetadata.Model = obj;
  15.             var bindingContext1 = new ModelBindingContext()
  16.             {
  17.                 ModelMetadata = modelMetadata,
  18.                 ModelName = subPropertyName,
  19.                 ModelState = bindingContext.ModelState,
  20.                 ValueProvider = bindingContext.ValueProvider
  21.             };
  22.             var propertyValue = this.GetPropertyValue(controllerContext, bindingContext1, propertyDescriptor, binder);
  23.             SetProperty(controllerContext, bindingContext, propertyDescriptor, propertyValue);
  24.             return;
  25.         }
  26.  
  27.         base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
  28.     }
  29. }

Vale… el código luce un “pelín” complicado, pero básicamente hace lo siguiente:

  1. Mira si la propiedad a enlazar está decorada con algún atributo que sea [ModelBinder] o derivado (en este caso será precisamente el [PropertyModelBinder]).
  2. Si dicha propiedad está enlazada obtiene el binder (mediante el método GetBinder() del atributo) y:
    1. Crea un “subcontexto” de binding, que contenga tan solo esta propiedad.
    2. Obtiene el valor a bindea, mediante la llamada a GetPropertyValue, un método definido en el propio DefaultModelBinder, pasándole el subcontexto de binding y el binder a usar.
    3. Llama a SetProperty (otro método de DefaultModelBinder) para establecer el valor de la propiedad
  3. En caso de que la propiedad NO esté decorada con ningún atributo [ModelBinder] o derivado, llama a base.BindProperty para procesarla de forma normal.

Nota: Este es el MÍNIMO código para que funcione, pero ¡ojo! no estamos tratando todas las casuísticas posibles 😉

Por supuesto para que funcione debemos decirle a ASP.NET MVC que el nuevo ModelBinder por defecto es nuestro DefaultModelBinderEx:

  1. ModelBinders.Binders.DefaultBinder = new DefaultModelBinderEx();

Y con esto ya tendríamos habilitados nuestros ModelBinders por propiedad!

Una nota final…

¿Y como tratar todas las casuísticas posibles que antes he dicho que nuestro método BindProperty no trataba y tener así un código “más completo”? Bien, pues la forma más sencilla y rápida es coger el código fuente de ASP.NET MVC y COPIAR TODO el método BindProperty del DefaultModelBinder en el DefaultModelBinderEx. Y luego una vez lo habéis echo localizad la línea:

  1. IModelBinder binder = this.Binders.GetBinder(propertyDescriptor.PropertyType);

Y modificadla por:

  1. var cmbattr = propertyDescriptor.Attributes.
  2.     OfType<CustomModelBinderAttribute>().
  3.     FirstOrDefault();
  4. IModelBinder binder = cmbattr != null
  5.     ? cmbattr.GetBinder()
  6.     : this.Binders.GetBinder(propertyDescriptor.PropertyType);

Y con esto ya tendréis el soporte completo. La verdad es una pena que el DefaultModelBinder no ofrezca un método virtual GetBinder(PropertyDescriptor) ya que si lo tuviese tan solo redefiniendo este método hubiese sido suficiente… Pero en fin… ¡esto es lo que hay!

Espero que os haya resultado interesante!