Mostrando entradas con la etiqueta javascript. Mostrar todas las entradas
Mostrando entradas con la etiqueta javascript. Mostrar todas las entradas

20 abr 2024

.Net minimal API y Auto-Bindings

Github Repo

 Estoy, de a poco, retornando al mundo de .Net, por lo que para ir desempolvando conocimiento al tiempo que pruebo algunos conceptos con los que no estoy familiarizado pensé en realizar un ejercicio sencillo de crear una minimal API y jugar (para entender) con la característica de los auto-bindings que posee .Net 8. Esto basándome en un post en ingles que encontré aquí).

Empecemos pues: Primero escogemos un nuevo proyecto del tipo asp.net core empty, para tener lo mínimo necesario

Le asignamos un nombre y una ubicación y seguidamente escogemos el framework 8.0, con la configuración para HTTPS y habilitamos Docker.


Al final obtendremos una configuración muy básica donde tendremos un archivo program.cs donde podremos agregar nuestros endpoints. El código generado es el siguiente:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();
De hecho yo solo voy a cambiar el texto del "Hello world!" por ""My minimal API is up and running!" y lo voy a ejecutar, al hacerlo se abre una ventana de navegador con el mensaje esperado:
Lo siguiente que voy a hacer es añadir soporte para el estándar OpenAPI, por medio de swagger. Para esto se debe añadir el paquete Swashbuckle.AspNetCore por medio de la Package Manager Console ejecutando la siguiente instrucción en esta consola:
Install-Package Swashbuckle.AspNetCore
Una vez instalado este paquete paquete, modificamos el archivo program.cs para quede de la siguiente manera:
using Microsoft.AspNetCore.Mvc;

#region Configuring

var builder = WebApplication.CreateBuilder(args);

//configuring CORS
builder.Services.AddCors(op => op.AddDefaultPolicy(
    builder =>
    {
        builder.AllowAnyOrigin()
        .AllowAnyMethod()
        .AllowAnyHeader();
    }));

//Adding Swagger
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddMvc(x => x.EnableEndpointRouting = false);

var app = builder.Build();

app.UseCors();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseMvc();

#endregion Configuring

#region Endpoints

app.MapGet("/", () => "My minimal API is up and running!");

#endregion Endpoints

app.Run();

#region helpers/utilities
#endregion
Esta parte es un poco de "carpintería" estándar en la que añadimos el manejo de CORS para facilitarlos las pruebas, así como la adición del Estándar OpenAPI el que nos da una interfaz más amigable para nuestra api:
Com se puede notar, el código esta divido en tres secciones "configuration", "endpoints" y "helpers/utilities" lo que nos ayudará a organizar e indicar en dónde va el código que vamos a crear. Ahora vamos a añadir nuevos endpoints para probar la característica de Auto-Binding. 

Microsoft .Net permite marcar los parámetros de los endpoints de nuestras APIs (no únicamente de las minimal APIs) con atributos que establecen como éstos se enlazan (binding) automáticamente según como se espera que se envíen al API. Estos atributos en la version 8 de .Net son:
  • [FromQuery]
  • [FromForm]
  • [FromBody]
  • [FromHeader]
  • [FromServices]
  • [FromKeyedServices]

FromQuery

Añadimos un nuevo endpoint Get que obtenga un id del querystring y devuelva un simple objeto con un mensaje indicando que id recibió
app.MapGet("/users", IResult ([FromQuery] int id) =>
{
    return Results.Ok(new { message = $"You send the id {id}" });
});
Esto lo podemos probar atravéz de la interfaz de OpenAPI (Swagger)
Como podemos ver el id es enviado a la API como un parametro querystring (en la url) y es enlazado correctamente, devolviendo una respuesta satisfactoria.

