Jump list & Internet Explorer 9

Revisando las novedades por parte de Microsoft en el PDC 2010 hubo un apartado donde hablaban de las mejoras introducidas en la nueva versión de Internet Explorer (aún disponible sólo en fase beta). Una de las características que me llamó la atención fue la posibilidad de integrar Internet Explorer 9 con la barra de tareas y el menú de inicio de Windows 7. Con la nueva versión del explorador, existe la posibilidad de anclar una página web tanto a la barra de tareas como al menú de inicio de nuestro sistema operativo simplemente arrastrando el tab de la página web hasta la barra de tareas

o bien si pulsamos ALT sobre la ventada del explorador y seleccionamos Herramientas + Agregar sitio al menú de inicio podemos crear un enlace directo al sitio web.

Pero lo curioso no es para nada la posibilidad de poder enlazar nuestros sitios preferidos a estos apartados sino la funcionalidad extra que podemos ofrecer con ello. Para verlo directamente con dos claros ejemplos de la funcionalidad que os hablo voy a mostrar qué ocurriría con las páginas de Facebook y Twitter al ser ancladas:

Como podemos ver, en ambas páginas nos ofrecen un conjunto de funcionalidades adicional acorde al sitio, la cual es posible customizar 😀 Para ello tenemos dos formas de hacerlo.

Etiquetas meta en la cabecera de nuestra web

La forma más sencilla de añadir estas tareas a nuestro sitio web sería utilizando las siguientes etiquetas meta dentro del apartado head de nuestro HTML:

<!-- Jumplist --> 
  
    <meta name="application-name" content="Return(GiS); - Blog about technologies" />
    <meta name="msapplication-tooltip" content="Return(GiS);" />
    <meta name="msapplication-starturl" content="./" />
    <meta name="msapplication-task" content="name=My Blog;action-uri=http://www.returngis.net;icon-uri=/favicon.ico" />
    <meta name="msapplication-task" content="name=GiS on Linkedin;action-uri=http://www.linkedin.com/in/giselatorresbuitrago;icon-uri=http://www.linkedin.com/favicon.ico" />
    <meta name="msapplication-task" content="name=GiS on Facebook;action-uri=http://www.facebook.com/returngis#!/pages/ReturnGiS/161658947178444;icon-uri=http://www.facebook.com/favicon.ico" />
    <meta name="msapplication-task" content="name=GiS on Twitter;action-uri=http://www.twitter.com/0GiS0;icon-uri=http://www.twitter.com/favicon.ico" />
<!-- End Jumplist -->

Si nos fijamos en el código anterior, vemos que cada tarea asociada utilizará el nombre msapplication-task y contendrá los siguientes valores dentro de content:

  • name: Nombre a mostrar dentro del Jump list para ese elemento.
  • action-uri: Nos indica la url a la cual nos dirigiremos si accionamos ese elemento.
  • icon-uri: Será la dirección donde está alojado el icono asociado con la tarea.

Si ancláramos mi blog utilizando Internet Explorer 9 conseguiríamos las siguientes tareas:

Customizando el Jump list con Javascript

Por otro lado, otra opción que tenemos es hacer uso de los nuevos métodos en javascript que nos ofrece IE 9 para añadir y eliminar elementos dinámicamente, crear secciones personalizadas, etcétera.

Espero que sea de utilidad 🙂

Más información

¡Saludos!

Tips para evitar SQL Injection

En un gran porcentaje, la mayoría de nuestras aplicaciones necesitan acceder a las bases de datos para realizar consultas, actualizaciones e incluso eliminaciones de nuestros registros… El problema es cuando alguien desde fuera intenta hacer esas tareas por nosotros 😉

Voy a proponeros algunos tips a la hora de tratar los valores ajenos a nosotros desde el lado del servidor, para evitar este tipo de catástrofes. Por otro lado, cualquier tip adicional será bienvenido 😀

Convierte siempre el valor a su tipo correspondiente

Si por ejemplo estamos esperando un valor de tipo numérico, deberíamos intentar parsear este valor a dicho tipo para asegurarnos de que no incluye texto adicional:


var id = Request.QueryString["id"];

int result;

if (Int32.TryParse(id, out result))
    //Do things

Parametrizar las consultas SQL

Un error muy común es hacer uso de los valores que nos llegan sin especificar ningún tipo para el mismo. Algo parecido a esto:

