martes, octubre 16, 2007

GeoIP WebService con ASP.NET y C#

Para este ejemplo me enfoqué en los elementos necesarios para crear un WebService en ASP.NET para obtener el país de origen de una IP. Por este motivo deben tener en cuenta que temas como separación de capas, administración de errores, configuraciones en web.config y otras cosas muy importantes deberán ser implementadas por ustedes.

Este ejemplo consta de una sola página .asmx y, aunque no es lo recomendado, debo admitir que hay veces en las que me gusta que toda la solución pueda ser fácilmente portada copiando un solo archivo.

Con todas estas salvedades, pasemos a la receta.

Ingredientes
  • Una base de datos de IP. Recomiendo la de MaxMind - GeoLite Country / Open Source IP Address to Country Database. Para este ejemplo elegimos la versión gratuita.
  • Una base de datos de una sola tabla. Para este ejemplo elegimos Microsoft Access para hacer el ejemplo más sencillo y transportable.
  • Una Regular Expression para validar IPs.
  • Un algoritmo para convertir un string IP en un double.

Preparación
1.- Nos bajamos la base gratuita de IPs desde la página de MaxMind (para una solución comercial deberíamos adquirir la versión full) desde haciendo “clic aquí”.

2.- Creamos una nueva base de datos Access y creamos una tabla que llamaremos “IPSegments”. Esta tabla deberá contener los siguientes campos (si respetamos el orden será luego más fácil importar la base).
  • beginIP – Text
  • endIp – Text
  • beginNro – Numbrer (double)
  • endNro – Numbrer (double)
  • CountryCode – Text
  • ContryName - Text
3.- Importamos la base de IPs a la base que creamos en Access. Para este ejemplo la base debe estar en la misma carpeta del Web Server donde estará nuestro Webservice.

4.- Creamos nuestro archivo .asmx.
Luego de la declaración de WebService nos aseguramos de incluir las siguientes líneas “using”:
using System.Data;
using System.Data.OleDb;
using System.Text.RegularExpressions;

El único método web será algo así:
[WebMethod]
public string GetCountryCode(string ip) {
return GetCountryByIp(ip);
}


El primer método privado será el validador de IPs. Lo podemos resolver de forma elegante con una Regular Expression.
private bool IsValidIp(string ip) {
string pattern = @"^([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3}$";
Regex reg = new Regex(pattern, RegexOptions.Singleline | RegexOptions.ExplicitCapture);
return reg.IsMatch(ip);
}


Luego necesitamos el algoritmo para convertir una IP en un double
private double Ip2Double(string ip) {
string[] octetos = ip.Split('.');
int w = int.Parse(octetos[0]);
int x = int.Parse(octetos[1]);
int y = int.Parse(octetos[2]);
int z = int.Parse(octetos[3]);

double result = 16777216 * (double)w + 65536 * (double)x + 256 * (double)y + (double)z;
return result;
}


Finalmente creamos el método que busca el país en la base de datos
private string GetCountryByIp(string ip) {
if (!IsValidIp(ip)) throw new Exception("Invalid IP format");

object result;
double ipNum = Ip2Double(ip);
string connectionString = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=";
connectionString += System.Web.HttpContext.Current.Server.MapPath("GeoIPCountry.mdb");

OleDbConnection oConn = new OleDbConnection(connectionString);
string query = "SELECT CountryCode FROM IPSegments WHERE @IpNum BETWEEN beginNro AND endNro";
OleDbCommand dbCommand = new OleDbCommand(query, oConn);
dbCommand.Parameters.Add("@IpNum", DbType.Double).Value = ipNum;

oConn.Open();
try {
result = dbCommand.ExecuteScalar();
} finally {
oConn.Close();
}

if (result == null) throw new Exception("IP not in Database, maybe is a local IP");
return result.ToString();
}


En este link tienen el “plato terminado”: GeoIP WebService con ASP.NET y C#. Pueden copiarlo en una instancia de IIS configurada para ejecutar ASP.NET (funciona en todas las versiones) y probarlo. Sin embargo les recomiendo hacer sus propias implementaciones tomando este ejemplo solo como una especie de “starter kit”.