José F. Romaniello

Las aventuras y desventuras de un codificador.

A raíz de una pregunta que hice en el foro de S#arp, fue que surgió este tema. El problema es que en un modelo MVC la vista es totalmente agnóstica con respecto al modelo de dominio. No obstante ASP.Net MVC introduce un concepto denominado ModelBinder cuyo funcionamiento sería algo así; supongamos que en una vista que sirve para llenar los datos de una persona tenemos 3 inputs llamados Persona.Nombre, Persona.Apellido y Persona.Domicilio. Mi action method podría ser de la siguiente forma:

//Sin usar model binder.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult GuardarPersona(string nombre, string apellido, string domicilio)
{
    //bla-bla-bla
    return View();
}

//Usando model binder.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult GuardarPersona(Persona personaAGuardar)
{
    //bla-bla-bla
    return View();
}
La ventaja de usar el modelbinder en este caso es que automáticamente obtengo una instancia de la clase del modelo con todas sus propiedades cargadas. El problema empieza cuando tengo una clase un poco mas complejas con propiedades no triviales (no es string, int, decimal, etc.) Para los fines del ejemplo, supongamos una vista de este tipo: pantalla El dominio tiene esta pinta:
    public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string UserName { get; set; }
    public string Password { get; set; }
    public Country Country { get; set; }
    public IEnumerable<Sport> SportsPreferences { get; set; }
}

public class Country
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Sport
{
    public int Id { get; set; }
    public string Name { get; set; }
}
DefaultModelBinder no puede con las propiedades Country y SportsPreferences y sería muy POSITIVO que si pudiera. Un problema-muchos caminos El problema radica en que la vista no tiene información acerca de países ni deportes, mas que la necesaria para mostrarle al usuario esos datos y permitirle saber al controlador cuales son los que el usuario selecciono. A grandes rasgos las soluciones que se plantean son las siguientes;
  • Que la vista contenga instancias reales sobre países y deportes.
  • Que la acción en el controlador reciba un DTO de Usuario con propiedades triviales y a partir de él construir un objeto de dominio.
  • Crear un model binder especifico para cada situación, ya sea implementando IModelBinder o heredando de DefaultModelBinder.
  • Crear un model binder genérico que sepa como resolver propiedades mas complejas ubicando la instancia a través de su respectivo repositorio (u otro método proporcionado) o creando una instancia nueva en caso de ser necesario.
Estoy muy entusiasmado con la opción 4 si bien es el camino mas engorroso. Este model binder de alguna manera resolvería la propiedad User.Country dado que el usuario ha seleccionado un país cuyo ID=’AR’ (es decir en el htmlform tenemos ‘AR’) buscando a través del método indicado dicha instancia. Intentaría que este Modelbinder no este ligado a un repositorio en concreto, ni a una forma particular de obtener las instancias. Este es el concepto: Concept2

| More

En .Net es posible definir una propiedad de esta manera

public class Sucursal{
public int Id {get; set;}
public string Nombre {get; set;}
public Empresa Empresa {get; set;}
}
WebOrb intentará generar un ValueObject de esta manera:
public class Sucursal
{
public function Sucursal(){}
public var Id:Number;
public var Nombre:String;
public var Empresa:Empresa;
}
Esto no funciona en ActionScript. Pero no se preocupen que aquí les traigo la solución; si se escribe la referencia completa al package no hay problema, es decir:
public class Sucursal
{
public function Sucursal(){}
public var Id:Number;
public var Nombre:String;
public var Empresa:miAplicacion.miPaquete.etc.vo.Empresa;
}

Como si esto fuera poco, es posible decirle a WebOrb.Net que genere los scripts con esta corrección.

Adjunto acá el archivo con la corrección.

| More

Estoy por comenzar un proyecto con una arquitectura basada en Flex - WebOrb - .Net - Nhibernate. Los detalles de por que están involucradas estas tecnologías, escapan del alcance del artículo. Nhibernate funciona bien con flex, dado que la arquitectura Flex-Weborb.net es la de un sitio web común y corriente. Lo que quería integrar era el nuevo framework de validación Nhibernate Validator a Flex. Del lado de .Net hubo una complicación dado a que WebOrb.Net no tiene un buen manejo de excepciones customizadas (como lo es la InvalidStateException de nhv). Por lo tanto definí una función que se llama ReThrowInvalidState de la siguiente forma:

public void ReThrowInvalidState(InvalidStateException ex)
{
var newException = new Weborb.Exceptions.ServiceException(ex.GetType().FullName);
newException.Data.Add("InvalidValues", ex.GetInvalidValues());
throw newException;
}
Para manejar la excepción se puede hacer algo así:
try
{
   repPersonas.Save(persona);
}
catch (InvalidStateException ex)
{
  ReThrowInvalidState(ex);
}
Ok, ahora como interceptar y mostrar la excepción en flex. Para ello defini una clase en actionscript:
package util
{
import mx.core.Container;
import mx.rpc.events.FaultEvent;
import mx.messaging.messages.ErrorMessage;

public class nhvDisplayer
{
private var _container:mx.core.Container ;


public function nhvDisplayer(container:mx.core.Container){
_container = container;
}

private virtual function ClearRecursively(control:mx.core.UIComponent) : void
{
if(control is mx.core.Container){
for each(var child:mx.core.UIComponent in mx.core.Container(control).getChildren()){
ClearRecursively(child);
}
}else{
control.errorString = "";
}


}

public virtual function ShowErrors(info:mx.rpc.events.FaultEvent ):void
{

var obj:Object = mx.messaging.messages.ErrorMessage(info.message).extendedData;
var InvalidStates:Array = obj[ "InvalidValues" ];

if(InvalidStates == null) return;

ClearRecursively(_container);

for (var i:uint = 0; i <> 0){
mx.core.UIComponent(_container[InvalidStates[i].PropertyName]).errorString += "\n";
}
mx.core.UIComponent(_container[InvalidStates[i].PropertyName]).errorString += InvalidStates[i].Message;
}
}
}
}
Ok, como se puede apreciar, esta clase contiene 2 funciones y un constructor que recibe el contenedor de controles sobre el cual opera el nhvDisplayer. La primer función limpia todos los errorString de todos los controles que estan en el contenedor recursivamente. La segunda función recibe un objeto del tipo FaultEvent, examina para ver si contiene una lista de valores invalidos, y si la contiene carga la propiedad errorString. Por ultimo en cada form deberíamos hacer algo así, en el fault event del guardar:
public virtual function Fallo(info:mx.rpc.events.FaultEvent ):void
{

var nhvdisp:nhvDisplayer = new nhvDisplayer(this);
nhvdisp.ShowErrors(info);
}
Y eso es todo, este método me gusta mas que las validaciones en la capa de presentación ver y que las validaciones en el modelo pero escritas en la presentación !!! ver. Finalmente de esta forma, las validaciones estan ligadas al modelo y definidas en el modelo ya sea mediante atributos en las clases del modelo, clases externas o xml. Para mas información sobre Nhibernate Validator ver aquí y aquí. Como no podía faltar, una screenshot:

| More