var id = Request.QueryString["id"];
var query = "SELECT * FROM HOUSES WHERE ID=" + id;

Para evitarlo, podemos parametrizar las sentencias SQL y poder especificar de esta manera el tipo que estamos esperando para cada parámetro.


var objConnection = new OleDbConnection(strDbConnectionString);

objConnection.Open();

const string query = "SELECT * FROM HOUSES WHERE ID=?";

var objCommand = new OleDbCommand(query, objConnection);
var id = new OleDbParameter("@idParam", OleDbType.Integer) { Value = id};
objCommand.Parameters.Add(id);

var objReader = objCommand.ExecuteReader();;

Usar una cuenta con permisos restringidos a la base de datos

Otro dato importante a tener muy en cuenta es asegurarnos de que la cuenta de usuario utilizada por nuestra aplicación tiene los permisos necesarios para poder acceder y/o modificar unos datos concretos pero también que sea lo suficiente restrictiva para no alterar otro tipo de datos.

No mostrar al usuario la información de error generada por la base de datos

En muchos casos los mensajes de error pueden ser lo suficientemente descriptivos como para que el usuario se percate de información acerca de la estructura de la base de datos.

Rechazar las peticiones con caractéres sospechosos

Carácter especial Significado SQL
; Delimitador de consultas.
Carácter delimitador de cadena de datos.
Comentario.
/* */ Delimitadores de comentario. El texto entre /* y */ no es evaluado.
xp_ Se utiliza en el inicio del nombre de procedimientos almacenados extendidos de catálogo, como xp_cmdshell.

Espero que sea de utilidad 🙂

¡Saludos!

PDC 2010 online y en directo 28 y 29 de Octubre

Como cada año, desde el campus de Microsoft en Redmond, tiene lugar la Conferencia de Desarrolladores Profesionales (PDC). En este 2010 se celebrará los días 28 y 29 de Octubre.

Si bien otros años hemos podido seguir el evento de manera online, este año Microsoft Ibérica nos invita a sus oficinas para ver la retransmisión de la sesión inaugural 😀 ¡Regístrate a través de este enlace!

Si por el contrario Pozuelo de Alarcón te queda lo bastante lejos como para no poder acompañarnos os invito a seguir el evento de manera online en la siguiente dirección.

En cualquier caso, el acontecimiento comenzará a las 18:00 (hora peninsular española) el 28 de Octubre. Para más información os adjunto la agenda del evento.

Por otro lado, para estar al corriente de las ultimas noticias, podemos seguir la cuenta @PDCEvent de Twitter y utilizar el hashtag #PDC10 para enviar nuestros comentarios.

¡Saludos!

Fault domains & Update domains

Ahora que poco a poco vuelvo a retomar mi vida, mi trabajo y la parte de Windows Azure :P, me gustaría hablaros de estos dos términos utilizados dentro de la nube. Si bien la plataforma de Windows Azure nos permite abstraernos de la infraestructura es necesario conocer algunos aspectos de su funcionamiento. Lo primero que debemos tener claro es qué es un role. Un role es en realidad una aplicación dentro de nuestro proyecto Cloud Service con una configuración personalizada. Como cualquier solución, podemos crear varios proyectos de distinto tipo. Con la plantilla de Visual Studio podemos ver claramente cuáles son los proyectos disponibles a día de hoy:

Por cada uno de esos roles podemos elegir el número de instancias que queremos desplegar en la nube, lo que supondrá una máquina virtual por instancia. Para modificar este valor, nos ubicamos en el proyecto de Cloud Service y, dentro de la carpeta Roles, hacemos doble clic en el rol que queramos configurar.

Ahora bien, cuando tenemos dos o más instancias de uno o más roles Windows Azure, más concretamente una parte llamada Fabric Controller, distribuye las instancias en diferentes racks en una determinada posición para conseguir la mayor disponibilidad del servicio en caso de fallo físico, lógico, debido a actualizaciones de los sistemas e incluso a nuevos deploys. Dicho esto ¿Qué es un Fault Domain y un Update Domain?

Fault Domain

