Mar/100
Servicio para localización por IP
Este tema lo tengo pendiente hace ya unos meses, y por fin puedo hacer el post que tenía pensado, así que vamos a ello. Intentaremos explicar cómo realizar nuestro propio servicio para tener la localización de nuestros visitantes a través de su dirección IP, lo cual puede ser muy interesante para filtrar contenidos, mostrar diferentes publicidades, etc…
Para realizar esta tarea, lo que se necesita es una Base de datos actualizada con todas las direcciones IP y su localización, existen varias opciones de pago, pero para este caso vamos a utilizar la versión gratuita GeoLite Country de MaxMind que proporciona un acierto del 99.5% – lo cual es suficiente en la mayoría de los casos – y además, la actualizan cada mes debido a que los ISP van variando la localización de las direcciones IP.
* También existe GeoLite City, pero después de probarla, obtuve unos resultados malos con su nivel de acierto. Si decidís utilizarla, cuidado con el script, porque pesa un “poquito”, paciencia cuando importéis los datos – casi 4 millones de registros – el portátil no fue capaz de ejecutar la consulta, me tiraba un error y tuve que hacerlo desde el PC (¿por fin sirvió para algo el Quad-Core?).
Si no necesitáis aplicar lógica según la localización desde vuestro código servidor, hay varias soluciones vía javascript, yo destacaría la opción del API AJAX de Google con el uso de su propiedad google.loader.ClientLocation.
Bien, para nuestro desarrollo utilizaremos las tecnologías LINQ para el sencillo acceso a datos y WCF para nuestro servicio. No voy a entrar en detalles sobre esto, ya existen muy buenos recursos para introducirse en estas tecnologías.
Creamos un nuevo proyecto de Aplicación de servicios WCF en Visual Studio, yo he cambiado el nombre del ServiceContract por IGeoIp y he definido dos métodos, uno dónde devolveremos el nombre del país del usuario y el otro dónde devolveremos toda la información:
[ServiceContract] public interface IGeoIp { [OperationContract] string GetCountryByIp(string ip); [OperationContract] WcfServiceGeoIp.LocalizacionIp GetInfoByIp(string ip); }
El DataContract lo he sacado a un segundo fichero, en este caso InfoIP.cs y le he definido los dos unicos valores que vamos a devolver al cliente, el código ISO 3166-1 del país y su nombre:
[DataContract] public class LocalizacionIp { [DataMember] public string CountryCode { get; set; } [DataMember] public string Country { get; set; } }
Después implementamos el servicio, que es muy sencillo, simplemente haremos una consulta a la base de datos pasando la dirección en formato double, para lo que nos apoyaremos en el método GetDoubleIp. El servicio quedará así:
public class ServiceGeoIp : IGeoIp { #region Miembros de IGeoIp string IGeoIp.GetCountryByIp(string ip) { double dIp = GetDoubleIp(ip); return WcfServiceGeoIp.Facade.GetCountryByIp(dIp); } WcfServiceGeoIp.LocalizacionIp IGeoIp.GetInfoByIp(string ip) { double dIp = GetDoubleIp(ip); return WcfServiceGeoIp.Facade.GetInfoByIp(dIp); } #endregion private double GetDoubleIp(string ip) { string[] ipSection = ip.Split(".".ToCharArray()); if (ipSection.Length == 4) { // Fórmula para calcular la IP ((A*256+B)*256+C)*256 + D // (A * 256^3) + (B * 256^2) + (C * 256^1) + (D * 256^0) double dIp = ((int.Parse(ipSection[0]) * 256 + int.Parse(ipSection[1])) * 256 + int.Parse(ipSection[2])) * 256 + int.Parse(ipSection[3]); return dIp; } throw new Exception("La IP facilitada no tiene un formato correcto."); } }
Cómo nos advierten los comentarios que nos genera VS en la clase del servicio, si hacemos cambios en los nombres de las clases, tendremos que modificar el web.config, en este caso, el system.serviceModel quedará así:
<system.serviceModel> <services> <service name="WcfServiceGeoIp.ServiceGeoIp" behaviorConfiguration="WcfServiceGeoIp.ServiceGeoIpBehavior"> <endpoint address="" binding="basicHttpBinding" contract="WcfServiceGeoIp.IGeoIp"> <identity> <dns value="http://localizadorip.glopez.es"/> </identity> </endpoint> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/> </service> </services> <behaviors> <serviceBehaviors> <behavior name="WcfServiceGeoIp.ServiceGeoIpBehavior"> <serviceMetadata httpGetEnabled="true"/> <serviceDebug includeExceptionDetailInFaults="true" /> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel>
Sólo nos faltan las consultas a base de datos, que como comentaba anteriormente, en este caso he optado por utilizar LINQ, así que generamos el dbml de nuestra base de datos, si estais utilizando GeoLite Country, sólo contiene una tabla. La clase quedará así:
public static class Facade { static LinqDbDataContext dataContext = new LinqDbDataContext(); public static string GetCountryByIp(double ip) { var countries = from c in dataContext.GeoIPCountryWhois where c.start_long <= ip orderby c.start_long descending select c; GeoIPCountryWhois country = countries.FirstOrDefault(); if (country != null) { return country.country_name; } return string.Empty; } public static LocalizacionIp GetInfoByIp(double ip) { var countries = from c in dataContext.GeoIPCountryWhois where c.start_long <= ip orderby c.start_long descending select new LocalizacionIp { Country = c.country_name, CountryCode = c.country_code }; LocalizacionIp countryInfo = countries.FirstOrDefault(); return countryInfo; } }
Ya tenemos nuestro servicio para localizar a los usuarios por su IP.
Para consumir este servicio es tan sencillo como agregar la referencia al servicio, y utilizar este código de ejemplo desde una página aspx de prueba:
protected void Page_Load(object sender, EventArgs e) { WCFServiceGeoIp.GeoIpClient servicio = new WCFServiceGeoIp.GeoIpClient(); WCFServiceGeoIp.IGeoIp Iservicio = servicio; WCFServiceGeoIp.LocalizacionIp info = Iservicio.GetInfoByIp(GetPublicIp()); Response.Write("Country name: " + info.Country); Response.Write("<br/>"); Response.Write("Country code: " + info.CountryCode); } public static string GetPublicIp() { string ip = HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"]; if (ip == null) { ip = HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"]; } return ip; }
*Si ejecutáis la solución en local, no os podrá reconocer porque capturará localhost en vez de vuestra IP pública.
Mar/100
Configurar el firewall de SQL Azure
Si estamos realizando un desarrollo con acceso a una base de datos de SQL Azure, tendremos que configurar una serie de parámetros para que nos funcione.
Para tener acceso a la base de datos remotamente desde nuestro entorno de desarrollo, debemos crear una nueva regla al firewall de SQL Azure – que por defecto está configurado para rechazar todas las peticiones – y habilitar el acceso a nuestra IP. Para esto vamos al portal de gestión de SQL Azure (http://sql.azure.com) y accedemos a la pestaña de Firewall Settings.
Añadimos una nueva regla indicándole nuestra IP, que nos facilita el mismo formulario en la parte inferior.
Si tenemos una IP variable, podemos agregar un rango de valores. Nos advierten que la nueva regla tardará unos 5 minutos en replicarse, y no mienten
Bien, con este cambio ya podemos acceder desde la maquina local, con el Management Studio o desde nuestra aplicación con la cadena de conexión específica, que debería ser de este tipo:
connectionString="Server=tcp:xxxxxxxxxx.database.windows.net;Database=DemoGlopez;User ID=MyUsername;Password=MyPassword;Trusted_Connection=False;Encrypt=True;"
Para verificar esto, en un rol de ASP.NET de un proyecto Cloud Service, ponemos un simple código de acceso a datos, podría servir este:
using (SqlConnection con = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["DemoConnectionString"].ConnectionString)) { using (SqlCommand com = new SqlCommand("select id, nombre from Demo", con)) { con.Open(); using (SqlDataReader reader = com.ExecuteReader()) { while (reader.Read()) { Response.Write(reader["id"].ToString()); Response.Write(reader["nombre"].ToString()); Response.Write("<br/>"); } } } }
Ejecutamos y vemos que todo funciona bien (debemos crear una tabla en nuestra base de datos y modificar la consulta del command de este ejemplo para adaptarlo a nuestro entorno).
Ahora hacemos un Deploy de esta simple aplicación y veremos que falla (tendremos que deshabilitar los customErrors para que se nos muestre el mensaje de error).
Parece que seguimos teniendo problemas de acceso a SQL Azure, asi que volvemos al panel de gestión y hacemos un Test Connectivity (hay que tener seleccionada la base de datos), comprobamos que la conexión falla desde la IP del servidor dónde está alojada la aplicación.
Volvemos al Firewall Settings y marcamos el check de Allow Microsoft Services access to this server. Repetimos el Test Connectivity y ahora funciona.
Bien, volvemos a ejecutar nuestra aplicación, pero aún sigue fallando!

