a nil value

Blog sobre programación e informática en general (Por Guillermo Zafra)

Browsing Posts tagged asp.net

Quizás en alguna ocasión hayamos querido mostrar una ventana “estilo modal” por encima del resto del formulario. Es algo muy usado en la programación web y que aumenta de forma considerable la usabilidad del interfaz. Sin embargo, en aplicaciones con muchos controles, nos hemos podido encontrar con un desagradable problema, y es que los controles asp.net DropDownList (Select en HTML) ignoran totalmente el estilo z-index de las capas, y como consecuencia aparecen por encima de nuestra ventana modal, estropeando todo el buen aspecto de la aplicación.

Existen dos soluciones conocidas al problema:

La primera, aunque funciona, conlleva la creación de un script que carga mucho trabajo a la aplicación, y según la cantidad de controles que tengamos, puede no ser una posible alternativa.

for (f = 0; f < document.forms.length; f++)
{
    var elements = document.forms[f].elements;
    // looping through all elements on certain form
 
    for (e = 0; e < elements.length; e++)
    {
        if (elements[e].type == "select-one")
        {
            elements[e].style.display = 'none';
        }
    }
}

Este script recorre todos los controles de nuestro formulario, ocultando aquellos que sean del tipo Select. Claro que si tenemos un número finito de este tipo de controles podemos obviar el bucle e ir directamente al grano.

Una vez los tengamos ocultos podemos mostrar nuestra ventana modal sin riesgo a que sea destrozada por los Select, que estarán ocultos.

Por supuesto hay que controlar que, una vez hayamos acabado con la ventana modal, hay que volver a mostrar estos controles usando el mismo sistema.

La segunda alternativa es mas interesante todavía, aunque parece más un “hack” que otra cosa, y desconozco hasta que punto sería compatible con todos los navegadores. Consiste en hacer uso del elemento “iframe”. Este elemento tiene la característica de aparecer siempre por encima de cualquier otro control web, es decir, incluso por encima de los DropDownList. Y diréis, ¿pero eso no sustituye un problema por otro? Nó exactamente, ya que este elemento admite el estilo z-index y ¡lo cumple!.

Vamos, que podemos mostrar una especie de panel separador entre los controles DropDownList y la ventana modal que queramos mostrar tan solo asignandole un z-index inferior al de nuestra ventana.

Primero añadiríamos el iframe dentro de la ventana que vamos a ocultar y mostrar. Tan solo se mostrará cuando mostremos la ventana.

<iframe id="iframe" class="iframeLayer" frameborder="0"></iframe>

Y segundo, añadiremos el estilo correspondiente tal que:

iframe.iframeLayer
{
    left:600px;
    top:300px;
    position: absolute;
    width: 200px;
    height: 100px;
    z-index: 10;
 
}

Hay que tener especial cuidado en especificar exactamente la misma posición y tamaño que la ventana modal. El iframe se mostrará como un area blanca y ocultará todo lo que esté debajo suyo, pero mostrará nuestra ventana siempre que tenga un z-index superior a 10.

El problema de este método es que queramos crear algo más complejo, o por ejemplo crear un fondo transparente, ya que en ese caso, en lugar de crear un iframe para el area de la ventana, tendremos que crear un iframe para el area de cada control DropDownList que tengamos, lo cual ya es otra historia.

La elección es vuestra. Un saludo

Fuente: CodeProject

Cuando se trabaja de forma asíncrona con AJAX y UpdatePanels resulta imprescindible implementar procesos para informar al usuario de que se están efectuando actualizaciones en el servidor.

Por defecto, ASP.NET AJAX nos proporcionar el complemento UpdateProgress, que unido a un ScriptManager y un UpdatePanel permiten mostrar cualquier indicador mientras el UpdatePanel esta trabajando asíncronamente.

No obstante el UpdateProgress es limitado si queremos realizar tareas o efectos mas complicados. Por ejemplo posicionar el loader dinamicamente en la pantalla segun lo que se actualice, realizar alguna accion de Jscript antes de comenzar la actualización, o permitir al usuario cancelar el proceso.

Para ello existe un maravilloso y muy simple javascript que controla dos eventos importantísimos de la página; el comienzo y el fin del httprequest.