Este término puede ser relacionado con el rack físico donde se alojan las instancias de los distintos roles. El objetivo del Fabric Controller es distribuir las instancias en estos faults domains a través de un algoritmo con el fin de no repetir la misma ubicación para instancias de un mismo rol en un mismo sitio físico. Por otro lado, si uno de los fault domains presenta algún problema de hardware que impida a una de nuestras instancias funcionar con normalidad, tendríamos el resto de ellas distribuidas por los otros fault domains con el objetivo de seguir prestando servicio. Cuando el Fabric Controller se percate de este incidente actuará para reubicar la instancia de nuestro rol en un fault domain operativo.

Update Domain

Si bien un fault domain puede relacionarse con una unidad física (un rack) donde quedan almacenados diferentes roles, procurando no duplicar varias instancias de uno de los integrantes, el término update domain trata de una unidad lógica donde Windows Azure consigue agrupar un conjunto de instancias de distintos roles con el fin de poder realizar una actualización progresiva sin anular el servicio de un role. Dicho de otra forma: El conjunto de instancias que forman un update domain consta de varios roles distintos con el fin de poder actualizar los mismos dejando operativos el resto de instancias de dichas aplicaciones. Podemos configurar el número de update domains a través del archivo ServiceDefinition.csdef. El número por defecto es 5 si no especificamos ningún valor.
<ServiceDefinition name=»CloudService» upgradeDomainCount=»2″
xmlns=»http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition»>

En este caso el Fabric Controller procurará tener las instancias de manera equilibrada entre el número de dominios de actualización para seguir manteniendo el servicio activo.


(Click para agrandar)

Si nos fijamos en la imagen anterior, podemos ver que tenemos 4 fault domains y 4 roles distintos con 4 instancias ubicadas en los distintos racks sin repetición en cuanto a la posición dentro del fault domain y el update domain. ¡Saludos!

Cómo enviar peticiones utilizando OAuth: Firmando peticiones

En el mes de Mayo intenté mostrar cómo podíamos recuperar los tokens de autenticación, tanto para Twitter como para Yahoo, con el objetivo de hacer uso del protocolo abierto OAuth. A pesar de todo, es posible que aún tengamos dudas sobre cómo realizar nuestras peticiones y nos hagan falta algunos tips a tener en cuenta 🙂 En este post me centraré en Twitter.

En el apartado de documentación podemos ver una barra lateral derecha donde se nos muestran todas las acciones disponibles, la URL a la cual debemos realizar la petición y el HTTP Verb necesario:

 

Para este ejemplo, voy a mostrar cómo sería para el caso más común que es actualizar
el estado
.

En primer lugar, voy a crear una aplicación de consola donde vamos a llamar a
un wrapper creado para interactuar con la API y que a su vez utilizará la clase
implementada para el protocolo OAuth en el
post anterior
.

using System;
using TwitterWrapper;

namespace TwitterClient
{
    class Program
    {
        static void Main()
        {
            const string status = "Testing my twitter client with #OAuth and ¡special characteres! ¿*?";

           var result = new Twitter().UpdateStatus(status);

            Console.WriteLine("The status was successfully updated!");
            Console.WriteLine("ID: {0}", result.Id);
            Console.WriteLine("Date: {0}", result.Created);
            Console.WriteLine("Status: {0}", result.Text);

            Console.ReadLine();
        }
    }
}

Como podéis ver, he creado una clase llamada Twitter desde la cual llamo al método UpdateStatus. El objetivo del mismo es reunir los parámetros necesarios para poder construir la petición y que posteriormente sea firmada.

using System.IO;
using System.Net;
using System.Web;
using System.Xml.Serialization;
using OAuthTools;
using TwitterWrapper.Resources;

namespace TwitterWrapper
{
    public class Twitter
    {
        private const string ConsumerKey = "yourConsumerKey";
        private const string ConsumerSecret = "yourConsumerSecret";
        private const string AccessToken = "yourAccessToken";
        private const string AccessTokenSecret = "yourAccessTokenSecret";

        public Status UpdateStatus(string status, WebProxy webProxy = null)
        {
            var query = string.Format("http://api.twitter.com/1/statuses/update.xml?status={0}", HttpUtility.UrlEncode(status));
            var request = new OAuth(ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret);
            var result = request.PostResource(query);

            return Serialize(result);
        }

        public Status Serialize(string xml)
        {
            var xmlSerializer = new XmlSerializer(typeof(Status));
            var reader = new StringReader(xml);
            var status = (Status)xmlSerializer.Deserialize(reader);

            return status;
        }
    }
}

