lunes, 27 de diciembre de 2010

Evitar doble Click en un botón de Ajax.

Pesadilla de medianoche. Una aplicación en Ajax que funciona perfectamente, de repente empieza a duplicar registros. Y esto es motivado porque los usuarios le han cojido el tranquillo  a la pantalla de alta y cuando llegan al botón de “Guardar”, pulsan como animales (cuatro o cinco veces por segundo).

Teniendo cuidadín de no romper el sistema de ventanas de alerta, y buscando en San Google, he llegado al blog The Disturbed Budda en donde está la siguiente solución que es elegante y limpia.

<asp:ToolkitScriptManager ID="ToolkitScriptManager1" EnablePageMethods="true" EnablePartialRendering="true"
runat="server">
</
asp:ToolkitScriptManager>

<
script type="text/javascript">
var
pbControl = null;
var prm = Sys.WebForms.PageRequestManager.getInstance();
prm.add_beginRequest(BeginRequestHandler);
prm.add_endRequest(EndRequestHandler);
function BeginRequestHandler(sender, args) {
pbControl = args.get_postBackElement(); //the control causing the postback
pbControl.disabled = true;
}
function EndRequestHandler(sender, args) {
pbControl.disabled = false;
pbControl = null;
}
</script>


Este script, que captura todos los eventos de postback asíncronos e inhabilita el control emisor hasta que haya una respuesta, hay que situarlo entre el scriptmanager y el updatepanel. En este caso particular entre el  ToolkitScriptManager  y el ContenPlacer de la MasterPage.



De todas formas debería hacerle una refactorización para usarlo solamente en los controles del tipo button y ver si puedo hacerlo en JQuery.



Seguro que es útil,

viernes, 24 de diciembre de 2010

Comparaciones de fechas en SQL.

Una de esas cosas que en código son tan simples, en SQL parecen espantosamente complicadas hasta que encuentres a quien lo ha resuelto de forma elegante y sencilla.

En este caso aquí hay una perfecta solución a un problema engorroso de comparación de fechas. Ya que el formato DateTime incluye información horaria y hay que complicar la comparación delimitandola desde  las 0:00:00 hasta las 23:59:59 para evitar errores en el filtro. Típico pedir todos los registros del dia XX y que descarte todos aquellos que sean posteriores a las 00:00:00.

En el post de Richard Chamorro se hace referencia a la solución de Anatoly Lubarsky La cual es, simplemente brillante.
DATEADD(dd, 0, DATEDIFF(dd, 0, CAMPO_FECHA))

De esta forma se descarta la parte horaria de los valores de las fecha y así, por ejemplo, para comprobar que una fecha está dentro del día de hoy simplemente realizo la siguiente comprobación:

DATEADD(dd, 0, DATEDIFF(dd, 0, @Fecha)) = DATEADD(dd, 0, DATEDIFF(dd, 0, GETDATE()))

Espero que os sea tan util como a mi.

miércoles, 22 de diciembre de 2010

Distinct en linq. Con IQueryables.

Esta es una continuación al post inicial Distinct en linq que escribí en octubre del 2010.

Cuando nos encontramos que queremos hacer un distinct a un IQueryable, nos encontramos que dá un error de sobrecarga… por lo cual la salida más rápida que he encontrado (dadas las horas de la noche que son) es convertir dicha IQueryable en un IEnumerable, que funciona de vicio.

    IQueryable<vias> listaDeVias = (from via in pDataContext.vias
where via.nombre.Contains(pNombreVia) &&
via.id_pobl == 41
orderby via.nombre, via.nombre.Length
select via).Take(30);


IEnumerable<vias> viasFinal = listaDeVias.ToList().Distinct(new CodPostal_tComparer());



Y la clase del comparador, con el hash mucho más elaborado