Ahora vamos a crear un endpoint POST que reciba dos parametros por medio de querystring
app.MapPost("/users", IResult ([FromQuery] int id, [FromQuery] string name) =>
{
    return Results.Ok(new { message = $"You send the id {id} with the name {name}" });
});
Aunque es posible y más sencillo probar este nuevo endpoint tal y como lo hicimos con el anterior, usando swagger; Esta vez vamos a usar otra técnica: vamos usar usar javascript. Y ¿Cómo lo hacemos? pues en la ventana del navegador abrimos las herramientas de desarrollador (usualmente con la tecla F12, vamos a la consola y aqui podemos escribir o pegar codigo javascript) por ejemplo:
fetch(`https://localhost:32770/users?id=1&name=foy`,
      {method: 'POST'})
        .then(response => response.json())
        .then(data => console.log(data));
Una vez más obtenemos el resultado esperado.

FromForm

Vamos a crear un nuevo endpoint POST que espera que los parametros sean enviados atravez de un formulario (form)
app.MapPost("/users2", IResult ([FromForm] int id, [FromForm] string name) =>
{
    return Results.Ok(new { message = $"You send the id {id} with the name {name}" });
});
Este código compila y parece correcto, de hecho lo es. Pero si intentamos probarlo, por ejemplo con Swagger, vamos a obtener el siguiente mensaje
El problema es que apartir de la versión 8 de .Net el envío de formularios requieren, por defecto, validar el token de anti-forgery usando un nuevo middleware. Para los efectos de esta publicación únicamente voy a omitir esta validación ajustando ligeramente el código añadiendo el llamado a DisableAntiforgery al final del endpoint quedado el código de esta manera (esto para nada es una recomendación):
app.MapPost("/users2", IResult ([FromForm] int id, [FromForm] string name) =>
{
    return Results.Ok(new { message = $"You send the id {id} with the name {name}" });
}).DisableAntiforgery();
Para comprobar que ahora funciona correctamente podemos usar este código de javasript o bien Swagger:
var fdata = new FormData();
fdata.append("id", 1);
fdata.append("name","foy");
fetch(`https://localhost:32770/users2`,{
     method:'POST',
     body:fdata,
})
.then(response => response.json())
.then(data => console.log(data));
Adicionalmente podemos usar una clase o un record como parámetro cuando usamos el atributo [FromForm]. Por ejemplo vamos a crear un record en nuestra region de helper/utilities de esta manera:
record User
{
    public int id { get; set; }
    public string name { get; set; }
}
Ahora podemos añadir un nuevo endpoint que utilice este record como el tipo de parámetro
app.MapPost("/users3", IResult ([FromForm] User user) =>
{
    return Results.Ok(new { message = $"You send a model with the id {user.id} and name {user.name} " });
}).DisableAntiforgery();
Se puede usar exactamente el mismo código javascript que utilizamos para probar el último endpoint solo cambiando users3 en lugar de users2 en la URL y obtendremos una respuesta satisfactoria (o también podemos usar el swagger).

FromBody

El attribute [FromBody] es el que usamos para obtener los datos cuando se nos envía un objeto JSON. Y ese concepto del "objeto" es donde esta la clave para entender su comportamiento, Intentemos un enfoque muy simple, creamos un endpoint donde recibimos un único parámetro. 
app.MapPost("/users4", IResult ([FromBody] int id) =>
{
    return Results.Ok(new { message = $"You send the id {id}" });
});
A primera vista este codigo parece intuitivo y correcto, pero tiene un problema. Si probamos el código, usando el swagger por ejemplo, obtenemos esta respuesta:
El mensaje nos indica que no pudo leer el parámetro int id. La pista, como dijimos antes, es que estamos enviando el parámetro id dentro de un objeto JSON, por lo que debemos tener un objeto para recibir este parámetro. Para lograr esto vamos a crear una clase con una única propiedad llamada id en le región de helpers/utilities:
internal class UserId
{
    public int id { get; set; }
}
Es solo un contenedor de la propiedad, un modelo muy simple podríamos decir. Ahora podemos modificar el endpoint users4 para que use esta clase como tipo del parámetro, quedando el código de esta manera:
app.MapPost("/users4", IResult ([FromBody] UserId userId) =>
{
    return Results.Ok(new { message = $"You send the id {userId.id}" });
});

Ya lo podemos probar usando Swagger y obtendremos una respuesta satisfactoria

