Sirviendo gráficos SVG dinámicamente

miércoles, 3 de diciembre de 2008

Para mostrar imagenes SVG dentro de un html disponemos del tag embed, el cual funciona en los dos navegadores mas usados actualmente, IE6/7 y Firefox3.

Pero ¿Que pasa si las imagenes no estan en el sistema de ficheros de nuestra web? Lo mas normal es disponer de un servicio que nos devuelva la imagen, que para este caso son imagenes SVG.

Este servicio puede ser tranquilamente una página aspx o un handler ashx(http://miservidor/miaplicacion/miservicio.ashx?imagen=1) que nos devuelva el fichero utilizando el siguiente código:

Response.ContentType = "image/svg+xml";
Response.AddHeader("content-disposition",
"inline; filename=imagen.svg");
Response.Write(contenido_svg);
Response.Flush();


En el elemento embed del html la inclusion sería algo así como:
<embed 
src="http://miservidor/miaplicacion/miservicio.ashx?imagen=1"
type="image/svg+xml"
width="300" height="100"
pluginspage="http://www.adobe.com/svg/viewer/install/" />


Pues bien, resulta que eso si funciona correctamente en firefox, ya que parece que hace caso al content-type, pero para no variar en el IE NO FUNCIONA!!!

Lo único que reconoce y pinta adecuadamente son los ficheros con extensión SVG, asi que no hay mas remedio que crear un manejador de peticiones que controle en nuestra aplicación web todas las peticiones con esta extensión y modificar el atributo src de nuestro embed.
<embed 
src="http://miservidor/miaplicacion/miservicio.svg?imagen=1"
type="image/svg+xml"
width="300" height="100"
pluginspage="http://www.adobe.com/svg/viewer/install/" />


Ahora lo que nos queda es especificar a nuestra aplicación web que todas las peticiones SVG pasen por nuestro código en lugar de buscarlos en su sistema de ficheros; y para hacer esto debemos hacer dos pasos:
  1. Crear una clase que implemente de IHttpHandler en el App_Code
  2. Mapear en el web.config todas las peticiones SVG para que las redirija a nuestra clase.

Creación de la clase que implementa IHttpHandler
public class SVGHandler : IHttpHandler{

public void ProcessRequest(HttpContext context){
HttpRequest Request = context.Request;
string imagen = Request.QueryString["imagen"]; //id de la imagen a mostrar
string contenido_svg = MiServicio.ObtenerContenidoImagen(imagen);
HttpResponse Response = context.Response;
Response.ContentType = "image/svg+xml";
Response.AddHeader("content-disposition", "inline; filename=imagen.svg");
Response.Write(contenido_svg);
Response.Flush();

}

public bool IsReusable{ get{return false;} }
}


Mapeo de la extensión svg en el web.config
<httpHandlers>
...
<add verb="*" path="*.svg" type="SVGHandler"/>
...
</httpHandlers>


Y listo!!! ya podemos incrustar imagenes SVG de contenido dinámico, sin tener problemas de compatibilidad entre IE y Firefox.
<embed 
src="http://miservidor/miaplicacion/miservicio.svg?imagen=1"
type="image/svg+xml"
width="300" height="100"
pluginspage="http://www.adobe.com/svg/viewer/install/" />


Finalmente hay que recordar que la extensión SVG la tenemos que tener registrada dentro del IIS para que este acepte las peticiones con esta extensión.

Enlaces relacionados :

- FIN -

Mashup con Silverlight 2

miércoles, 26 de noviembre de 2008

Aqui cuelgo una pequeña presentacion de como Silverlight puede actuar como un Mashup.



Ver en pantalla completa

- FIN -

Paso de parámetros de inicialización a aplicaciones Silverlight 2

viernes, 31 de octubre de 2008

Para incrustar objetos Silverlight en nuestras páginas web tenemos 3 vías:

- Utilizando el fichero silverlight.js
- Utilizando el Tag HTML Object
- Utilizando el control asp.net <asp:silverlight>

Para pasar parámetros de inicialización tenemos que:

- En el tercer caso se dispone de la propiedad InitParameters



<asp:Silverlight ID="Xaml1" runat="server" Source="~/ClientBin/SilverlightApplication2.xap" MinimumVersion="2.0.31005.0" Width="100%" Height="100%" InitParameters="key1=value1,key2=value2,key3=value3nnnnnnnnn" />

- En el segundo caso hay que pasar un tag param con el nombre initParams



<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
<param name="source" value="ClientBin/SilverlightApplication2.xap"/>
<param name="onerror" value="onSilverlightError" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="2.0.31005.0" />
<param name="autoUpgrade" value="true" />
<param name="initParams" value="key1=value1,key2=value2,key3=value3nnnnnnnnn" />

<a href="http://go.microsoft.com/fwlink/?LinkID=124807" style="text-decoration: none;">
<img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight" style="border-style: none"/>
</a>
</object>



Bien, ahora que sabemos como pasar parametros de inicialización, solo nos queda explorar las diferentes formas de obtenerlos.

- Dentro de cualquier parte de la aplicación silverlight, ya sea el app o cualquier página podemos obtenerlos directamente usando:


object miparam = System.Windows.Browser.HtmlPage.Plugin.GetProperty("initParams");
string sParameters = (miparam != null) ? miparam.ToString() : "parametro no encontrado";
//sParameters obtendría el valor "key1=value1,key2=value2,key3=value3nnnnnnnnn"


Dentro de App.xml.cs disponemos del evento Application_Startup, el cual recibe en propiedad InitParams del argumento llamado e un conjunto de claves-valor con el string correctamente convertido en un objeto IDictionary<string, string>

private void Application_Startup(object sender, StartupEventArgs e) {
string p1 = e.InitParams["key1"];
...
this.RootVisual = new Page();
...
}

Nota de diseño:

- Ya que en el método Application_Startup se obtienen los parámetros de inicialización correctamente parseados, podríamos sobrecargar el constructor de las páginas a mostrar para que puedan recibir un objeto diccionario.

private void Application_Startup(object sender, StartupEventArgs e) {
this.RootVisual = new Page(e.InitParams);
}


namespace SilverlightApplication2{

public partial class Page : UserControl {

public Page(){
InitializeComponent();
}

IDictionary<string, string> initParams;

public Page(IDictionary<string, string> parameters){
initParams = parameters;
}

...
}
}


- FIN -

Parte 2: HTML Entities - Encoding / Decoding

martes, 28 de octubre de 2008

Para cerrar el ciclo de codificación y descodificación de entidades HTML; aquí dejo una pequeña función javascript que convierte un buen numero de entidades especiales (concretamente desde la 160 hasta 255 - incluyen acentos) a su codificación html.


function EncodingText(str){
var res = str;
for(var i=160; i < 256; i++){
var newcaracter = "&#" + i + ";";
var oldcaracter = DecodeHtmlEntities(newcaracter);
res = res.replace(oldcaracter, newcaracter);
}
return
res;
}


Enlaces relacionados:

- FIN -

Clave no válida para utilizar en el estado especificado.

lunes, 29 de septiembre de 2008

Por alguna misteriosa razón últimamente cuando abro mis datasets creados hace unos días el vs2005 no es capaz de abrirlos nuevamente y me dice "Clave no válida para utilizar en el estado especificado"








La razón, no se cual; regenerar el proyecto en mi caso no me ha solucionado el problema, así que opte por borrar el fichero DefaultView.SEView, tal como rezaba la solución de un foro:

C:\Documents and Settings\emmerson\Datos de programa\Microsoft\VisualStudio\8.0\ServerExplorer\DefaultView.SEView

El resultado fue que pude volver a editar los datasets visualmente, osea que el problema quedo solucionado, pero aún así esta solución es la de la fuerza bruta!

La respuesta:

Por muy obvio que parezca, en el explorador de servidores de cada conexión que utilizo no guardo los passwords, la solución en algún caso es volver a conectar con la base de datos antes de abrir el dataset en modo visual(doble click sobre el DS :-p ); en todo caso hay que eliminar conexión que habia y volver a crearla con el mismo nombre.... y eureka!!!

- FIN -

Insertar nodos según el orden del XSD

viernes, 26 de septiembre de 2008

Hace poco me tope con un problema insertando nodos en un documento XML (utilizaba el método AppendChild).

Resulta que después de generar el documento lo validaba contra su XSD, y me tope con el problema que generaba los elementos, según el orden de invocaciones al método AppendChild(no de acuerdo a lo que dicta su esquema); por tanto el XML no era válido.

Solución:
Cada nodo tiene información de su esquema y por tanto del elemento en cuestión, solo habría que saber como listar en orden los hijos que lo componen y luego hacer un pequeño algoritmo de inserción.

------------------------------------------
elemento = _doc.CreateElement(elementName);
//se procede a insertar el elemento en la posicion adecuada

//se detecta el orden del nuevo elemento segun su esquema del nodo padre (nodoBase)
XmlSchemaParticle esquemaNodoPadre = ((XmlSchemaComplexType) nodoBase.SchemaInfo.SchemaElement.ElementSchemaType).ContentTypeParticle;
XmlSchemaSequence seqHijos = (XmlSchemaSequence) esquemaNodoPadre;
string[] ordenHijos = new string[seqHijos.Items.Count];
int indice = 0;


foreach(XmlSchemaObject obj in seqHijos.Items){
XmlSchemaElement elem = (XmlSchemaElement) obj;
ordenHijos[indice++] = (elem.Name == null) ? elem.RefName.Name : elem.Name;
}


//se inserta el nuevo nodo en la posicion adecuada
bool insertado = false;
int posicion = 0;
foreach(XmlNode n in nodoBase.ChildNodes){
if(NodoNuevoAnteriorAlActual(ordenHijos, n.Name, elementName)){
nodoBase.InsertBefore(elemento, n);
insertado = true;
break;
}
posicion++;
}


if(!insertado){
nodoBase.AppendChild(elemento);
}
------------------------------------------

Enlaces relacionados:


- FIN -

Entrenamiento SQL Server 2008

martes, 23 de septiembre de 2008

Navegando me he topado con una dirección que tiene un listado con muchos manuales/presentaciones que tratan sobre SQL Server 2008, aquí la dirección:

http://mredison.wordpress.com/2008/09/08/entrenamiento-sql-server-2008/


- FIN -

Generando múltiples filas de un string (split) en SQL Server

miércoles, 27 de agosto de 2008

Las funciones sp_executesql y execute de t-sql nos permiten generar consultas dinámicamente concadenando textos, algo así se hace generalmente cuando se hace un "select in". Por ejemplo de marcas que tengan unos determinados productos, pero cuyo listado proviene de un conjunto de checks marcados desde una página web.

La consulta antes expuesta debería ser algo así;
select * from brands where brand_id in
(select brand_id from ProductsBrands where product_id in (12,13,14,77,121,598))

Ya que la parte de los identificadores de productos es variante muchos desarrolladores de t-sql optan por concadenar texto y ejecutar sus consultas con los comandos antes mencionados; este tipo de consultas no se pueden optimizar o mejorar mediante las statistics de SQL Server.

declare @listado nvarchar(1000)
set @listado = '10,11,12,13,14,15'

declare @sql nvarchar(1000)
set @sql = 'select * from brands where brand_id in (select brand_id from ProductsBrands where product_id in ( ' + @listado + ' ))'

EXEC(@sql)

Sin embargo existe una posibilidad de generar un conjunto de filas dado un string que contenga todos los identificadores de productos mediante la creación de una función definida por el usuario que devuelva una tabla (es decir un split de un string que devuelva un conjunto de filas con una columna que se pueda utilizar dentro de una "select in" sin necesidad de concadenar textos).


CREATE FUNCTION [dbo].[FnSplitTable]
(@Array varchar(1000),@separator char(1))
RETURNS @table_variable TABLE (col1 nvarchar(1000))
AS
BEGIN
declare @separator_position int
-- almacena el valor de cada vuelta
declare @array_value varchar(1000)
set @array = @array + @separator
-- recorre mientras haya un caracter separador
while patindex('%' + @separator + '%' , @array) <> 0
begin
-- se ubica el separador
set @separator_position = patindex('%' + @separator + '%' , @array)
-- se extrae el valor
set @array_value = substring(@array, 0, @separator_position)
-- se acorta la cadena de caracteres de busqueda
set @array = stuff(@array, 1, @separator_position, '')
-- se almacena en la tabla de respuesta
insert into @table_variable select @array_value as col1
end

RETURN
END



Finalmente nuestra consulta quedaría de la siguiente manera:

declare @listado nvarchar(1000)
set @listado = '10,11,12,13,14,15'


select * from brands
where brand_id in
(select brand_id from ProductsBrands where product_id in (
select col1 from dbo.FnSplitTable(@listado,',')))


Esta consulta si pasa por el proceso de las estadísticas y permite mejorar su rendimiento.


Enlaces relacionados


- FIN -

asp:repeater OnItemCommand no funciona (no se ejecuta)

jueves, 21 de agosto de 2008

Durante la creación de un formulario de búsqueda, con filtros listados en forma de grid, además claro de los resultados; utilicé un control Repeater con un UpdatePanel para recargarlos usando Ajax en ambos casos.

El problema:
El evento OnItemCommand del asp:repeater no se ejecutaba nunca.

Escenario:
En el Page_Load se cargaba los datos del Repeater únicamente en la primera carga (!Page.IsPostBack)

La solución:
El hecho es que para que este evento se lanze, el control Repeater debe tener datos, asi que la carga del Repeater se ha de hacer siempre en el evento Page_Load independientemente si es postback o no; si aún asi, no se desea esto, la otra solución es activar el viewstate del updatepanel.

<asp:UpdatePanel ID="updatePanelFiltro" EnableViewState="true" runat="server" >

No esta demás recalcar que los eventos a controlar asíncronamente han de estar dentro de la sección de Triggers del UpdatePanel llamado en este caso updatePanelFiltro.


<asp:AsyncPostBackTrigger ControlID="RepeaterFiltro" EventName="ItemCommand" />


- FIN -

HTML Entities - Encoding / Decoding

miércoles, 20 de agosto de 2008

Desde la página asp.net disponemos de dos métodos para codificar y descodificar un texto:

  • Server.HtmlEncode("canción")
  • Server.HtmlDecode("canci&oacute;n")
Desde javascript, para decodificar un texto con las entidades Html codificadas no existe ninguna función por defecto, por tanto se puede usar la siguiente función :


function DecodeHtmlEntities(str){
try{
var txt=document.createElement('textarea');
txt.innerHTML = str;
return txt.value;
}catch(e){
return str;
}
}

Aunque por otro lado javascript dispone de las funciones escape y unescape para codificar/descodificar urls.

Enlaces relacionados:

- FIN -

Llamadas dinámicas(callbacks) dentro de un asp:Repeater

jueves, 14 de agosto de 2008

Hay ocasiones en las que deseamos presentar un listado de elementos, utilizando un control Repeater y poder por ejemplo eliminar un elemento del listado, independiente del resto a través de su identificador. El siguiente texto explica como realizar esto.

La página a de implementar la interfaz IPostBackEventHandler


public partial class MiPaginaASPX : System.Web.UI.Page, IPostBackEventHandler {


Esta interfaz nos obliga a implementar el método RaisePostBackEvent, este será el que atienda las llamadas desde el cliente; recibe un parámetro en forma de cadena de texto (en este caso el identificador del registro).


public void RaisePostBackEvent(string eventArgument){
//lógica de negocio....
DatosManager.BorrarRegistro(eventArgument);
//lógica de negocio....
}

Finalmente para generar el código javascript que llame al evento RaisePostBackEvent de la página se ha de utilizar el método GetPostBackEventReference disponible dentro del ClientScript.

public string getRemoveImage(object obj) {
ShortView r = (ShortView)obj;
string evento = Page.ClientScript.GetPostBackEventReference(this, r.Id);
string res = "<a href=\"#\" onclick=\"" + evento + "\"><img src=\"/Resources/icons/borrar.png\" border=\"0\" /></a>";
return res;
}

La llamada al método getRemoveImage se realiza desde dentro del ItemTemplate:

....
<td><%# getRemoveImage(Container.DataItem)%></td>
......

No esta demás decir que esta es solo una de las posibles soluciones.

- FIN -

{"Error genérico en GDI+."}

viernes, 25 de julio de 2008

Generando imagenes dinámicamente me tope con el error :

{"Error genérico en GDI+."}

Como se puede observar el mensaje es "clarísimo" :-p ; bueno este fallo me daba cuando intentaba grabar la imagen en una corriente de salida.


public void WriteImagen(System.IO.Stream salida){
Bitmap res = new Bitmap(......);
.......
res.Save(salida, System.Drawing.Imaging.ImageFormat.Png);
res.Dispose();
}


Una solución para evitar este tipo de problemas es usando un MemoryStream, de la siguiente manera.


public void WriteImagen(System.IO.Stream salida){

Bitmap res = new Bitmap(......);
.......
MemoryStream ms = new MemoryStream();
res.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
ms.Seek(0, SeekOrigin.Begin);
ms.WriteTo(salida);
res.Dispose();

}

Y colorin, colaro esta solución a funcionado!!!

- FIN -

Formatos numéricos según la cultura regional

jueves, 24 de julio de 2008

Trabajando con formatos numéricos, nos damos cuenta que mientras en un pais se utiliza la coma como separador decimal, en otros se utiliza como separador de miles y lo mismo sucede con el punto.

Este tipo de problemas viene dado normalmente, a que estamos consumiendo servicios (datos) de otros servidores que tienen formatos númericos, de fechas.... distintos al nuestro (dandose este caso incluso entre servidores internos de una misma organización, servidor web, de base de datos...).

Tambien sucede que durante la fase de desarrollo de las aplicaciones nuestro sistema esta en "Español" y cuando subimos a producción a veces suele estar en "Ingles" o viceversa; esto suele dar muchos problemas de refactorización de código.



latitud = double.Parse(valorString);



En .NET, una buena solución para este último punto, es forzar la utilización de un formato numérico perteneciente a una "Culture" que nosotros definamos.



NumberFormatInfo numberInfo = CultureInfo.GetCultureInfo("es-ES").NumberFormat;
Latitud = double.Parse(valorString, numberInfo);



De esta manera, por ejemplo, evitaremos posibles fallos de conversión de formatos numericos cuando nuestra máquina de desarrollo este en "Español" y el framework del servidor en "Ingles".


- FIN -

error document.body.offsetHeight no funciona

miércoles, 16 de julio de 2008

Cuando determinadas funciones y propiedades de Javascript no funcionan adecuadamente como por ejemplo si se espera que document.body.offsetHeight nos de un valor y en su lugar devuelve un cero, es por que probablemente estemos colocando la siguiente directiva en la página:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">


La solución es simple.... hay que quitarla o comentarla.

Error un tanto raro, que me ha pasado utilizando paginas maestras y controles de usuario de .NET

P.D: No esta demás advertir que al quitar la declaración del DOCTYPE se puede dar problemas al usar los estilos dentro de elementos DIV que utilicen por ejemplo la propiedad position.

- FIN -

Desarrollo .NET

jueves, 10 de julio de 2008


Dirección de mi blog Desarrollo.NET