<script language="javascript" type="text/javascript">
Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(
  function(sender, e)
  {
        $get('cargador').style.display="block";
   });
 Sys.WebForms.PageRequestManager.getInstance().add_endRequest(
  function(sender, e)
  {
        $get('cargador').style.display="none";
   });
 
</script>

Donde “cargador” podría ser, por ejemplo:

<div id="cargador" runat="Server" style="display:none">
        <table width="200">
            <tr>
                <td height="50" valign="bottom" align="center">Cargando...</td>
            </tr>
            <tr>
            <td height="50" valign="top" align="center"><img alt="cargando..." src="../img/ajax-loader.gif" /></td>
            </tr>
        </table>
    </div>

add_beginRequest se lanzaría antes de comenzar el evento de solicitar información al servidor y add_endRequest justo despues de terminar todo el proceso.

Para cancelar el Request bastaría con mostrar un boton en el beginRequest que lanzara el siguiente script:

function CancelarPostback() {
    var man= Sys.WebForms.PageRequestManager.getInstance();
    if (man.get_isInAsyncPostBack())
        man.abortPostBack();
    }

Aunque ojo! con este “Cancelar” ya que, si bien cancelara el request y forzando el endRequest, el proceso en el servidor es muy probable que se haya ejecutado ya. Mi consejo es que se utilice unicamente para procesos de lectura y no de escritura.

Un saludo

Bueno, esta es sencilla y fácil de encontrar pero nunca se sabe cuando puede ser útil. Ahora bien, puede confundir al usuario.

<asp:CheckBox runat="server" ID="chkPrueba" onclick="cambiarTextoCheckBox(this);" />

Y la función Javascript:

function cambiarTextoCheckBox(checkbox)
{
  if (checkbox.checked)
    checkbox.nextSibling.innerHTML = 'Activado';
  else
    checkbox.nextSibling.innerHTML = 'Desactivado';
}

Si generamos un UserControl dinámicamente y queremos pasarle parámetros desde la página principal para poder trabajar con ellos tenemos dos métodos:

Metodo fácil:

Utilizando propiedades públicas en nuestro UserControl tal que:

myUserControl ctrl = this.Page.LoadControl("myUserControl.ascx");
ctrl.value1 = "First Parameter";
ctrl.value2 = 200
Panel.Contrlols.Add(ctrl);

Obviamente tendremos que tener dos propiedades publicas en nuestra clase myUserControl para los dos parámetros que queremos pasar.

Y el método ninja:

Añadimos la siguiente función en la página donde hagamos queramos cargar dinámicamente el UserControl:

private UserControl LoadControl(string UserControlPath, params object[] constructorParameters)
{
List constParamTypes = new List();
foreach (object constParam in constructorParameters)
{
constParamTypes.Add(constParam.GetType());
}
 
UserControl ctl = Page.LoadControl(UserControlPath) as UserControl;
 
// Find the relevant constructor
ConstructorInfo constructor = ctl.GetType().BaseType.GetConstructor(constParamTypes.ToArray());
 
//And then call the relevant constructor
if (constructor == null)
{
throw new MemberAccessException("The requested constructor was not found on : " + ctl.GetType().BaseType.ToString());
}
else
{
constructor.Invoke(ctl, constructorParameters);
}
 
// Finally return the fully initialized UC
return ctl;
}

Añadimos en nuestro UserControl dos constructores; uno vacío y otro con los parámetros que le queramos pasar.

public partial class myUserControl : System.Web.UI.UserControl
{
private string value1;
private int value2;
public myUserControl ()
{
}
 
public myUserControl (string parameter1, int parameter2)
{
this.value1 = parameter1;
this.value2 = parameter2;
}
}

Y por último cuando queramos añadir dinámicamente el control:

Control ctrl = LoadControl("myUserControl.ascx","First Parameter",200) ;
Panel.Controls.Add(ctrl) ;

Fuente: http://blah.winsmarts.com/2006/05/20/loadcontrol-a-usercontrol–and-pass-in-constructor-parameters.aspx

Cualquier control web tanto HTML cómo ASP.NET tiene un ID propio que le identifica en una página web y que no puede ser repetido en dicha página. Sin embargo, cuando utilizamos controles de usuario (.ascx) nos encontramos con un problema, y es que podemos tener controles con identificadores repetidos dentro del control de usuario.

Cuando la página se renderiza en completo HTML estos dos controles con el mismo ID no tendrán el mismo, para solucionarlo se utilizan un identificador único que podemos obtener mediante la propiedad UniqueID.