Por supuesto que el tipo usado como parámetro puede ser mas complejo, de hecho podemos usar el record creado anteriormente. Vamos a crear un nuevo endpoint para comprobarlo
app.MapPost("/users5", IResult ([FromBody] User user) =>
{
    return Results.Ok(new { message = $"You send a model with the id {user.id} and name {user.name} " });
});

Esta vez para probarlo usemos el siguiente código javascript (el resultado es el mismo que con swagger):
var jdata = {"id":1, "name":"Foy"};
fetch(`https://localhost:32770/users5`,{
     method:'POST',
     body:JSON.stringify(jdata), 
  headers: {
           'Content-type':'application/json; charset=UTF-8',
       }  
})
.then(response => response.json())
.then(data => console.log(data));
Como podemos comprobar el resultado es satisfactorio.

FromHeader

El atributo [FromHeader] es muy simple y similar a [FromQuery] sólo que en este caso en lugar de buscar el parámetro en el querystring lo buscaremos en el header del request. Definimos un endpoint que reciba un único parámetro:
app.MapPost("/users6", IResult ([FromHeader] int id) =>
{
    return Results.Ok(new { message = $"You send the id {id} in the header" });
});
Una vez más podemos probar el endoint usando swagger o el siguiente código javascript que envia en el header el parametro id
fetch(`https://localhost:32770/users6`,{
    method:'POST', 
    headers: {
           'Content-type':'application/json; charset=UTF-8',
            "id": 1
       },
})
.then(response => response.json())
.then(data => console.log(data));
Y obtenemos la respuesta es la esperada.

Inyección de dependencias

Ahora vamos a pasar a los últimos dos atributos, pero estos tienen una aplicación diferente a los que hemos visto hasta ahora. [FromQuery], [FromForm], [FromBody] y [FromHeader] nos sirven para determinar la fuente del enlace esperado para los parámetros que definimos, pero esta fuente viene dada por el consumidor de nuestro servicio, es decir, lo que nos envía quien consume nuestra API.

Ahora bien, [FromServices] y [FromKeyedServices] tienen otro propósito, nos ayudan a enlazar nuestros parámetros con servicios internos de la API, es decir a inyectar dependencias directamente en los métodos que en este caso representan endpoints. Esto es nuevo en .Net 8, en las versiones anteriores la inyección de dependencias la establecíamos en el constructor de la clase, ahora estos nuevos atributos nos brindar flexibilidad adicional en este sentido.

FromServices

Supongamos que tenemos un servicio de caché que implementa la interfaz ICache que hemos definido previamente. En nuestra minimal API (o en una web API "normal") queremos usar este servicio pero únicamente en uno de nuestros endpoints. Anteriormente lo que hubiésemos hecho seria inyectar esta dependencia en el constructor del controlador correspondiente, sin importar la cantidad endpoint que este contenga, aunque solo lo necesitamos en uno.

Ahora con el atributo [FromServices] podemos ser mas flexibles y precisos de dónde queremos esta inyección. Empecemos por añadir en la región de Helpers/Utilities la interfaz ICache y la clase que la implementa que llamaremos SmallCache:
public interface ICache
{
    object Get(string key);
}

public class SmallCache : ICache
{
    public object Get(string key) => $"Resolving {key} from small cache.";
}
Como podemos deducir del código este servicio tiene un único método "Get" que recibe una "key" y, para los propósitos de este código, únicamente devolvemos un mensaje (Si fuera una implementación real devolveríamos el objeto asociado a este key si estuviese almacenado en nuestro caché). Ahora necesitamos registrar este servicio en el builder de la minimal API para que el mecanismo de inyección de dependencias nos permita utilizarlo, para esto en la region de configuration antes de la instrucción var app = builder.Build(); añadimos la siguiente instrucción:
builder.Services.AddSingleton();
Con esto añadimos una instancia singleton de SmallCache que será inyectada cada vez que solicitemos un objeto que implemente la interfaz ICache. Con esta implementación vamos a crear un nuevo endpoint GET que reciba un id en el querystring y al que se le inyectará el servicio SmallCache:

app.MapGet("/users7", IResult ([FromQuery] int id, [FromServices] ICache cacheService) =>
{
    string cacheMessage = (string)cacheService.Get($"MyKey{id}");
    return Results.Ok(new { message = $"You send the id {id}.", cache = cacheMessage });
});

