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.
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.
Ene/101
Añadir una aplicación web existente en Azure
Por temas de trabajo me está tocando ponerme al tanto en Windows Azure, no puedo quejarme
Ya hay buenos artículos sobre cómo crear nuevas aplicaciones web y publicarlas en Azure.
Enlace: Subir una aplicación a Windows Azure
Aquí voy a tratar de explicar cómo publicar una aplicación web que ya teníamos creada.
Lo primero que debemos hacer es preparar nuestro entorno si aún no lo hemos hecho.
Vamos a la página http://www.microsoft.com/windowsazure/ y buscamos el enlace que dice Get Tools & SDK
Nos descargamos el fichero y lo instalamos. Revisad las system requeriments, yo tuve que instalar IIS 7.0 que por defecto no viene en Windows 7.
Después de esto, arrancamos Visual Studio 2008, hay que ejecutarlo en modo administrador para darle permisos al Development Fabric.
Nos vamos a crear un nuevo proyecto, tendremos un nuevo tipo, Windows Azure Cloud Service dentro de la pestaña Cloud Service.
Cuando le demos a aceptar, VS nos lanzara un nuevo wizard:
En este caso, como agregaremos después nuestro proyecto, le damos a Ok sin agregar nada a la solución.
Visual Studio nos ha creado un proyecto:
Que sólo contiene los ficheros de configuración del servicio Azure.
Ahora, agregamos nuestro proyecto web a la solución, botón derecho sobre la solución -> Add -> Existing Project y este es uno de los puntos importantes, y es que actualmente Azure no soporta los Web Site. No podemos hacer uso de Add -> Existing Web Site… Esto quiere decir que nuestra aplicación web debe estar en una ASP.NET Web Application o ASP.NET MVC Web Application.
Una vez agregado el proyecto, nos vamos a la carpeta Roles del cloud service y hacemos botón derecho Add -> Web Role Project in solution
Nos aparece una pantalla con todos los proyectos de la solución, en este caso sólo uno, lo seleccionamos y le damos Ok. Visual Studio ya nos ha creado el enlace y la configuración para la aplicación web. Ya podemos dar F5 y trabajar!
Para publicar en Azure seguid el post enlazo al inicio de ‘Subir una aplicación a Windows Azure’.
Dic/091
Crear un AddIn para Outlook – Parte I
Se puede decir que Office es una plataforma de desarrollo, y más aún cuando llegó la versión 3.0 de VSTO, podéis ver las novedades de la versión en http://msdn.microsoft.com/es-es/library/86bkz018.aspx
Bien, vamos a hacer una serie de post explicando cómo crear un AddIn para Outlook y así mostrar la potencia que ofrece esta tecnología.
Como entorno de desarrollo necesitaremos:
- Microsoft Outlook 2007
- Visual Studio 2008 SP1
- Microsoft Framework 3.5
- VSTO 3.0 Runtime (se instala por defecto con VS 2008)
Una vez cumplimos estos requisitos, podemos crear un nuevo tipo de proyecto en visual studio
Al crear este nuevo proyecto, veremos una estructura como la siguiente:
Aquí podemos ver cómo por defecto, nos ha agregado las referencias necesarias. También vemos la clase ThisAddIn.cs que es la clase que llamará Outlook cuando quiera cargar nuestro complemento.
Si nos fijamos en el código de la clase, veremos cómo lo primero que hace es asociar los eventos de inicio y parada de la aplicación para que podamos empezar a ejecutar nuestro propio código.
Ahora, para hacer la prueba, agregamos un mensaje en cada función:
1 2 3 4 5 6 7 8 9 | private void ThisAddIn_Startup(object sender, System.EventArgs e) { MessageBox.Show("Hello!"); } private void ThisAddIn_Shutdown(object sender, System.EventArgs e) { MessageBox.Show("Bye!"); } |
Ponemos algún punto de interrupción y lanzamos la aplicación(F5). Como podéis observar, visual studio ya nos lanza la aplicación outlook. Nos saltará el mensaje del evento Startup y podéis comprobar lo fácil que resulta debugar este tipo de aplicaciones.
El siguiente paso, será crear un nuevo menú para ejecutar nuestras acciones bajo demanda del usuario.
Creamos las siguientes variables de la clase:
1 2 3 4 5 | private Office.CommandBar _menubar; private Office.CommandBarButton _menuCommand1; private Office.CommandBarButton _menuCommand2; private Office.CommandBarPopup _cmdBarControl; private string _menuTag = "CustomMenu"; |
Añadimos dos funciones, una que creará el menú y otra que lo elimina y las llamaremos desde la función ThisAddIn_Startup:
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | private void AddMenuBar() { try { if (this.Application.ActiveExplorer() != null) { _menubar = this.Application.ActiveExplorer().CommandBars.Add( _menuTag, Office.MsoBarPosition.msoBarTop, false, true) as Office.CommandBar; if (_menubar != null) { string menuCaption = "&" + _menuTag; _menubar.Visible = true; _cmdBarControl = _menubar.Controls.Add( Office.MsoControlType.msoControlPopup, Type.Missing, Type.Missing, Type.Missing, true) as Office.CommandBarPopup; if (_cmdBarControl != null) { _cmdBarControl.Caption = menuCaption; _menuCommand1 = _cmdBarControl.Controls.Add( Office.MsoControlType.msoControlButton, Type.Missing, Type.Missing, Type.Missing, true) as Office.CommandBarButton; if (_menuCommand1 != null) { _menuCommand1.Caption = "&Hello"; _menuCommand1.Tag = "Hello"; _menuCommand1.Click += new Microsoft.Office.Core._CommandBarButtonEvents_ClickEventHandler(CustomAction); } _menuCommand2 = _cmdBarControl.Controls.Add( Office.MsoControlType.msoControlButton, Type.Missing, Type.Missing, Type.Missing, true) as Office.CommandBarButton; if (_menuCommand2 != null) { _menuCommand2.Caption = "&Bye"; _menuCommand2.Tag = "Bye"; _menuCommand2.Click += new Microsoft.Office.Core._CommandBarButtonEvents_ClickEventHandler(CustomAction); } } } } } catch (Exception) { //log } } private void RemoveMenubar() { try { Office.CommandBar foundMenu = this.Application.ActiveExplorer().CommandBars.ActiveMenuBar.FindControl( Office.MsoControlType.msoControlPopup, Type.Missing, _menuTag, true, true) as Office.CommandBar; if (foundMenu != null) { foundMenu.Delete(); } } catch (Exception) { //log } } |
De este código hay que descatar el uso de la variable
1 | this.Application |
que nos da acceso a la instancia de la aplicación Outlook. Después lo único que hacemos es crear un menú desplegable con dos botones. Como veis, al evento Click de los botones se les asocia una función, en este caso CustomAction, que debe tener la siguiente estructura:
1 2 3 4 | private void CustomAction(Microsoft.Office.Core.CommandBarButton Ctrl, ref bool CancelDefault) { MessageBox.Show("Click en botón: " + Ctrl.Tag); } |
Como resultado de este código, ya tenemos nuestro propio menú:
Hasta aquí llega la primera parte de la serie, en el siguiente post, explicaremos como configurar la cinta de opciones (Ribbon).
Podéis descargar el proyecto de ejemplo: Proyecto
Nov/091
Indizadores en c#
Los indizadores son propiedades de una clase que hacen que esta se comporte como una matriz.
La definición de un indizador se hace declarando una propiedad con el nombre this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class Jugadores { private List<Jugador> jugadores = new List<Jugador>(); public Jugadores() { } internal Jugador this[int index] { get { return jugadores[index]; } set { jugadores.Add(value); } } } |
Podemos sobrecargar el indizador, cambiando el tipo de dato que recibe o devuelve la propiedad. Por ejemplo, podemos hacer una sobrecarga, que reciba el nombre del jugador en una cadena, y devuelva la instancia del Jugador con dicho nombre.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | internal Jugador this[string nombre] { get { foreach (Jugador item in jugadores) { if (item.Nombre.Equals(nombre)) { return item; } } return null; } } |
Como se ve en el codigo anterior, podemos hacer la propiedad de sólo lectura si lo deseamos, en este aspecto, funcionan como cualquier otra propiedad.
Para hacer uso del indizador, ponemos un código sencillo de ejemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class Program { static void Main(string[] args) { Jugadores jug = new Jugadores(); jug[0] = new Jugador(1, "Victor Valdes"); jug[1] = new Jugador(2, "Daniel Alves"); jug[2] = new Jugador(5, "Carles Puyol"); jug[3] = new Jugador(10, "Andrés Iniesta"); Jugador j = jug["Carles Puyol"]; Console.WriteLine(string.Format("Drosal: {0}, nombre: {1}: ", j.Dorsal, j.Nombre)); j = jug[3]; Console.WriteLine(string.Format("Drosal: {0}, nombre: {1}: ", j.Dorsal, j.Nombre)); Console.ReadKey(); } } |
Nov/090
Palabra clave global::
Al definir namespaces en C#, puede que necesitemos definir uno con el mismo nombre que otro ya definido en la plataforma .NET. Por ejemplo, nos puede interesar definir un namespace en nuestro proyecto con el nombre de System.
Si hacemos esto, visual studio no nos dejara hacer referencia al namespace del framework porque siempre encontrara primero el de nuestro ambito. Para resolver este conflicto, debemos utilizar la palabra clave global:: antes del nombre del namespace y nos habilitara el acceso.
Es buena practica no utilizar los mismos nombres que el propio framework, pero si es necesario, tenemos esta manera de resolverlo.