Este ID único se genera de la siguiente manera:

Si en la página principal tuviéramos un control ID “txtMensaje” y con UserControl “PanelMensaje.ascx”.

Y dentro del UserControl PanelMensaje tuvieramos otro control con ID “txtMensaje”, el resultado sería que este último tendría el ID “PanelMensaje$txtMensaje”. Es decir, que todos los controles hijos de PanelMensaje añadirían el prefijo PanelMensaje$ a su id.

Esto es acumulativo, de manera que si anidamos mas controles se identificarán jerárquicamente por los identificadores de los contenedores y separados por el carácter dolar $.

Algo muy común si generamos los controles dinámicamente y los agrupamos por contenedores.

Siguiendo con la temática del artículo anterior sabemos que conseguir un postback es tarea fácil con la mayoría de los controles web de ASP.NET, pero puede ocurrir que queramos forzar a la página a realizar una acción de postback desde un lugar que no este preparado para ello.

Para conseguir esto utilizaremos la funcion __dopostback:

function __doPostBack(eventTarget, eventArgument) {
if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
theForm.__EVENTTARGET.value = eventTarget;
theForm.__EVENTARGUMENT.value = eventArgument;
theForm.submit();
}
}

Donde eventTarget es el control que realiza el postback y eventArgument el argumento que queremos enviar junto con el postback (si es que queremos).

Llamando a esta función desde cualquier evento de cualquier control HTML conseguiremos enviar la página al servidor. Especialmente útil cuando si estamos generando controles dinámicamente que realicen postbacks al servidor y queremos controlarlas manualmente.

Un saludo

Bueno, cualquiera que haya hecho algún desarrollo web sabrá lo que es un postback, pero en el momento que nos ponemos a trabajar con AJAX nos encontramos con un arsenal de técnicas a nuestra disposición para controlar que las comunicaciones entre el cliente y el servidor sean cómo nosotros queramos y no cómo quiera nuestra aplicación.

No voy a ponerme a explicar todo desde el principio, así que haré un pequeño resumen:

Postback – Acción por la cual, el contenido de una página en el lado del cliente es enviada al servidor para su procesamiento.

Callback – Aunque podemos confundirlo con un postback, los callback surgen con ASP.NET 2.0 y permiten realizar llamadas al servidor desde el código del lado del cliente, es decir, desde javascript. La principal diferencia es que, mientras que un Postback envía toda la página al servidor y la vuelve a renderizar, un Callback unicamente invoca a una función determinada en el código del servidor. Este método se usa generalmente para devolver datos que necesitemos procesar u obtener en el servidor.

Si utilizamos el AjaxControlToolkit o ASP.NET 3.5 y utilizamos los maravillosos UpdatePanels tenemos que saber que aunque generalicemos y hablemos siempre de PostBacks, muchas veces el propio framework nos generará internamente callbacks, y aunque ASP.NET nos haga el trabajo sucio conviene diferenciarlos ya que puede ocurrir que no necesitemos o incluso que no queramos que se haga un postback completo, especialmente si vamos a tener en cuenta el rendimiento de nuestra web.

Cómo continuación del truquillo que puse para guardar las lineas de los errores en el servidor voy a añadir un tutoríal para guardar un log de errores en ficheros, de esta manera cada vez que ocurra un error, sea contrlado mediante un bloque try-catch o sin controlar, tendremos constancia de él y sabremos donde ha ocurrido, incluido el origen y la linea.

Lo primero que hay que tener en cuenta es que la mayoría de los errores que ocurren en nuestra página web deben estar controlados, pero puede suceder que ocurran excepciones en lugares que no habiamos previsto. Incluso aunque sepamos que tenemos todo controlado creo que siempre es recomendable establecer un proceso para los errores globales de nuestra aplicación, aunque nunca se utilice.

Para ello implementaremos dos versiones de la misma función, a la que llamaremos, por ejemplo, “lanzarError”; la primera recibirá la excepción que hemos controlado desde nuestra página web, la tratará y la almacenará en una ruta específica.