Podemos probarlo usando Swagger para constatar que el servicio small cache es inyectado correctamente y devuelve el resultado esperado.

FromKeyedServices

El atributo [FromKeyedServices] funciona de manera muy similar a [FromServices] con la diferencia de que el caso de que existan varios servicios que implementan la misma interfaz podemos especificar cual queremos que se instancie e inyecte en nuestro método. Para ejemplificar esto supongamos que ahora tenemos otro servicio que implementa la interfaz ICache, la clase que lo implementa se llama BigCache, de manera que en nuestra región Helpers/Utilities añadimos el siguiente código:
public class BigCache : ICache
{
    public object Get(string key) => $"Resolving {key} from big cache.";
}
Ahora necesitamos registrar este nuevo servicio. Vamos a hacerlo en la region configuration antes de la linea var app = builder.Build(); con esta nueva instrucción.
builder.Services.AddKeyedSingleton("big");
Como podemos deducir, estamos registrando un nuevo servicio que implementa la interfaz ICache pero le estamos asignado la llame (key) "big" por lo que ahora podemos crear un nuevo endpoint y por medio de [FromKeyedServices] especificar que queremos esta implementación y no la por defecto por medio de este código:
app.MapGet("/users8", IResult ([FromQuery] int id, [FromKeyedServices("big")] ICache cacheService) =>
{
    string cacheMessage = (string)cacheService.Get($"MyKey{id}");
    return Results.Ok(new { message = $"You send the id {id}.", cache = cacheMessage });
})
El atributo especificamos la "key" del servicio que queremos se inyecte y la inyección de dependencias de .net hace el resto. Al probarlo con Swagger constatamos que funciona correctamente.

También es posible registrar ambos servicios BigCache y SmallCache usando el método AddKeyedSingleton para ambos escogiendo por ejemplo la llave "small" para el segundo, lo que nos obligaría a usar el atributo [FromKeyedServices] en el endpoint users7 si así se desea.

Con esto termino este post que creo que se me extendió más de lo que pretendía. 

Como un recurso adicional se encuentra esta página de la documentación de Microsoft respecto a los bindings disponibles. Eso es todo por hoy.

26 ago 2012

Notas sobre High Perfomance JavaScript

Hace ya tiempo que medio leí un libro que se llama High Perfomance JavaScript, muy interesante en realidad sobre la optimización del uso de javascript desde su posicionamiento en una página web hasta la manipulación de los objetos DOM de la misma. No esta muy actualizado con respecto a los nuevos engines de los browser en los que trabajan Mozilla, Google o Microsoft, pero aun así me parece una buena lectura.

Pocas veces tomo notas de lo que leo, aunque considero que es una muy buena costumbre; pero en este caso en particular lo hice y dejo aquí esas notas,  no si antes recomendar la lectura del libro, ya que mis notas pueden ser confusas o estar incompletas:

Anotaciones:

- El browser dibuja la página conforme va leyendo las instrucciones. Si encuentra una instrucción <Scriptdetiene el dibujado de la pagina hasta que el script se halla parseado y ejecutado.

- En el caso de que las clausula script contenga archivos “src” estos deben bajarse, parsearse y ejecutarse, mientras pasa todo esto, el dibujado de la pagina se encuentra detenido y el usuario se enfrenta a una página en blanco por lo general.

- Siempre que sea posible poner los scripts lo más cerca de la etiqueta final del body y no en el head como generalmente se usa. Así la página se dibujara todo lo posible antes de parsear los scripts.
<html>
<head>
   <title>Script Example</title>
   <link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
  <p>Hello world!</p>
   <--Ejemplo del posicionamiento recomendado de los scripts -->
   <script type="text/javascript" src="file1.js"></script>
   <script type="text/javascript" src="file2.js"></script>
   <script type="text/javascript" src="file3.js"></script>
</body>
</html>

- Tratar de agrupar los scripts en la menor cantidad de archivos js ya que cada llamada para bajar un archivo (cada src) es un http Request al servidor lo que añade overhead.