Una vez que la petición ha sido realizada, intentamos almacenar el resultado
XML dentro de la clase Status gracias a XmlSerializer y los XmlAttributes en los atributos que
necesitemos almacenar en nuestra aplicación.

Tip: Es muy importante hacer uso de
HttpUtility.UrlEncode para recuperar los valores Unicode del
status que acabamos de pasarle al método. De lo contrario algunos caracteres especiales podrían interpretarse
de un modo erróneo y serán omitidos de la petición
. Por ejemplo, uno de
los casos que me estaba ocurriendo, es al intentar introducir un hashtag. Al no
pasar esta información en formato Unicode, si intentamos transformar el string
en Uri interpretará el símbolo # y todo lo que venía detrás como un ancla ;)

Si bajamos una capa más en el código, llegamos a la clase OAuth pero con un
nuevo método para las peticiones POST llamado PostResource.

/* POST Requests */
public string PostResource(string url)
{
    var uri = new Uri(url);
    var parameters = GetOAuthParameters(HttpMethod.Post, url, HttpUtility.ParseQueryString(uri.Query), _accessToken, _accessTokenSecret);
    url = string.Format("{0}://{1}{2}", uri.Scheme, uri.Authority, uri.AbsolutePath);
    var oAuthRequest = new OAuthRequest(parameters, url, HttpMethod.Post, proxy: _proxy);
    var queryParameters = HttpUtility.ParseQueryString(uri.Query);
    var response = oAuthRequest.GetResponse(queryParameters);

    return response;
}

En esta función vamos a formatear el parámetro de entrada (url de Twitter +
 el nuevo estado) dentro de la clase Uri y posteriormente vamos a recuperar los
parámetros para el protocolo OAuth que irán incluidos en la cabecera.

Una vez que tenemos generados estos parámetros (nonce, timestamp, la firma,
etc.) podemos hacer la llamada a través del método POST utilizando la clase
oAuthRequest. Crearemos una instancia de dicha clase con los
parámetros recopilados hasta el momento y por último llamaremos a GetResponse
para obtener el resultado.

using System;
using System.Text;
using System.Collections.Specialized;
using System.Net;
using System.IO;

namespace OAuthTools
{
    public class OAuthRequest
    {
        private readonly HttpWebRequest _request;
        private static byte[] _content;

        public string GetResponse(NameValueCollection queryParameters = null)
        {
            try
            {
                /*POST method*/
                if (queryParameters != null)
                {
                    var sb = new StringBuilder();
                    for (var i = 0; i < queryParameters.AllKeys.Length; i++)
                    {
                        var key = queryParameters.AllKeys[i];

                        sb.AppendFormat("{0}={1}", Uri.EscapeDataString(key), Uri.EscapeDataString(queryParameters[key]));

                        if (i < queryParameters.Count - 1)
                            sb.Append("&");

                    }

                    _content = Encoding.ASCII.GetBytes(sb.ToString());
                    _request.ContentLength = _content.Length;

                    using (var stream = _request.GetRequestStream())
                    {
                        if (_request.ContentLength > 0)
                            stream.Write(_content, 0, _content.Length);
                    }

                }

                using (var response = _request.GetResponse())
                {
                    var stream = response.GetResponseStream();
                    using (var reader = new StreamReader(stream))
                    {
                        return reader.ReadToEnd();
                    }
                }
            }
            catch (WebException ex)
            {
                //If something is wrong, We'll retrieve the server response.
                /*    HTTP 400 Bad Request
                        o Unsupported parameter
                        o Unsupported signature method
                        o Missing required parameter
                        o Duplicated OAuth Protocol Parameter
                      HTTP 401 Unauthorized
                        o Invalid Consumer Key
                        o Invalid / expired Token
                        o Invalid signature
                        o Invalid / used nonce
                */
                if (ex.Response is HttpWebResponse)
                {
                    using (var reader = new StreamReader(ex.Response.GetResponseStream()))
                    {
                        var result = reader.ReadToEnd();
                        return result;
                    }
                }

                throw;
            }
        }