Cannot open server 'xxxxxxxxxx' requested by the login. Client with IP address '65.52.138.89' is not allowed to access the server.
Esto es un problema de la plataforma, debería funcionarnos, pero parece que hay un fallo con los rangos de IP que tienen permitidos internamente (info: http://tinyurl.com/yf46fxk). Así que nos toca añadir la IP del servidor como regla en nuestro firewall de SQL Azure hasta que esto se solucione.
Debería quedarnos una configuración de firewall así:
Y ahora nuestra aplicación funcionará correctamente.
Existe la posibilidad de administrar las reglas del firewall mediante procedures de la base de datos si no queremos utilizar el portal de gestión. Accedemos a la base de datos master de nuestro SQL Azure y utilizamos las siguientes sentencias:
-- Reglas existentes SELECT * FROM sys.firewall_rules -- Añadir nueva regla de firewall exec sp_set_firewall_rule N'DemoTSQL','0.1.1.0','0.1.1.255' --Modificar una regla de firewall exec sp_set_firewall_rule N'DemoTSQL','0.1.1.1','0.1.1.255' --Eliminar una regla de firewall exec sp_delete_firewall_rule N'DemoTSQL'
Feb/100
Cambiar la versión de la base de datos [SQL Azure]
Ayer publicaron en el SQL Azure Team Blog la noticia de la primera actualización al servicio de SQL Azure, esta actualización consiste en una serie de mejoras recogidas del feedback de la comunidad, toda la info.
Me parece interesante la posibilidad de cambiar la edición de la base de datos (Web edition – Business edition) con un simple comando:
ALTER DATABASE database_name { MODIFY (MAXSIZE = {1 | 10} GB) }
Feb/100
Cómo cuenta Azure las horas de proceso de instancia
Cuando empiezas a hacer cálculos sobre estimaciones de costes para subir una aplicación a la nube, enseguida te das cuenta dónde están los costes reales, en las horas de proceso y en el ancho de banda. En una aplicación con un tráfico que empieza a ser considerable ( por ejmeplo, > 5GB/hora). Las horas de proceso, pueden representar el 25% de la factura. Por lo tanto es importante conocer como computa Microsoft las horas que consumimos de proceso.
Lo primero que hay que saber, es que aunque no hagas un uso real de cpu, pagas por todas las horas que tengas tu aplicación levantada, y si tienes la aplicación levantada 5 minutos, pagas una hora completa.
Hasta aquí, no son noticias geniales, pero funciona similar a otros productos como Amazon EC2.
Lo que me ha parecido curioso y creo que es importante conocer, es que cuando mantienes una aplicación en estado de Parada o suspendida, continua computando horas. Eso quiere decir que si mantienes el entorno de producción levantado y el de staging, stopped, estás consumiendo 48h al día!
Para evitar esto, tenemos que eliminar la aplicación.
Si mantenemos un entorno como el de la imagen, gastamos 1h de proceso, si le damos a suspend, continuaremos consumiendo esa hora de proceso, debemos hacer un Delete para dejar de consumir esos recursos. Los dos entornos debería aparecernos como el de Production de la imagen.
Visto en: blog de Ryan Dunn
Ene/109
Routing en ASP.NET Web forms
Con el enrutamiento, podemos conseguir transformar las URL de nuestra aplicación para que sean más amigables al usuario, de tal manera, que no tiene porque hacerse referencia al fichero físico (.aspx, .html, …) por ejemplo, si tradicionalmente tenemos una URL del tipo: http://www.dominio.com/Post.aspx?category=7&id=14 podemos conseguir una url de este otro tipo http://www.dominio.com/Post/Eventos/14
Esta característica es una de las ventajas del framework ASP.NET MVC, pero lo que voy a tratar de explicar aquí es como activar esto en una aplicación web site o un proyecto web application. Para poder hacerlo, es necesaria la versión 3.5 SP1 del .NET framework.
Creamos una aplicación web ASP.NET en visual studio 2008. Lo primero que tenemos que hacer es agregarnos una referencia al ensamblado System.Web.Routing, que es quien nos proveerá de toda la funcionalidad de enrutado.
Ahora vamos al fichero web.config, y según con que versión de IIS vayamos a trabajar, debemos agregar las siguientes configuraciones.
- IIS 6.0 o IIS 7.0 en Classic mode
<httpModules> <add name="RoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> </httpModules>
- IIS 7.0 Integrated mode
<system.webServer> <modules runAllManagedModulesForAllRequests="true"> <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> </modules> <handlers> <add name="UrlRoutingHandler" preCondition="integratedMode" verb="*" path="UrlRouting.axd" type="System.Web.HttpForbiddenHandler, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> </handlers> </system.webServer>
NOTA: para trabajar en local, aunque tengamos IIS 7.0 instalado, recordad que trabajamos con el servidor web de visual studio 2008 (Cassini), y hay que añadir la configuración de los httpModules.
Ahora nos vamos al fichero global.asax (lo añadimos si no lo tenemos), incluimos el using System.Web.Routing y creamos la siguiente función (que llamaremos desde el método Application_Start).
1 2 3 4 5 6 7 | private static void RegisterRoutes() { RouteTable.Routes.Add( "NombreRuta", new Route("nivel1/{cualquiercosa}", new DemoRouteHandler("~/Demo.aspx"))); } |
1 2 3 4 | protected void Application_Start(object sender, EventArgs e) { RegisterRoutes(); } |
Aquí debemos registrar todas las rutas que queramos controlar, por ejemplo, en este caso, solo vamos a capturar las peticiones a las URL del tipo http://server/nivel1/cualquiervalor y lo que haremos será enviar esa petición a la página Demo.aspx.
Si recibimos una petición a una URL que no cumple ningún patrón de los registrados, la aplicación devolverá un error 404.
Nos falta crear la clase DemoRouteHandler que implementa IRouteHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | using System.Web; using System.Web.Routing; using System.Web.Compilation; using System.Web.UI; namespace DemoRouting { public class DemoRouteHandler : IRouteHandler { string _virtualPath; public DemoRouteHandler(string virtualPath) { _virtualPath = virtualPath; } public IHttpHandler GetHttpHandler(RequestContext requestContext) { var display = BuildManager.CreateInstanceFromVirtualPath( _virtualPath, typeof(Page)) as IHttpHandler; return display; } } } |
Si creamos la página Demo.aspx, en el Page_Load podemos acceder a la propiedad Url de la propiedad Request de la instancia Page.
Ya tenemos activado el enrutamiento en nuestra aplicación, podemos lanzarla desde vs y comprobarlo accediendo directamente a la url que cumpla el patrón.
Podemos descargar un ejemplo más bonito y completo de msdn magazine y profundizar con las reglas de enrutado en este artículo de msdn.