- Hay una alternativa para cargar archivos que no utilicen el DOM de la pagina (osea que no modifiquen controles o añadan contenido dinámico a la pagina) El IE 4 en adelante y Firefox 3.5 en adelante soporta la clausula Defer (Defferred) para continuar cargando la pagina. El archivo se baja, pero se ejecuta al final (para no bloquear el dibujado de la pagina) en los otros browsers la sentencia se ignora
<script type="text/javascript" src="file1.js" defer></script>

- Los valores literales (nombre que representa variables pero sin la palabra var) son trivialmente más rápidas de leer que las variables (var) éstas son mucho más rápidas de leer que los arrays y estos similar en tiempo de lectura que las miembros de objetos (Recomendación no usar arrays y miembros de objetos mientras sea posible)

- Por el orden de la “cadena de alcance (scope chain)” Las variables local son siempre más eficientes que las globales. Tratar de no usar with, try catch, eval en javascript a menos que sea absolutamente necesario.

- Tratar de no usar funciones o propiedades de los prototipos (en lo que se basan los objetos) y tratar de no usar cadenas de objetos: location.href es más rápido que usar window.location.href que a su vez es más rápido que usar window.location.href.toString().

- Nunca leer directamente la propiedad de un objeto directamente más de una vez, mejor guardarlo en una variable para usarla después NO usar esto:
function hasEitherClass(element, className1, className2){
    return element.className == className1 || element.className == className2;
}
En lugar usar esto (más eficiente)
function hasEitherClass(element, className1, className2){
    var currentClassName = element.className;
    return currentClassName == className1 || currentClassName == className2;
}

- En la mayoría de navegadores es mejor usar innerHTML que DOM puro (createElement, createTextNode, etc) solo es al revés en SAFARI 4 y CHROME 3 y es muy poca la diferencia.

- Siempre es más eficiente guardar en una variable local las propiedades o métodos de elementos DOM si van a utilizar más de una vez en la función. Esto es lento:
function collectionGlobal() {
    var coll = document.getElementsByTagName('div'),
        len = coll.length,
        name = '';
    for (var count = 0; count < len; count++) {
        name = document.getElementsByTagName('div')[count].nodeName;
        name = document.getElementsByTagName('div')[count].nodeType;
        name = document.getElementsByTagName('div')[count].tagName;
    }
    return name;
};
Esto es mucho más rápido
 var coll = document.getElementsByTagName('div'),
        len = coll.length,
        name = '',
        el = null;
    for (var count = 0; count < len; count++) {
        el = coll[count];
        name = el.nodeName;
        name = el.nodeType;
        name = el.tagName;
    }
    return name;
};

- Usar nexttSibling si está disponible y si programamos para IE. Por ejemplo: Esta función se puede usar sin inconveniente en la mayoría de browser con un buen rendimiento.
function testNextSibling() {
    var el = document.getElementById('mydiv'),
        ch = el.firstChild,
        name = '';
    do {
        name = ch.nodeName;
    } while (ch = ch.nextSibling);
    return name;
};
Sin embargo esta alternativa es mucho más rápida en IE (en IE6 16 veces y en IE7 10.5 veces más rápida que la anterior)
function testChildNodes() {
    var el = document.getElementById('mydiv'),
        ch = el.childNodes,
        len = ch.length,
        name = '';
    for (var count = 0; count < len; count++) {
        name = ch[count].nodeName;
    }
    return name;
};

- (Existen dos métodos en DOM que se disparan cada vez que cambiamos características de elementos DOM y son computacionalmente costosos; el primero es reflow que recalcula el árbol de elementos de una página cada vez que los cambios afecten la geometría de elementos o distribución dentro de la pagina y el otro es repaint que dibuja los elementos de la pagina. Cambiar el fondo de un div, por ejemplo, dispara repaint pero no reflow, los elementos ocultos solo afectan repaint, no reflow. Todo es se nota más en browser viejos) Se puede optimizar el re-dibujado (repaint) de la pagina cuando se manipulan directamente propiedades de elementos DOM utilizando el atributo cssText de estos elementos En lugar de hacer esto:
var el = document.getElementById('mydiv');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';
Mejor esto:
var el = document.getElementById('mydiv');
el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;';
e incluso es mejor, más limpio y mantenible tener una hoja de estilo y cambiarla:
var el = document.getElementById('mydiv');
el.className = 'active';