        //  2. For each parameter, the name is immediately followed by an ‘=’ character (ASCII code 61), a ‘”’ character (ASCII code 34), the parameter value (MAY be empty), and another ‘”’ character (ASCII code 34).
        //  3. Parameters are separated by a comma character (ASCII code 44) and OPTIONAL linear whitespace.
        //  4. The OPTIONAL realm parameter is added
        /// <summary>
        /// The OAuth Protocol Parameters are sent in the Authorization  header the following way:
        /// 1.  Parameter names and values are encoded.
        /// </summary>
        /// <param name="parameters">oAuthParameters</param>
        /// <param name="url"></param>
        /// <param name="method">Http Method</param>
        /// <param name="proxy"></param>
        public OAuthRequest(NameValueCollection parameters, string url, string method, IWebProxy proxy = null)
        {
            ServicePointManager.Expect100Continue = false;
            ServicePointManager.UseNagleAlgorithm = false;

            var header = new StringBuilder();

            header.Append("OAuth ");

            for (var i = 0; i < parameters.Count; i++)
            {
                var key = parameters.GetKey(i);
                header.Append(string.Format("{0}="{1}"", key, parameters[key]));

                if (i < parameters.Count - 1)
                    header.Append(",");
            }

            _request = CreateHttpWebRequest(url, method, proxy);

            _request.Headers["Authorization"] = header.ToString();

        }

        private static HttpWebRequest CreateHttpWebRequest(string url, string method, IWebProxy proxy = null)
        {
            var httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
            httpWebRequest.Method = method.ToUpper();

            if (proxy != null) httpWebRequest.Proxy = proxy;

            if (method == HttpMethod.Post)
                httpWebRequest.ContentType = "application/x-www-form-urlencoded";

            return httpWebRequest;
        }
    }
}

Con esta clase podríamos realizar tanto peticiones GET como POST, donde en
las segundas se envía el cuerpo del mensaje.

Para poder ver el ejemplo completo, he actualizado el proyecto de CodePlex.

Espero que sea de utilidad :D

¡Saludos!

¡Dominio propio!

¡Buenas a tod@s!

Como muchos sabéis, por motivos de salud estoy actualmente de baja (y aburrida a más no poder :(
) y no he sido capaz de publicar de forma habitual como me gustaría.
Mientras tanto, he tenido la oportunidad de poder ampliar tanto mis
conocimientos como mis ganas de seguir aprendiendo más y más y es por
ello que he decidido lanzarme a la aventura de tener un dominio propio :)

En cuanto a las personas que estaban suscritas a través del lector
rss … Estoy teniendo problemas para que se propaguen los nuevos post
desde el nuevo sitio
… La verdad es que no sé lo qué le pasa :( Lamento las molestias que esto haya podido ocasionar. Para remediarlo, solamente sería necesario inscribirse de nuevo en este enlace. La suscripción a través de RSS será la misma para ambos blogs.

Estoy en plena migración de los posts más relevantes. Por otro lado,
he decidido actualizar progresivamente aquellos que quedaron obsoletos
debido a actualizaciones demasiados aceleradas por parte del equipo de
desarrollo pertinente ;) Esto no quiere decir que mi blog de geeks quede atrás pero centraré mi atención en este ya que pretendo que sea el principal tanto en actualizaciones como mejoras.

Como novedades tenemos:

  • Posibilidad de publicar en Twitter los post que os resulten relevantes desde el mismo.
  • Usar el típico “Me gusta” de Facebook para poder referenciar desde esa vía.
  • Interfaz más amigable (espero :) ).
  • Integración con Gravatar.
  • Publicación de otro tipo de posts, como por ejemplo sobre nuevos
    productos adquiridos y mi valoración al respecto (hablamos mucho sobre
    este tipo de cosas en Twitter y es posible que más de una persona esté
    interesad@ o pueda aportar sus experiencias).

Están pendientes más actualizaciones en el sitio y nuevos apartados
que intentaré ir publicando en la medida de lo posible. Cualquier
sugerencia siempre es bien recibida :)

Por último, me gustaría dar las gracias a Asier Marqués por su apoyo y su ayuda en el montaje de la base del sitio, ya que no veía PHP desde que dejé de estudiar :P .

Espero que sea de vuestro agrado y me deis todo el feedback posible.

¡Saludos!

De baja laboral

Buenos días a tod@s,

Quizás muchos de vosotros podéis extrañaros del bajón que ha pegado mi blog en los últimos dos meses. El motivo es mi baja laboral 🙁