public class CodPostal_tComparer : IEqualityComparer<Entidades.CodPostal_t_SQL>
{
public bool Equals(Entidades.CodPostal_t_SQL x, Entidades.CodPostal_t_SQL y)
{
if (x.nombre == y.nombre && x.id_tipo_v == y.id_tipo_v)
{
return true;
}
return false;
}
public int GetHashCode(Entidades.CodPostal_t_SQL obj)
{
int hashCalleNombre = obj.nombre == null ? 0 : obj.nombre.GetHashCode();
int hashCalleTipoVia = obj.id_tipo_v.GetHashCode();

//Devuelvo la unión de ambos código hash.
return hashCalleNombre ^ hashCalleTipoVia;
}
}



Espero que os sea de utilidad tanto como a mí.




lunes, 20 de diciembre de 2010

Acceder a los controles de la página que está dentro de una MasterPage

Uno de esos inesperados dolores de cabeza que, casi en producción, me han surgido en el proyecto que ya finaliza. Tengo una página dentro de una MasterPage y quiero que las cajas de texto, y demás controles, se inhabiliten. Y no quiero ir una a una.

Sorprendentemente si utilizo un simple foreach(Control control in Page.Controls) esto no funciona. Por lo cual me he devanado un poquillo los sesos buscando como preguntar en San Google y he acabado en el foro de msdn con la siguiente respuesta

private void ClearForm(ControlCollection ctrlsCollection) // I don't get why I have to pass a ControlCollection here !  
{

if (ctrlsCollection == null) // i.e. if the collection is empty
{
return; // do nothing
}

foreach (Control c in ctrlsCollection)
{
if (c.HasControls()) // boolean True or False if it has child controls.
{
ClearForm(c.Controls); // Go back and run the method again .. kind of like depth traversal .. keeps going inside till there are no more child controls.
}
else // If there are no child controls.
{
if (c.GetType() == new TextBox().GetType()) // Check if the type of control matchs that of a text box (i.e. is that control a text box control)
{
((TextBox)c).Text = "HELLO"; // If YES .. then type cast the C as a text box control and clear its text.
}
}
}
}

O sea, utilizo recursividad para ir ahondando en los diferentes arboles de controles que lleva la página que tengo en pantalla y lo comparo con el tipo de control que quiero. En este caso, y para ver claramente que funciona en vez de decirle un .Enable = false le digo que me escriba en todos los TextBox que encuentra la palabra HELLO.



Para invocarla, por ejemplo

protected void Page_Load(object sender, EventArgs e)
{
ClearForm(Page.Controls);
}



Espero que os sea tan útil como a mi.

martes, 7 de diciembre de 2010

AsyncFileUpload + GridView. El infierno de un postback.

EDITADO: Me dá un problema, para mí, insalvable que es que me hace un doble postback. Cuidadin con esto.
En este día de puente, en donde la inmensa mayoría se están tomando unas merecidas vacaciones, heme aquí disfrutando como un enano con la suma del control AsyncFileUpload + un GridView.
Lo primero, aviso que la estructura de utilizar un datatable, seguro que se puede mejorar. De echo seguro que refactorizaré para mejorar. Pero el error que me ha consumido casi cinco horas de trabajo ha sido el siguiente: AsyncFileUpload NO hace postback en el evento de servidor AsyncFileUpload_UploadedComplete…(demonios!!).
El escenario es que voy a leer un fichero de texto del cliente, y lo voy a introducir directamente en un GridView. Para ello lo primero es saber que el AsyncFileUpload tiene tres eventos de cliente y uno de servidor. Por lo cual conformaremos nuestro control así:
<asp:AsyncFileUpload ID="AsyncFileUpload" Width="865px" 
OnClientUploadError="uploadError"
OnClientUploadStarted="StartUpload" 
OnClientUploadComplete="UploadComplete"
OnUploadedComplete="AsyncFileUpload_UploadedComplete"
CompleteBackColor="Lime"
UploaderStyle="Modern" ErrorBackColor="Red" 
UploadingBackColor="#66CCFF" runat="server"  />

Los Javascript que comprende la actuación de los tres primeros eventos en cliente son (*No consigo utilizar el Control.IdClient en un js)

// Funciones de AsyncFileUpload
function uploadError(sender, args) {
document.getElementById('ctl00_chPrincipal_lblStatus').innerText = args.get_fileName(),
"<span style='color:red;'>" + args.get_errorMessage() + "</span>";
}