- El reflow se puede optimizar si primero sacamos del árbol de elementos de la pagina, el elemento que deseamos modificar esto se puede lograr si utilizando tres aproximaciones:
1. Ocultando el elemento (elemento.style.display = 'none';) hacemos los cambios y luego lo volvemos visible (ul.style.display = 'block')
 2. Clonar elemento, realizar los cambios sobre el elemento clon y luego usar replaceChild para actualizar los cambios con el clon.
3. Por último haciendo uso de la sentencia similar a esta var fragment = document.createDocumentFragment(); para añadir elementos y luego usar elemento.appendChild(fragment); para añadir los cambios, esto permite que los elementos se agreguen fuera del árbol “en vivo” de la pagina. Esta es la recomendada por el libro.


Estas son mis notas del libro, ojala y sirvan para mejorar nuestras implementaciones; insisto en que es mejor leer el libro que un "resumen" tan a grosso modo como este.

24 mar 2012

Ataque XSS

Utilizando los dos post anteriores (Controlando el Control y Tiempo de Diseño en Controles) podemos ejemplificar como funciona unas las vulnerabilidades propias de las aplicaciones Web: el XSS  Cross-site scripting  (le ponen la x para que no se confunda con CSS).

El XSS consiste básicamente en inyectar javascript malicioso aprovechando las debilidades de los sitios que lo permitan, no se deben tomar a la ligera por cuanto puede llegar a se muy dañinos  Tomando como referencia la Wikipedia hay varias formas de llevar a cabo este tipo de ataque, pero para nuestro ejemplo nos centraremos en el ataque directo, que normalmente consiste en inyectar código en un textbox cuyo dato posteriormente se mostrará, por lo que al dibujar el contenido del control se convierte en un script que se ejecuta dentro de la pagina.

Tomemos la pagina de prueba de nuestro tutorial de creación de controles.


Como se puede apreciar consiste en un textbox, un botón y un label. Lo que escribimos en el TextBox al hacer clic sobre el Botón se mostrará en el Label. Imaginemos una pantalla donde el usuario digita algunos datos y otra donde estos se muestran solo como lectura, la mecánica es similar.

Que pasa si en la caja de textto incluimos algo como
<script>alert ('Vulnerablidad XSS'); </script>.

Así como esta nuestro control (sin utilizar un UpdatePanel) pasaría lo siguiente:
Como se puede ver el ASP.Net incorpora un mecanismo por medio del cual se validan las entradas que realizan los usuarios. Esta activada por defecto, pero es tan sencilla de deshabilitar, como agregar la directiva validateRequest="false" a la página o por medio del archivo de configuración para todo un sitio.

Supongamos que esta barrera esta de habilitada o que fue superada y el script logró ser introducido. ¿Que sucedería? Pues que nuestro label personalizado como cualquier Label de .Net se dibujará como un elemento HTML tipo span y el script inyectado se ejecutaría.


El riesgo es bastante alto. Imaginemos que el script ingresado hubiese sido:
while(1)alert("Este mensaje saldra indefinidamente");

El sistema ingresaría en ciclo infinito de mensajes. Por supuesto que hay script mucho más peligrosos, con fines más específicos, pero ha modo de ejemplo con esto basta para darnos cuenta que nuestro control, nuestro Label personalizado, debe contar con alguna forma de lidiar con este tipo de ataque.

La respuesta está en  HttpUtility.HtmlEncode del namespace System.Web, por medio de este prevenimos que elementos propios de script o manipulaciones de DOM como tratar de incluir iframes, divs, etc. se puedan ejecutar, ya que parsea los caracteres especiales en códigos html y al final los muestra como simple texto.

Al final del método Render de nuestro control personalizado, podemos modificar el finally para que quede de la siguiente manera
    finally
    {
        Text = HttpUtility.HtmlEncode(Text);
        base.Render(writer);
    }

