domingo, 5 de junio de 2011

(Repeater - Checkbox - CommandName) ItemCommand en repeaters por parte de Checkboxes

En esta ocasión les traigo una solución a un problema con el que me encontré varias veces. Si alguna vez tuvieron que usar checkboxes que disparen el evento OnItemCommand en un repeater, se habrán dado cuenta de que, no importa lo que hagan, el evento NO se dispara. Esto es por la forma en la que el radiobutton esta implementado (por alguna razón que desconozco, MS decidió de que, estos componentes usados dentro de un repeater no iban a comportarse como los linkbuttons o los buttons que todos queremos y estamos acostumbrados a usar).
Navegando un poco me encontré con este articulo, que me permitio hechar un poco de luz al respecto (el código de ejempo SI esta en C#).
Mientras que el botón y el linkbutton, cuentan con un metodo "OnCommand", y una llamada a dicho método, dentro de su "RaisePostbackEvent" (con el CommandName y el CommandArgument como parametros), por alguna razon, el checkbox NO lo posee.
Dicho Método ("OnCommand"), llama a "RaiseBubbleEvent", el que permite proyectar los parámetros (CommandName y CommandArgument) a quien sea que este escuchando (en este caso nuestro repeater, que implementa el "OnBubbleEvent"). Como nuestros checkboxes NO implementan este "OnCommand", nunca se dispara el "RaiseBubbleEvent", con los CommandName y CommandArgument y nunca se llega al "OnBubbleEvent" del repeater.

Como solución a esto encontré una implementación "custom" de un checkbox que sobre escribe el "OnCheckedChanged" invocando al metodo "RaiseBubbleEvent" que necesitamos pasándole los valores que queremos (CommandName y CommandArgument):


<ToolboxData("<{0}:RadioButtonRepeaterAware runat=server></{0}:RadioButtonRepeaterAware>")> _
Public Class RadioButtonRepeaterAware
Inherits RadioButton

Public Property CommandName As String
Get
If (Me.ViewState("CommandName") Is Nothing) Then
Return String.Empty
Else
Return Me.ViewState("CommandName").ToString
End If
End Get
Set(ByVal value As String)
Me.ViewState("CommandName") = value
End Set
End Property

Public Property CommandArgument As String
Get
If (Me.ViewState("CommandArgument") Is Nothing) Then
Return String.Empty
Else
Return Me.ViewState("CommandArgument").ToString
End If
End Get
Set(ByVal value As String)
Me.ViewState("CommandArgument") = value
End Set
End Property

Protected Overrides Sub OnCheckedChanged(ByVal e As System.EventArgs)
'create a new event args of type command event args
Dim ce = New CommandEventArgs(Me.CommandName, Me.CommandArgument)

'allow the base checkbox to handle the event as normal
MyBase.OnCheckedChanged(e)

'raise the contorls method RaiseBubbleEvent
MyBase.RaiseBubbleEvent(Me, ce)
End Sub
End Class


El codigo esta en VB.net como para complementar el del ejemplo que esta en C#.
Para poder usar este control en el markup de su aspx o ascx, simplemente tienen que registrar el assembly en el cual colocaron dicha clase:

<%@ Register Assembly="MyAssembly" Namespace="MyNamespace" TagPrefix="cc1" %>



<cc1:RadioButtonRepeaterAware ID="chkSelected" AutoPostBack="true" CommandName="Select"
CommandArgument='<%# DataBinder.Eval(Container.DataItem,"CommandArgumentElementHere") %>' runat="server"
>
</cc1:RadioButtonRepeaterAware>



Espero les haya servido.
P.D.: No lo probe, pero intuyo que esto también deberia poder aplicarse para los radiobuttons.

Happy Programming.

domingo, 29 de mayo de 2011

(Entity Framework Metadata) Usando metadata y LINQ para validación de campos

Hoy quería compartir con ustedes una solución practica que encontré para la valuación de campos server side. Se me presento una situación en la cual tenia que validar múltiples campos de un formulario server side, tanto en su cualidad de nullables, como en su longitud.
Como realizar esta tarea es una de las cosas mas tediosas por las que uno como desarrollador, tiene que atravesar, busque la forma de hacerlo lo mas automatizado posible. Es así que me pregunte "Tendré alguna manera de queryar la metadata del modelo, y usar eso para validar los campos??". Resulta de que es posible, usando el "MetadataWorkspace" de EF (Hago la referencia obligada a la fuente principal de donde pude sacar la info que necesitaba aqui).

Primero les voy a mostrar los métodos que uso para verificar si el campo es nullable, y cual es su maxlength, y luego el método de valuación en si mismo (pueden cuestionar el uso de Reflection como NO óptimo, pero la verdad que el degradado de performance no es perceptible en mi situación, ya que el lugar donde lo uso, no es un formulario de uso continuo e intensivo).


public static int GetMaxLength(EntityObject entity, string nomProperty  )
{
int result = 0;

var context = GetNewContext();

var queryResult = from meta in context.MetadataWorkspace.GetItems(DataSpace.CSpace)
.Where(m => m.BuiltInTypeKind == BuiltInTypeKind.EntityType)
from p in (meta as EntityType).Properties
.Where(p => p.DeclaringType.Name == entite.GetType().Name
&& p.Name == nomProperty
&& p.TypeUsage.EdmType.Name == "String")
select p.TypeUsage.Facets["MaxLength"].Value;
if (queryResult.Count() > 0)
{
result = Convert.ToInt32(queryResult.First());
}

return result;
}


El método anterior me permite obtener la longitud máxima de un campo usando la metadata del modelo conceptual, paso a diseccionarlo para ustedes:

Primero, le pido al modelo conceptual del "MetadataWorkspace" que me devuelva todas las entidades de tipo "EntityType" (puesto que podria pedir las AssociationsSet/Type, las RelationshipSet/Type, etc.):

from meta in context.MetadataWorkspace.GetItems(DataSpace.CSpace)
.Where(m => m.BuiltInTypeKind == BuiltInTypeKind.EntityType)

Sobre este subset de metadata, me intereso en conseguir, ÚNICAMENTE la metadata de la property de la entidad en la cual estoy particularmente interesado, filtrando por:
- El tipo de la entidad
- El nombre de la property
- El tipo de la property que deseo validar (esto es un filtro extra, ya que solo me interesaba validar properties del tipo "String"):

from p in (meta as EntityType).Properties
.Where(p => p.DeclaringType.Name == entity.GetType().Name
&& p.Name == nomProperty
&& p.TypeUsage.EdmType.Name == "String")




Una vez aislada la metadata de la property que solicite, solo me queda consultar la informacion en particular que necesito, en este caso "MaxLength":


select p.TypeUsage.Facets["MaxLength"].Value;


El resto es anecdotico, solo devolver el valor necesario casteado correctamente.

Para comprobar si la property es nullable o no, el procedimiento es el mismo, unicamente varia la forma en la que se devuelve la data:


public static bool IsNullable(EntityObject entity, string nomProperty)
{
var context = GetNewContext();
var queryResult = from meta in contexte.MetadataWorkspace.GetItems(DataSpace.CSpace)
.Where(m => m.BuiltInTypeKind == BuiltInTypeKind.EntityType)
from p in (meta as EntityType).Properties
.Where(p => p.DeclaringType.Name == entite.GetType().Name
&& p.Name == nomProperty
&& p.TypeUsage.EdmType.Name == "String")
select p;
if (queryResult.Count() > 0)
{
return queryResult.First().Nullable;
}

return false;
}

Como ven, el procedimiento para seleccionar la metadata de la property que necesito es el mismo, y unicamente cambia la forma en la que devuelvo el resultado, devolviendo el valor de la property "Nullable" de la metadata.

Con los metodos anteriores, facilmente pueden armar un método génerico de validación, que itere por cada una de las properties de un objeto dado, verifique cada una de las propiedades
y devuelva los nombres de las propiedades que no cumplen con los requerimientos del modelo (y por ende no permitirán que se guarde dicha entidad):



public static string ValidateStringProperties(EntityObject entity, string emptyMessage, string overflowMessage, List<string> exclusions)
{
var sb = new StringBuilder();

foreach (PropertyInfo info in entity.GetType().GetProperties())
{
if (info.PropertyType == typeof(String) && (exclusions == null || exclusions.Where(a => a == info.Name).Count()==0))
{

if (!IsNullable(entity, info.Name) && (entity.GetType().GetProperty(info.Name).GetValue(entity, null)==null || string.IsNullOrEmpty(entity.GetType().GetProperty(info.Name).GetValue(entity, null).ToString())))
{
sb.Append(string.Format(emptyMessage, info.Name));
}
else
{
if (GetMaxLength(entity, info.Name) < entity.GetType().GetProperty(info.Name).GetValue(entity, null).ToString().Length)
{
sb.Append(string.Format(overflowMessage, info.Name));
}
}
}
}

return sb.ToString();
}



Como agregado, adicione una lista de nombre de propiedades que uno desea excluir de la verificación.

Como dije antes, la performance de esta solución puede ser cuestionada, ya que uso reflection en múltiples lugares, es por eso que cada solución depende de la situación en la que se aplique.

Happy Programming.