function StartUpload(sender, args) {
document.getElementById('ctl00_chPrincipal_lblStatus').innerText = 'Uploading Started.';
}

function UploadComplete(sender, args) {
document.getElementById('ctl00_chPrincipal_lblStatus').innerText = 'Fichero subido corréctamente';
 __doPostBack('tctl00$chPrincipal$UpdatePanel', '');
}

Y el método que captura el evento de servidor es el siguiente:

protected void AsyncFileUpload_UploadedComplete(object sender, AjaxControlToolkit.AsyncFileUploadEventArgs e)
{
DataTable listaMatriculas = new DataTable();
listaMatriculas.Columns.Add("Matricula", typeof(string));
StreamReader lectorDelFichero = new StreamReader(AsyncFileUpload.FileContent);

string linea;
do
{
linea = lectorDelFichero.ReadLine();
DataRow fila = listaMatriculas.NewRow();
fila["Matricula"] = linea;
listaMatriculas.Rows.Add(fila);
}
while (linea != null);

GridMatriculas.DataSource = listaMatriculas;
GridMatriculas.DataBind();

}

El problema es que por mucho que le dijera que hiciera el DataBind del Grid, este no se actualizaba. Lo peor es que como no se actualizaba pensé que era un problema de la fuente de datos y salte de ArrayList, a List y luego a var de Linq… y nada, no se veía.


Desesperado, le puse un botón para recargar y, asombroso, eso sí que funciona. Lo cual me llevó a la idea de poner un UpdatePanel.Update()… lo cual tampoco sirvió. Entonces, me dije, esto es un problema de postback y me puse a mirar por San Google hasta llegar al excelente blog Schelfaut.NET (Sven Schelfaut's technical blog) en donde está la solución que he puesto en negrita. En el evento de cliente  UploadComplete, se fuera un postback sobre el UpdatePanel que contiene al GridView. Y con esto todo va como la seda.


La siguiente refactorización urgente es poder invocar directamente al IdClient  de un control desde un javascript que viene de una librería js. Y en vez de tener que construir una DataTable, poder utilizar una lista. Ya que StreamReader no soporta Linq.


Espero que os sea útil.

lunes, 6 de diciembre de 2010

Convertir ArrayList de string en string[]

Tengo que añadir un listado de cadenas de texto a un fichero de texto y el método de escritura me obliga a remitirle una matriz de string. Esto me lleva a una de esas formas semánticas que no son muy legibles, pero si absolutamente lógicas y que me he encontrado en el blog de XNEURON, el cual es muy recomendado.

File.WriteAllLines(<string>PathFisico, (string[])ArrayList.ToArray(typeof(string)));

Y para que lo veáis en acción, aquí pongo el código de un método que recibe una lista de vehículos, y almaceno sus matrículas en un fichero de texto.

static void construyeFicheroMatriculas(IQueryable<Vehiculo> listadoVehiculos)
{
string pathFicheroMatriculas = @"C:\Users\Public\Documents\Matriculas.txt" ;
ArrayList matriculas = new ArrayList();
foreach (Vehiculo vehiculoEach in listadoVehiculos)
{
matriculas.Add(vehiculoEach.matricula);
}
File.WriteAllLines(pathFicheroMatriculas, (string[])matriculas.ToArray(typeof(string)));
}

viernes, 3 de diciembre de 2010

Los límites de smalldatetime

SQL es para mí una fuente de contradicciones constante. Por una parte es indudable que es el lenguaje perfecto para su objetivo: manipular datos de la forma más eficiente posible. Pero, como desarrollador en lenguajes de programación, la lógica me parece enrevesada y muchas veces confusa.

Así tenemos el objeto smallDateTime. Un objeto de fecha normal y corriente. ¿No? Pues no. Tiene márgenes temporales más reducidos que el tipo DateTime. Exactamente del 01/01/1900 al 06/06/2079. Me imagino que la literatura del porqué de estos valores será variada y completa. Pero la limitación de 1900 convierte un error de traducción en una excepción de la base de datos.

Asique, en mi opinión, no utilices smallDateTime y utiliza siempre DateTime.