Una vez hecho esto, ejecutamos de nuevo las pruebas de ataques XSS para demostrar que, por lo menos con esta ajuste, ya estamos mejor preparados.

Como desarrolladores de aplicaciones Web debe estar conscientes de la existencia de este tipo de ataques, para poder preparar nuestras aplicaciones para lidiar con ellos.

26 abr 2010

__doPostBack

ASP.Net 2.0. Me imagino que se habrán dado cuenta de que en las páginas de asp.net se implementa un curioso metodo llamado __doPostBack, de hecho nosotros lo podemos utilizar dentro de las funciones del lado del cliente de los controles de .Net como por ejemplo en el OnClientClick de un botón. Pero ¿que es lo que hace? y ¿cómo lo hace?

Básicamente realiza un submit de la página, ó sea, dispara el postback, como claramente indica su nombre. El método recibe dos parámetros: __EVENTTARGET y __EVENTARGUMENT El primero de los cuales recibe el nombre del control que dispara el postback y el segundo el método de dicho control.

Ahora, ¿cómo hace esta función para hacer el postback? El código que se genera automáticamente es mas o menos este:
< input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value="" / >
< input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value="" / >

function __doPostBack(eventTarget, eventArgument) 
{
    if (!theForm.onsubmit || (theForm.onsubmit() != false)) 
    {
        theForm.__EVENTTARGET.value = eventTarget;
        theForm.__EVENTARGUMENT.value = eventArgument;
        theForm.submit();
    }
}
Como se puede ver, son simplemente dos textbox ocultos y una función que los utiliza para el disparar el submit. Como lo dije antes, este código es generado automáticamente siempre y cuando haya al menos un control en la página que tenga al atributo “AutoPostBack” en True. Si lo queremos es usar la funcion sin que haya controles con el AutoPostBack en True, pues manualmente ponemos el código anterior en la página y listo.

Ahora bien, nosotros podemos hacen uso del método con los parámetros vacíos __doPostBack("",""); Esto dispara el submit y se hubiera algún método del lado del servidor que no se hayan ejecutado (por tener el AutoPostBack en False, por ejemplo) entonces ahora también se dispararán. Este método se usa en los treeview que no disponen de método de postback, cuando se utilizan con checkboxes, para que cuando hagan click en alguno de ellos se ejecute el método postback correspondiente.

Pero hay algo más. La realización de un simple Submit con controles ocultos significa que podemos capturar los valores de los parámetros(valores del los controles ocultos) del lado del servidor y por ende, ejecutar lo que nos parezca más conveniente en determinadas circunstancias. Así por ejemplo podemos hacer una función javascript como esta:
function MiPostBack ()
 {
     var o = window.event.srcElement;
     if ( o.tagName == "INPUT" && o.type == "checkbox")
     {                   
          __doPostBack("MiControl","MiEvento");
      } 
} 
La cual la podemos usar en el OnClick de un treeview para indicar de que si hace clic sobre un checkbox del treeview haga submit con estos parámetros. Y en el método Page_Load del lado del servidor tener algo similar a esto
string controlName = Request.Params.Get("__EVENTTARGET");
string controlEvent = Request.Params.Get("__EVENTARGUMENT");
if ((controlName=="MiControl")&&(controlEvent=="MiEvento"))
{
   UnMetodoCualquiera();
}
Así podemos ejecutar UnMetodoCualquiera(); cuando se produce un clic en alguno de los checkboxes del treeview (esto se realizó asi en un proyecto real para evitar que cuando se marcaba un checkbox padre se dispara el postback por cada hijo que también se marcaba automáticamente). Para mi esto abre más de una posibilidad para usar el __doPostBack cuando requiere ejecutar código del servidor.

4 ago 2009

Javascript desde C#