Desde el 1 de Julio llevo sufriendo graves dolores de espalda y articulaciones, concretamente en el hombro derecho, que me impide trabajar y poder actualizar mi sitio. Por el momento sigo de baja y esperando unas pruebas y citas médicas para poder valorar mi situación. Tengo muchas ganas de volver por aquí y poder seguir aprendiendo con vosotros pero, por el momento, sólo me queda esperar a que esta situación mejore y poder volver a mi vida normal 🙂

Igualmente os invito a seguirme en Twitter, donde intento mantener mi status y novedades a través del móvil. ¡Espero traer nuevos e interesantísimos post en cuanto pueda!

Gracias por la compresión y por el ánimo que he recibido de muchos de vosotros.

Gisela.

Follow 0GiS0 on Twitter

Gravatar helper para ASP.NET MVC

Para quien no lo conozca, el servicio de Gravatar nos da la posibilidad de poder asociar una imagen a un correo electrónico. De esta manera podremos personalizar nuestros comentarios en todos aquellos blogs, forums, etcétera que soporten el servicio de Gravatar, el cual es muy usado por los usuarios de WordPress entre otros. De hecho, existe la posibilidad de hacer uso del servicio incluso en nuestras propias aplicaciones.

En realidad, es bastante simple: Las URLs que nos facilitan las imágenes de nuestra cuenta en Gravatar no es más que el valor hash de nuestro correo electrónico. Lo único que debemos de tener en cuenta para obtener el hash correcto son 3 puntos importantes:

  1. Eliminar los espacios que pudieran existir en el correo electrónico introducido por el usuario.
  2. Todos los caractéres deben estar en minúsculas.
  3. Nuestro hash debe de obtenerse a través del algoritmo MD5.

Un ejemplo válido para ASP.NET MVC podría ser el siguiente:

using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Web.Mvc;

namespace GravatarMVC.Helpers
{
public static class CustosHelpers
{
public static MvcHtmlString Gravatar(this HtmlHelper htmlHelper, string email)
{
var md5 = new MD5CryptoServiceProvider();
var bytes = Encoding.ASCII.GetBytes(email.ToLower().Trim());
bytes = md5.ComputeHash(bytes);
var result = bytes.Aggregate(string.Empty, (current, b) => current + b.ToString("x2"));

var gravatarLink = new TagBuilder("a");
gravatarLink.Attributes["href"] = string.Format("http://www.gravatar.com/{0}", result);

var image = new TagBuilder("img");
image.Attributes["src"] = string.Format("http://www.gravatar.com/avatar/{0}", result);

gravatarLink.InnerHtml = image.ToString();

return MvcHtmlString.Create(gravatarLink.ToString());
}
}
}

Como podemos ver, lo único que necesitamos es recuperar el hash utilizando la clase MD5CryptoServiceProvider y posteriormente concatenamos cada valor de cada uno de los bytes en formato hexadecimal. Una vez almacenado cada uno de los valores en la variable result, asignamos la misma al final de la URL que solicitará a Gravatar la imagen (http://www.gravatar.com/avatar/HASH) o bien obtendremos la dirección del perfil asociada a ese correo electronico (http://www.gravatar.com/HASH).

En este simple helper para ASP.NET MVC lo único que he implementado es un enlace que contiene a su vez la imagen de Gravatar. Si pulsamos sobre él nos llevará al perfil del usuario:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

<%@ Import Namespace="GravatarMVC.Helpers" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Home Page
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>
<%: ViewData["Message"] %></h2>
<p>
To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">
http://asp.net/mvc</a>.
</p>
<p>
This is my Gravatar image
<%:Html.Gravatar("your@email.com") %>
</p>
</asp:Content>

Espero que sea de utilidad 🙂

¡Saludos!

Mad.Nug & Second.Nug os presenta ¿Qué es App Fabric?

En Mag.Nug estamos abriendo horizontes y qué mejor forma que de la mano de Second.Nug 🙂

Si bien este evento va tener lugar en Madrid el 24 de Junio, vamos a tener la oportunidad de poder seguirlo, a la vez que participar, de manera online. Creemos que es un gran acierto llevar un paso más allá los eventos presenciales con el fin de que más oyentes puedan disfrutar y aprender con estas reuniones.

Por ello, para aquellos que podáis acudir a las oficinas de Microsoft Ibérica en Madrid podéis registraros a través del siguiente link y los que queráis asistir de manera online será necesario inscribiros a través del siguiente enlace.

¡Os esperamos en analógico o en digital! ¡Saludos!