public static int lanzarError(Exception ex)
        {
 
            StringBuilder errorInfo = new StringBuilder();
 
            //obtenemos la traza del error
            System.Diagnostics.StackTrace trace = new System.Diagnostics.StackTrace(ex, true);
 
            //creamos un contenido que incluiremos en el log del error
            errorInfo.Append("Método: " + ex.TargetSite.ToString() + "\n");
            errorInfo.Append("Código: " + ex.Source + "\n");
            errorInfo.Append("Mensaje de Error: " + ex.Message + "\n");
            errorInfo.Append("Traza de la Pila: " + ex.StackTrace + "\n");
            errorInfo.Append("*******************************************************************" + "\n");
            errorInfo.Append("*******************************************************************" + "\n");
            errorInfo.Append("Excepción:" + ex.ToString());
            errorInfo.Append("*******************************************************************" + "\n");
            errorInfo.Append("*******************************************************************" + "\n");
 
            //añadimos información adicional (Es necesario cargar el .pbd de la aplicación)
            errorInfo.Append("Método:" + trace.GetFrame(0).GetMethod().Name + "\n");
            errorInfo.Append("Linea:" + trace.GetFrame(0).GetFileLineNumber().ToString() + "\n");
            errorInfo.Append("Columna:" + trace.GetFrame(0).GetFileColumnNumber().ToString() + "\n");
 
            //obtenemos el nombre del fichero añadiendo un sufijo
            string nombreFichero = obtenerNombreFichero("CAUGH_EX");
 
            //escribimos en el fichero del log
            if (escribirFichero(errorInfo.ToString(), nombreFichero) == -1)
            {
                return -1;
            }
            else
            {
                return 1;
            }
 
        }

Cómo podemos ver no tiene ningún secreto, crearemos un StringBuilder con lo que queramos guardar en nuestro fichero de log, elegimos un nombre para el fichero y escribimos.

Esta función hace uso de dos funciones adicionales; una para establecer el nombre del fichero y otra para escribir en él.

protected static string obtenerNombreFichero(string sufijo)
        {
            string fecError = "";
            string codError = "";
            DateTime Now = System.DateTime.Now;
            //DateTimeFormatInfo dfi = new DateTimeFormatInfo();
 
            //establecemos el formato que tendrá la fecha incluida en el nombre
            //dfi.FullDateTimePattern = "dd-MM-yyyy_HHmmss";
 
            fecError = String.Format("{0:dd-MM-yyyy_hh.mm.ss}", Now);
 
            codError = fecError + "_" + sufijo;
 
            string nombreFichero = "Error_" + codError + ".txt";
 
            return nombreFichero;
 
        }

Podemos elegir el nombre del fichero que queramos. En este caso he pensado que establecer la fecha completa sería una buena idea, pero ojo!, tenemos que tener en cuenta que si no añadimos un nombre único podemos encontrarnos con ficheros duplicados. Tenemos dos opciones. O comprobamos que si existe un fichero con ese nombre añadimos un sufijo adicional, o añadimos más parámetros a la hora (¿milisegundos?).

protected static int escribirFichero(string texto, string nombreFichero)
        {
 
            //obtenemos la ruta donde crearemos el log. Podemos enviarla desde nuestra aplicación
            string ruta = ConfigurationSettings.AppSettings.Get("errorPath") + "\\errores\\";
 
            //controlamos que existe la ruta y si no la creamos
            if (!System.IO.Directory.Exists(ruta))
            {
                System.IO.Directory.CreateDirectory(ruta);
            }
 
            //creamos el flujo de salida para escribir
            System.IO.StreamWriter sw = new System.IO.StreamWriter(ruta + nombreFichero, false);
 
            try
            {
                //intentamos escribir en el fichero
                sw.Write(texto);
                sw.Flush();
                return 1;
            }
            catch (Exception ex)
            {
                return -1;
            }
            finally
            {
                sw.Close();
            }
 
        }

Esta segunda función nos servirá para escribir en el fichero, enviándole previamente una ruta en donde almacenar los ficheros del log. En este ejemplo la ruta se obtiene desde un fichero de configuración de la aplicación, pero si queremos encapsular totalmente el registro de errores deberemos enviar esta ruta desde nuestra aplicación web y habrá que añadirlo como parámetro en la cabecera de la función.

Finalmente tenemos la segunda versión de lanzarError que sobrecarga a la primera:

public static int lanzarError(HttpContext ctx)
        {
            //Deberemos pasar información de la sesión desde nuestra aplicación
            //HttpContext ctx = HttpContext.Current;
 
            //obtenemos le ultimo error ocurrido
            Exception exception = ctx.Server.GetLastError();
            StringBuilder errorInfo = new StringBuilder();
 
            //obtenemos la traza del error
            System.Diagnostics.StackTrace trace = new System.Diagnostics.StackTrace(exception, true);
 
            //creamos un contenido que incluiremos en el log del error
            errorInfo.Append("Página del Error: " + ctx.Request.Url.ToString() + "\n");
            errorInfo.Append("Código: " + exception.Source + "\n");
            errorInfo.Append("Mensaje de Error: " + exception.Message + "\n");
            errorInfo.Append("Traza de la Pila: " + exception.StackTrace + "\n");
            errorInfo.Append("*******************************************************************" + "\n");
            errorInfo.Append("*******************************************************************" + "\n");
            errorInfo.Append("Excepción:" + exception.ToString());
            errorInfo.Append("*******************************************************************" + "\n");
            errorInfo.Append("*******************************************************************" + "\n");
 
            //añadimos información adicional (Es necesario cargar el .pbd de la aplicación)
            errorInfo.Append("Método:" + trace.GetFrame(0).GetMethod().Name + "\n");
            errorInfo.Append("Linea:" + trace.GetFrame(0).GetFileLineNumber().ToString() + "\n");
            errorInfo.Append("Columna:" + trace.GetFrame(0).GetFileColumnNumber().ToString() + "\n");
 
            //obtenemos el nombre del fichero añadiendo un sufijo
            string nombreFichero = obtenerNombreFichero("UNCAUGH_EX");
 
            //escribimos en el fichero del log
            if (escribirFichero(errorInfo.ToString(), nombreFichero) == -1)
            {
                return -1;
            }
            else
            {
                return 1;
            }
 
        }

Cómo podemos ver es similar a la primera salvo que en este caso se envía la información sobre el contexto de la aplicación y de ahí obtendremos el último error ocurrido. Esta función nos permitirá registrar excepciones no controladas, pero para ello primero deberemos añadir un par de cosas en el fichero “Global.asax” de nuestra aplicación web:

protected void Application_Error(object sender, EventArgs e)
        {
            HttpContext ctx = HttpContext.Current;
            exCatcher.lanzarError(ctx);
            ctx.Response.Redirect("Error.aspx");
        }

El evento Application_Error ya existe por defecto en el Global.asax, así que solo tendremos que añadir el contenido. Esto unicamente obtendra el estado de la aplicación y lo enviará a nuestra función lanzarError. Finalmente redirigiremos a una página de Error que nos habremos creado previamente y en la que informaremos al usuario de lo que creamos conveniente.

Y eso es todo. Esto podemos complicarlo todo lo que queramos para añadirle más posibilidades.

Espero que haya servido de ayuda.

Un saludo

Una técnica frecuente cuando realizamos grandes proyectos web es crear un log o registro de todos los errores que suceden en el servidor de nuestra aplicación web, de manera que, aunque no estemos depurando podamos conocer cuando y donde falla nuestra aplicación. Es especialmente útil cuando es utilizada por un gran numero de usuarios, pues estos siempre acaban encontrando fallos.

Sin embargo, en muchos casos, nos habremos dado cuenta de que la descripción del error que nos aparece, a pesar de ser muy detallada, no nos da una información esencial, la clase donde ocurre el error y la linea.

Estos datos se obtienen facilmente en el equipo local cuando depuramos, pero se pierden cuando la aplicación se ejecuta en el servidor con el debug desactivado. Para añadir esta información a la descripción del error deberemos hacer dos cosas:

1.- En las propiedades del proyecto, dentro del Visual Studio, debemos ir a la pestaña Compilar y a Opciones de compilación avanzadas. Dentro cambiaremos “Generar información de depuración” a pdb-only.

2.- Ahora solo tenemos que volver a generar nuestra aplicación y recordar subir el fichero .pdb del proyecto que se genera junto a la dll en la carpeta “bin”.

Con este método obtendremos en la información del Stack, el nombre del fichero donde ocurre el error y la linea exacta en la que se lanzó la excepción.

Otro día explicaré el método entero parar generar un log de errores, pero esto, mediante un poco de Google, es bastante sencillo.
Saludos

Powered by WordPress Web Design by SRS Solutions © 2010 a nil value Design by SRS Solutions