Hace poco necesitaba abrir una ventana emergente luego de dar clic en botón en una página aspx, he inmediatamente después re-direccionar la pantalla "padre" a otra dirección. Para cumplir estos objetivos se me ocurrió registrar un script en el evento del botón (obviamente después de ejecutar/procesar lo que tenía que hacer del lado del servidor) lo cual me quedó mas o menos así:
...

    StringBuilder script = new StringBuilder();
    script.Append(" &lt; script language= ");
    script.Append(Convert.ToChar(34));
    script.Append("javascript");
    script.Append(Convert.ToChar(34));
    script.Append("&gt; ");
    script.Append("window.open(");
    script.Append(Convert.ToChar(34));
    script.Append("PaginaPopup.aspx?Parametro1=");
    script.Append(intParametro1.ToShortDateString());
    script.Append("&amp;Parametro2=");
    script.Append(intParametro2.ToString());  
    script.Append(Convert.ToChar(34));
    script.Append(",");
    script.Append(Convert.ToChar(34));
    script.Append(Convert.ToChar(34));
    script.Append(",");
    script.Append(Convert.ToChar(34));
    script.Append("Width: 400px; Height: 250px; scroll: no; status:yes;");
    script.Append(Convert.ToChar(34));
    script.Append(");");
    script.Append(" window.location=");
    script.Append(Convert.ToChar(34));
    script.Append("PaginaRedireccionar.aspx");
    script.Append(Convert.ToChar(34));
    script.Append(";");        
    script.Append(" &lt; / script &gt;");
        
    ClientScript.RegisterClientScriptBlock(this.GetType(), "popup", 
script.ToString());

...


En este ejemplo usé un StringBuilder para ir creando el script; se utiliza un window.open para abrir la página PaginaPopup.aspx con dos parámetros (Parametro1 y Parametro2) y especificamos las características de la ventana a abrir. Seguidamente usamos window.location para re-direccionar la ventana "padre" a la página PaginaRedireccionar.aspx

finalmente registramos este script por medio del metodo ClientScript.RegisterClientScriptBlock y misión cumplida!

11 may 2009

Cerrar Ventanas

En una página se abren varias ventanas popups para recabar información variopinta, pero deseo que si me salgo de la pantalla que abre esas ventanas, éstas no queden desperdigadas, si no que también se cierren.

Hago en javascript lo siguiente:


var VentanaIndex = 0;
var VentanasHijas = Array();
function cerrarVentanas()
{
if (VentanasHijas.length > 0)
{
for (var n=0; VentanasHijas.length; n++)
{
VentanasHijas[n].close();
}
}
}

function AbrirVentana(sURL, sName, sFeatures, bReplace)
{
VentanasHijas[VentanaIndex] = window.open(sURL, sName, sFeatures, bReplace);
VentanaIndex++;
return VentanasHijas[VentanaIndex];
}


En el html, el encabezado del body colocamos lo siguiente:


body onunload="cerrarVentanas();"


Cada vez que necesitemos abrir una ventana lo hacemos por medio de la función AbrirVentana así incrementamos el arreglo de ventanas que después cerramos si aun están abiertas a la hora de cerrar la pagina padre.

No necesitamos decrementar la variable VentanaIndex cuando cerramos los popups pues javascript no da error si la ventana a cerrar ya no existe.

19 feb 2009

showModalDialog y postbacks

Estábamos utilizando window.showModalDialog para abrir una popUp modal en una aplicación Asp.net, pero nos encontramos con el problema que necesitábamos manejar unos eventos postback dado la carga de algunos controles. Pues acontece que cada postback abre una nueva ventana y que esta es el comportamiento natural de una ventana abierta por medio de window.showModalDialog. La solución encontrada fue añadir la siguiente linea inmediatamente después del tag de <> en el html del popUp modal

< target="_self">

Esta solucion probamos que funciona con IE6, IE7 y Firefox 3.0

Gracias a Roger dono que estuvo lidiando con el problema.

15 ene 2008

evitar doble clic

Se puede usar un javascript, en asp.net 2.0, para evitar el doble clic en un boton, previendo que la pagina haya sido validada del lado del cliente antes de ocultar el boton.
function HiddenBeforeClick()
{
    if (typeof(Page_ClientValidate)=='function')
    {
        if (Page_ClientValidate()== true)
        {
            event.srcElement.className = "Oculto";
        }
    }
    else
    {
        event.srcElement.className = "Oculto";
    }
}
La clase en el .css unicamente oculta el boton.
.Oculto
{
    visibility:hidden;
}