viernes, 27 de febrero de 2015

Tips de Crystal Reports

Quitar decimales a un numero en una formula
ToText(valor,0)
valor es el número a convertir
0 son los decimales
Se puede incluir un tercer parámetro para indicar el formato de separadores (''=ninguno)

Utilizar parámetros en un comando de un subinforme
Crear el comando en el subinforme. A la derecha crear el parámetro. Para usarlo, incluirlo en la sentencia SQL como {?NombreParametro}.
Luego, desde el informe principal, vincular el campo deseado al parámetro en cuestión del subinforme.

Utilizar un valor calculado en un subinforme en el informe principal
Supongamos que tenemos un valor que se calcula o se obtiene en un subinforme y deseamos utilizarlo (para mostrarlo o utilizarlo) en el informe principal.

Esto se soluciona utilizando variables compartidas, usadas desde la versión 7 de Crystal Report, y la solución consiste en lo siguiente:

Se debe tener en cuenta que el reporte primero evalúa la fórmula en donde se llena el valor de la variable compartida, y luego realiza la fórmula en donde se muestran los datos, independientemente de dónde se encuentre la fórmula y la variable, ya sea en el principal o en el subinforme.

1. En el subinforme, crear una formula parecida a esta:
//@SubFormula
//Guardar el total del campo {Orders.Order Amount}
//en una variable compartida llamada 'miTotal'
Shared CurrencyVar miTotal := Sum ({Orders.Order Amount})
Poner esta fórmula en el subinforme.
En el informe principal, crear una fórmula que declara el nombre de la misma variable:
//@FormulaPrincipal
//Retorna el valor que fue guardado en la variable compartida llamada
//miTotal del subinforme
Shared CurrencyVar miTotal;
miTotal
Poner la @FormulaPrincipal en el informe principal debajo de la sección que contiene el subinforme. Esto sirve para asegurar que el Crystal evalue antes la @SubFormula antes de la @FormulaPrincipal.

Una vez que tenemos el valor que necesitamos en la @FormulaPrincipal podemos hacer uso de ella como más nos convenga, por ejemplo, ocultar una sección dependiendo del monto que devuelve la @SubFormula.

jueves, 12 de febrero de 2015

Cargar el resultado de un fichero PHP dentro de un DIV con jQuery

Esta entrada muestra como podemos, dentro de una etiqueta DIV de nuestra página, colocar el resultado obtenido al hacer una llamada a una página php. Para poder hacer esto, lo primero que necesitamos es definir el DIV donde vamos a introducir el contenido:
<div class="contentWrapper" id="contenido"></div>
A cotinuación, con jQuery, en el lugar donde nos interese, haremos lo siguiente:
$("#contenido").load('url_al_archivo.php');
Paso de parámetros GET
Si queremos además pasar algún parametro por GET, se puede hacer de la siguiente forma:
$("#contenido").load('url_al_archivo.php?var1=x&var2=y&var3=z');
Paso de parámetros POST
En el caso de que los parámetros sean por POST, utilizaremos el siguiente formato.
$("#contenido").load('url_al_archivo.php', {var1:x, var2:y, var3:z});
Es todo, así de sencillo.

miércoles, 11 de febrero de 2015

Uso de fuentes externas sólo en fichero CSS

Si se desean usar fuentes externas, como por ejemplo Google Fonts, normalmente nos indicarán que para hacerlo necesitamos hacer un link href en la cabecera de nuestra página, y luego en nuestro fichero CSS ya podremos hacer referencia a la fuente. He aqui un ejemplo:
<link href="http://fonts.googleapis.com/css?family=Cuprum" type="text/css" rel="stylesheet" />
body {
  font-family: 'Cuprum', serif;
  font-size: 48px;
}
En ocasiones no nos interesa hacerlo así, ya que en cada página tendremos que incluir la referencia a la fuente, y si queremos cambiarla tendremos que ir página a página. O puede que estemos editando un fichero CSS de WordPress y queramos incluir una fuente sin tener acceso a las páginas HTML o PHP. En estos casos, podemos importar la fuente directamente desde dentro del fichero CSS. Hay varias formas de hacerlo, y aquí vamos a ver dos de ellas:

Forma 1
@font-face{
  font-family:'Droid Serif';
  font-style:normal;
  font-weight:400;
  src:local('Droid Serif'),local('DroidSerif'),url(http://fonts.gstatic.com/s/droidserif/v6/0AKsP294HTD-nvJgucYTaIbN6UDyHWBl620a-IRfuBk.woff) format('woff');
 }
body {
  font-family: 'Droid Serif';
  font-size: 14px;
}
Forma 2
@import url(http://fonts.googleapis.com/css?family=Open+Sans);
body {
  font-family: 'Open Sans';
  font-size: 14px;
}
Facil, ¿verdad?

viernes, 6 de febrero de 2015

Crear un WebService con PHP

Si trabajamos con PHP, una tarea con la casi seguro que nos encontraremos habitualmente es con la de construir un servicio web que exponga una serie de datos. Esto es muy sencillo de hacer en PHP, y vamos a ver como se hace.

Antecedentes
Supongamos que vamos a realizar un webservice que se conecte a una base de datos MySQL para lanzarle consultas y devolver los resultados. Esto será válido para la mayoría de casos con que nos encontramos, independientemente de que después debamos ampliar el servicio con otros métodos (como por ejemplo incluir seguridad, encriptación, etc).

Lo primero que se nos puede ocurrir, es crear una serie de métodos en el WebService de forma que cada uno reciba sus parámetros, monte las consultas, y las lance contra la base de datos para terminar devolviendo los resultados. Esto es correcto, pero vamos a crear una técnica más potente.

En vez de hacer un método por cada consulta a lanzar, vamos a tener un sólo método. Este, recibirá un nombre de opción a ejecutar, y según esa opción, lanzará una u otra consulta. Es más, no tendremos que tener las consultas dentro del propio WebService, sino que nos basaremos para configurarlas en un fichero XML. Esto nos va a permitir disponer de un código muy limpio y claro, con muy poco mantenimiento posterior, y configuraremos las posibles opciones y sus consultas relacionadas en un fichero XML, de forma que para crear, modificar, o eliminar nuevos métodos, no modificaremos el servicio web, sino únicamente el XML. Veamos cómo.

Fichero de configuración
Vamos a crear un fichero XML de configuración que contendrá los métodos de que dispondrá el servicio web. Posteriormente, dicho servicio recibirá el método a ejecutar, su lista de parámetros, y devolverá los resultados.

El fichero podría ser el siguiente:
<?xml version="1.0" encoding="utf-8" ?>
<OpcionesDisponibles>
  <opcion>
    <nombre>GetPaises</nombre>
    <SQL>SELECT cod_pais,pais FROM paises order by pais</SQL>
  </opcion>
  <opcion>
    <nombre>GetPais</nombre>
    <SQL>SELECT cod_pais,pais FROM paises WHERE cod_pais='#PARAMETRO_1#'</SQL>
  </opcion>
</OpcionesDisponibles>
Como se puede ver, es extremadamente simple, y nos permitirá ampliar, modificar, o reducir el servicio web sin tener que modificarlo.

El WebService
Una vez que sabemos lo que queremos, y que tenemos nuestro fichero de configuración de métodos definido, sólo nos queda crear el servicio web.

Nuestro servicio recibirá tres parámetros: la opción a ejecutar, la lista de parámetros relativas a esa función, y el formato de resultado que deseamos. Así pues, manos a la obra:
<?php
  // Recoger parámetros de entrada. Se permite GET y POST
  $paramOpcion = $_REQUEST['opcion'];
  $paramLista = $_REQUEST['listaParametros'];
  $formato = strtolower($_REQUEST['format']) == 'xml' ? 'xml' : 'json';

  // Obtener consulta SQL a ejecutar
  $sql = getSQL($paramOpcion);
  $sql = aplicarParametrosSQL($sql,$paramLista);

  // Lanzar consulta a la base de datos
  if ($sql == '')
    die ('Opcion incorrecta');
  else
  {    
    $datos = getArrayDatos($sql);
    echo formatData($datos,$formato); 
  }


  // Dado un nombre de opción, buscar en el xml su SQL correspondiente
  function getSQL($nombreOpcion)
  {
    $xmlOpciones = simplexml_load_file('opciones.xml');
    $sql = ""; 
    foreach ($xmlOpciones->opcion as $opcion)
    {
      $nombre = $opcion->nombre;
      if ($nombre == $nombreOpcion)
      {
        $sql = $opcion->SQL;
        break;
      }
    }
    return $sql;
  }

  // Aplicar parámetros de la sentencia SQL
  function aplicarParametrosSQL($sql, $listaPametros)
  {    
    if ($listaPametros != '')
    {
      $arrParam = explode('#',$listaPametros);
      for ($i=0; $i<count($arrParam); $i++)
      {
        $nomParam = '#PARAMETRO_'.($i+1).'#';
        $valor = $arrParam[$i];
        $sql = str_replace($nomParam, $valor, $sql);
      }
    }
    return $sql;
  }

  function getArrayDatos($sql)
  {
    $db = mysql_connect('servidor_base_datos','usuario','clave') or die('No se pudo conectar con la base de datos');
    mysql_select_db('base_de_datos',$db) or die('No se pudo seleccionar la base de datos.');
    $resul = mysql_query($sql,$db) or die('Consulta incorrecta:  '.$sql);

    /* create one master array of the records */
    $datos = array();
    if(mysql_num_rows($resul)) 
    {
     while($row = mysql_fetch_assoc($resul)) 
     {
      $datos[] = $row;
     }
    }

    /* disconnect from the db */
    @mysql_close($db);    

    /* Return data array */
    return $datos;        
  }

  function formatData($data,$format='json')
 {
    if($format == 'json' || $format == 'jsonp') 
    {
      header('Content-type: application/json');
      if (isset($_REQUEST['callback']))   // Peticiones ajax
        return $_REQUEST['callback'].'('.json_encode($data).')';
      else
        return json_encode($data);
    }
    else
    {
      header('Content-type: text/xml');
      $xml = new SimpleXMLElement('<response/>');
      return array2xml($data, $xml);
    }
  }

  function array2xml($array, $xml = false)
  {
    if($xml === false)
    {
      $xml = new SimpleXMLElement('<root/>');
    }
    foreach($array as $key => $value)
    {
      if(is_array($value))
      {
        //array2xml($value, $xml->addChild($key));
        array2xml($value, $xml->addChild('item'));
      }
      else
      {
        $xml->addChild($key, htmlspecialchars($value));
      }
    }
    return $xml->asXML();
  }

?>
Y eso es todo. Potente, ¿verdad?. Ahora sólo debemos modificar el fichero XML a nuestro gusto para adaptarlo a nuestras necesidades (ojo, recordar introducir algún sistema de seguridad, y a ser posible, excluir el fichero XML en el fichero robots.txt de nuestro servidor para que no sea indexado).

En otra entrada veremos como consumir un webservice.

Crear un WebService con .NET

Si trabajamos con .NET, una tarea con la casi seguro que nos encontraremos habitualmente es con la de construir un servicio web que exponga una serie de datos. Esto es muy sencillo de hacer en .NET, y vamos a ver como se hace.

Antecedentes
Supongamos que vamos a realizar un webservice que se conecte a una base de datos SQL Server para lanzarle consultas y devolver los resultados. Esto será válido para la mayoría de casos con que nos encontramos, independientemente de que después debamos ampliar el servicio con otros métodos (como por ejemplo incluir seguridad, encriptación, etc).

Lo primero que se nos puede ocurrir, es crear una serie de métodos en el WebService de forma que cada uno reciba sus parámetros, monte las consultas, y las lance contra la base de datos para terminar devolviendo los resultados. Esto es correcto, pero vamos a crear una técnica más potente.

En vez de hacer un método por cada consulta a lanzar, vamos a tener un sólo método. Este, recibirá un nombre de opción a ejecutar, y según esa opción, lanzará una u otra consulta. Es más, no tendremos que tener las consultas dentro del propio WebService, sino que nos basaremos para configurarlas en un fichero XML. Esto nos va a permitir disponer de un código muy limpio y claro, con muy poco mantenimiento posterior, y configuraremos las posibles opciones y sus consultas relacionadas en un fichero XML, de forma que para crear, modificar, o eliminar nuevos métodos, no modificaremos el servicio web, sino únicamente el XML. Veamos cómo.

Fichero de configuración
Vamos a crear un fichero XML de configuración que contendrá los métodos de que dispondrá el servicio web. Posteriormente, dicho servicio recibirá el método a ejecutar, su lista de parámetros, y devolverá los resultados.

El fichero podría ser el siguiente:
<?xml version="1.0" encoding="utf-8" ?>
<OpcionesDisponibles>
  <opcion>
    <nombre>GetPaises</nombre>
    <SQL>SELECT cod_pais,pais FROM paises order by pais</SQL>
  </opcion>
  <opcion>
    <nombre>GetPais</nombre>
    <SQL>SELECT cod_pais,pais FROM paises WHERE cod_pais='#PARAMETRO_1#'</SQL>
  </opcion>
</OpcionesDisponibles>
Como se puede ver, es extremadamente simple, y nos permitirá ampliar, modificar, o reducir el servicio web sin tener que modificarlo.

El WebService: La clase mágica
Una vez que sabemos lo que queremos, y que tenemos nuestro fichero de configuración de métodos definido, sólo nos queda crear el servicio web.

Nuestro servicio recibirá tres parámetros: la opción a ejecutar, la lista de parámetros relativas a esa función, y el formato de resultado que deseamos. Así pues, manos a la obra:
using System;
using System.Data;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Xml;

public class WS_Clase
{
  static string ficheroOpciones = HttpContext.Current.Server.MapPath("~/") + "\\opciones.xml";

  /// <summary>
  /// Ejecutar y devolver un XML resultado de ejecutar una sentencia SQL configurada en un fichero XML.
  /// </summary>
  /// <param name="nombre"></param>
  /// <param name="listaParametros"></param>
  /// <returns></returns>
  public static XmlDocument GetOpcion(string nombre, string listaParametros)
  {
    try
    {
      // Buscar opcion en el fichero de parametros para obtener la SQL a ejecutar
      DataSet ds = new DataSet();
      ds.ReadXml(ficheroOpciones);
      DataTable dt = new DataTable();
      dt = ds.Tables[0];
      string SQL = string.Empty;

      for (int i = 0; i < dt.Rows.Count; i++)
      {
        if (Convert.ToString(dt.Rows[i]["nombre"]).ToUpper() == nombre.ToUpper())
        {
          SQL = Convert.ToString(dt.Rows[i]["SQL"]);
          break;
        }
      }
      if (SQL == string.Empty)
        throw new Exception("Opción no disponible: " + nombre);

      // Asignar parametros
      string[] parametros;
      parametros = listaParametros.Split('|');
      for (int i = 0; i < parametros.Length; i++)
      {
        SQL = SQL.Replace("#PARAMETRO_" + Convert.ToString(i + 1) + "#", parametros[i]);
      }

      // Ejecutar SQL
      dt = AccesoDatos.GetDataTable(SQL);

      System.Xml.XmlDocument x = DataTableToXmlDocument(dt);
      dt.Dispose();
      ds.Dispose();
      return x;

    }
    catch (Exception ex)
    {
      throw new Exception(ex.Message);
    }
  }

  public static int SetOpcion(string nombre, string listaParametros)
  {
    try
    {
      // Buscar opcion en el fichero de parametros para obtener la SQL a ejecutar
      DataSet ds = new DataSet();
      ds.ReadXml(ficheroOpciones);
      DataTable dt = new DataTable();
      dt = ds.Tables[0];
      string SQL = string.Empty;

      for (int i = 0; i < dt.Rows.Count; i++)
      {
        if (Convert.ToString(dt.Rows[i]["nombre"]).ToUpper() == nombre.ToUpper())
        {
          SQL = Convert.ToString(dt.Rows[i]["SQL"]);
          break;
        }
      }
      if (SQL == string.Empty)
        throw new Exception("Opción no disponible: " + nombre);

      // Asignar parametros
      string[] parametros;
      parametros = listaParametros.Split('|');
      for (int i = 0; i < parametros.Length; i++)
      {
        SQL = SQL.Replace("#PARAMETRO_" + Convert.ToString(i + 1) + "#", parametros[i]);
      }

      // Ejecutar SQL
      int resul = AccesoDatos.ExecuteSQL(SQL);

      // Devolver resultado
      dt.Dispose();
      ds.Dispose();
      return resul;
    }
    catch (Exception ex)
    {
      throw new Exception(ex.Message);
    }
  }

  /// <summary>
  /// GetOpcion en formato JSON
  /// </summary>
  /// <param name="nombre"></param>
  /// <param name="listaParametros"></param>
  /// <returns></returns>
  public static String GetOpcionJSON(string nombre, string listaParametros)
  {
    try
    {
      // Buscar opcion en el fichero de parametros para obtener la SQL a ejecutar
      DataSet ds = new DataSet();
      ds.ReadXml(ficheroOpciones);
      DataTable dt = new DataTable();
      dt = ds.Tables[0];
      string SQL = string.Empty;

      for (int i = 0; i < dt.Rows.Count; i++)
      {
        if (Convert.ToString(dt.Rows[i]["nombre"]).ToUpper() == nombre.ToUpper())
        {
          SQL = Convert.ToString(dt.Rows[i]["SQL"]);
          break;
        }
      }
      if (SQL == string.Empty)
        throw new Exception("Opción no disponible: " + nombre);

      // Asignar parametros
      string[] parametros;
      parametros = listaParametros.Split('|');
      for (int i = 0; i < parametros.Length; i++)
      {
        SQL = SQL.Replace("#PARAMETRO_" + Convert.ToString(i + 1) + "#", parametros[i]);
      }

      // Ejecutar SQL
      dt = AccesoDatos.GetDataTable(SQL);

      string x = DataTableToJSONDocument(dt);
      dt.Dispose();
      ds.Dispose();
      return x;

    }
    catch (Exception ex)
    {
      throw new Exception(ex.Message);
    }
  }

  #region " Métodos privados "

  /// <summary>
  /// Configurar DataTable con nomenclatura estandar para hacer mas fácil la lectura desde aplicaciones cliente (siempre mismos campos).
  /// </summary>
  /// <param name="dt"></param>
  /// <returns></returns>
  private static DataTable DataTableToStandard(DataTable dt)
  {
    try
    {
      DataTable dtResultado = new DataTable(dt.TableName);
      dtResultado.Columns.Add("campo", typeof(System.String));
      dtResultado.Columns.Add("valor", typeof(System.String));

      DataRow dr;
      for (int i = 0; i < dt.Rows.Count; i++)
      {
        dr = dtResultado.NewRow();
        dr["campo"] = dt.Rows[i][0];
        dr["valor"] = dt.Rows[i][1];
        dtResultado.Rows.Add(dr);
      }

      return dtResultado;
    }
    catch (Exception ex)
    {
      throw ex;
    }
  }

  private static String DataTableToJSONDocument(DataTable dt)
  {

    string[] StrDc = new string[dt.Columns.Count];
    string HeadStr = string.Empty;
    for (int i = 0; i < dt.Columns.Count; i++)
    {
      StrDc[i] = dt.Columns[i].Caption;
      HeadStr += "\"" + StrDc[i] + "\":\"" + StrDc[i] + i.ToString() + "¾" + "\",";
    }

    HeadStr = HeadStr.Substring(0, HeadStr.Length - 1);

    StringBuilder Sb = new StringBuilder();

    Sb.Append("[");

    for (int i = 0; i < dt.Rows.Count; i++)
    {
      string TempStr = HeadStr;
      for (int j = 0; j < dt.Columns.Count; j++)
      {
        TempStr = TempStr.Replace(dt.Columns[j] + j.ToString() + "¾", dt.Rows[i][j].ToString().Trim());
      }
      //Sb.AppendFormat("{{{0}}},",TempStr);

      Sb.Append("{" + TempStr + "},");
    }

    Sb = new StringBuilder(Sb.ToString().Substring(0, Sb.ToString().Length - 1));

    if (Sb.ToString().Length > 0)
      Sb.Append("]");

    return StripControlChars(Sb.ToString());

  }

  public static string StripControlChars(string s)
  {
    return Regex.Replace(s, @"[^\x20-\x7F]", "");
  }

  /// <summary>
  /// Pasar DataTable a XML estandarizado para su devolución por el WebService.
  /// </summary>
  /// <param name="dt"></param>
  /// <returns></returns>
  private static XmlDocument DataTableToXmlDocument(DataTable dt)
  {
    XmlDocument xml = new XmlDocument();
    System.Text.StringBuilder sb = new System.Text.StringBuilder();

    sb.Append("<?xml version='1.0' encoding='utf-8' ?>");
    sb.Append("<DocumentElement>");

    foreach (DataRow dr in dt.Rows)
    {
      sb.Append("<Dato>");

      foreach (DataColumn dc in dt.Columns)
      {
        sb.Append("<" + dc.ColumnName + ">" + dr[dc].ToString() + "</" + dc.ColumnName + ">");
      }

      //foreach (DataColumn dc in dt.Columns)
      //  sb.Append("<" + dc.ColumnName + ">" + dr[dc].ToString() + "</" + dc.ColumnName + ">");

      sb.Append("</Dato>");
    }

    sb.Append("</DocumentElement>");
    xml.LoadXml(sb.ToString());

    return xml;
  }

  #endregion

}

El WebService: La interfaz
Una vez hemos definido la clase que hace lo que deseamos, simplemente exponemos el webservice:
using WS_Clase; // Nuestra clase creada antes
using System.Web;
using System.Web.Script.Services;
using System.Web.Services;
using System.Xml;

namespace eCliente_WebService
{
  /// <summary>
  /// Servicio web para el acceso a datos de la web de eCliente
  /// </summary>
  [WebService(Namespace = "http://nuestra_url")]
  [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
  [System.ComponentModel.ToolboxItem(false)]
  // Para permitir que se llame a este servicio web desde un script, usando ASP.NET AJAX, quite la marca de comentario de la línea siguiente. 
  // [System.Web.Script.Services.ScriptService]
  public class eCliente_WebService : System.Web.Services.WebService
  {
    [WebMethod]
    public string Test()
    {
      return "Hola desde el WebService de eCliente.";
    }

    [WebMethod]
    public XmlDocument GetOpcion(string nombre, string listaParametros)
    {
      return WS_Clases.GetOpcion(nombre, listaParametros);
    }

    [WebMethod]
    public int SetOpcion(string nombre, string listaParametros)
    {
      return WS_Clases.SetOpcion(nombre, listaParametros);
    }

    [WebMethod]
    [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
    public void GetOpcionJSON(string nombre, string listaParametros)
    {
      HttpContext.Current.Response.Write(WS_Clases.GetOpcionJSON(nombre, listaParametros));
    }


    [WebMethod]
    [ScriptMethod(UseHttpGet = true, ResponseFormat = ResponseFormat.Json)]
    public void GetOpcionJSONAjax(string nombre, string listaParametros, string callback = "")
    {
      System.Text.StringBuilder sb = new System.Text.StringBuilder();
      if (callback != "")
      {
        sb.Append(callback + "(");
        sb.Append(WS_Clases.GetOpcionJSON(nombre, listaParametros));
        sb.Append(");");
      }
      else
        sb.Append(WS_Clases.GetOpcionJSON(nombre, listaParametros));

      Context.Response.Clear();
      Context.Response.ContentType = "application/json";
      Context.Response.Write(sb.ToString());
      Context.Response.End();
    }

  }
}

Y eso es todo. Potente, ¿verdad?. Ahora sólo debemos modificar el fichero XML a nuestro gusto para adaptarlo a nuestras necesidades (ojo, recordar introducir algún sistema de seguridad, y a ser posible, excluir el fichero XML en el fichero robots.txt de nuestro servidor para que no sea indexado).

En otra entrada veremos como consumir un webservice.

Enlazando a Crystal Reports con .NET

En esta entrada vamos a crear una clase genérica que nos permita interactuar con informes realizados en Crystal Reports. La clase se logará en la base de datos, ejecutará los informes y nos los devolverá en el formato que nos interese. Si examinamos el código de la clase, realizado en C#, no son necesarias muchas explicaciones. Veámoslo pues:
// *************************************************************************
// CLASE PARA CREAR INFORMES UTILIZANDO CRYSTAL REPORTS (BUSINESS OBJECT)
//
// Referencias a importar (C:\Archivos de Programa\Business Objects\Common\3.5\managed\dotnet2)
//    CrystalDecisions.CrystalRports.Engine
//    CrystalDecisions.Shared
//
// Manuel Toscano Blancas
// *************************************************************************

using CrystalDecisions.CrystalReports.Engine;
using CrystalDecisions.Shared;
using System;
using System.Collections;

namespace GestorInformes
{
  class InformesCrystalReports
  {

    #region " Variables miembro "

    // Informacion de conexion a la BD
    private static string _bbdd_servidor;
    private static string _bbdd_nombre;
    private static string _bbdd_usuario;
    private static string _bbdd_clave;
    private static bool _bbdd_seguridadIntegrada;

    #endregion

    #region " Métodos públicos "

    /// <summary>
    /// Establecer las propiedades de conexión a la base de datos para los informes.
    /// </summary>
    /// <param name="servidor"></param>
    /// <param name="nombreBBDD"></param>
    /// <param name="usuario"></param>
    /// <param name="clave"></param>
    /// <param name="seguridadIntegrada"></param>
    public static void SetInfoConexion(string servidor, string nombreBBDD, string usuario, string clave, bool seguridadIntegrada)
    {
      _bbdd_servidor = servidor;
      _bbdd_nombre = nombreBBDD;
      _bbdd_usuario = usuario;
      _bbdd_clave = clave;
      _bbdd_seguridadIntegrada = seguridadIntegrada;
    }

    /// <summary>
    /// Generar informe y devolver resultado en forma de fichero en PDF.
    /// </summary>
    /// <param name="ficheroRPT"></param>
    /// <param name="hashTableFormulas"></param>
    /// <param name="ficheroPDF"></param>
    public static void ReportToPDF(string ficheroRPT, Hashtable hashTableFormulas, string ficheroPDF)
    {
      try
      {
        ReportDocument rpt;
        rpt = GetResultadoRPT(ficheroRPT, hashTableFormulas);
        rpt.ExportToDisk(ExportFormatType.PortableDocFormat, ficheroPDF);
        rpt.Close();
        rpt.Dispose();
      }
      catch (Exception ex)
      {
        throw new Exception(ex.InnerException.ToString());
      }
    }

    /// <summary>
    /// Generar informe y devolver resultado por impresora.
    /// </summary>
    /// <param name="ficheroRPT"></param>
    /// <param name="hashTableFormulas"></param>
    /// <param name="nombreImpresora"></param>
    /// <param name="numeroCopias"></param>
    /// <param name="dobleCara"></param>
    public static void ReportToPrinter(string ficheroRPT, Hashtable hashTableFormulas, string nombreImpresora = "", int numeroCopias = 1, bool dobleCara = false)
    {
      try
      {
        ReportDocument rpt;
        rpt = GetResultadoRPT(ficheroRPT, hashTableFormulas);
        // Se podrian configurar otras opciones de impresion con rpt.PrintOptions 
        rpt.PrintOptions.PrinterName = nombreImpresora;
        rpt.PrintOptions.PaperOrientation = CrystalDecisions.Shared.PaperOrientation.DefaultPaperOrientation;
        rpt.PrintOptions.PaperSize = CrystalDecisions.Shared.PaperSize.DefaultPaperSize;
        rpt.PrintOptions.PrinterDuplex = CrystalDecisions.Shared.PrinterDuplex.Default;
        rpt.PrintOptions.PaperSource = CrystalDecisions.Shared.PaperSource.Auto;
        if (dobleCara)
        {
          if (rpt.PrintOptions.PaperOrientation == PaperOrientation.Portrait)
            rpt.PrintOptions.PrinterDuplex = CrystalDecisions.Shared.PrinterDuplex.Vertical;
          else if (rpt.PrintOptions.PaperOrientation == PaperOrientation.Landscape)
            rpt.PrintOptions.PrinterDuplex = CrystalDecisions.Shared.PrinterDuplex.Horizontal;
          else
            rpt.PrintOptions.PrinterDuplex = CrystalDecisions.Shared.PrinterDuplex.Default;
        }
        // Imprimir
        rpt.PrintToPrinter(numeroCopias, true, 0, 0);
        rpt.Close();
        rpt.Dispose();
      }
      catch (Exception ex)
      {
        throw new Exception(ex.InnerException.ToString());
      }
    }

    #endregion

    #region " Métodos privados para la generación del informe "

    private static ReportDocument GetResultadoRPT(string ficheroRPT, Hashtable hashTableFormulas = null)
    {
      try
      {
        // Validaciones
        if (!System.IO.File.Exists(ficheroRPT)) throw new ApplicationException("No se encontró el fichero de reporte " + ficheroRPT);

        // Informe
        CrystalDecisions.CrystalReports.Engine.ReportDocument rpt = new CrystalDecisions.CrystalReports.Engine.ReportDocument();
        rpt.Load(ficheroRPT);
        // Pasar fórmulas
        if (hashTableFormulas != null && hashTableFormulas.Count > 0)
        {
          foreach (string key in hashTableFormulas.Keys)
            SetFormula(rpt, key, hashTableFormulas[key].ToString());
        }
        // Logarse en todas las tablas
        LoginEnTablas(rpt);

        return rpt;
      }
      catch (Exception ex)
      {
        throw new Exception(ex.InnerException.ToString());
      }
    }

    private static void SetFormula(ReportDocument rpt, string nombre, string valor)
    {
      try
      {
        rpt.DataDefinition.FormulaFields[nombre].Text = "\"" + valor.ToString() + "\"";
      }
      catch (Exception ex)
      {
        throw new Exception("Fórmula no válida. " + ex.Message);
      }
    }

    private static void LoginEnTablas(ReportDocument rpt)
    {
      try
      {
        CrystalDecisions.Shared.TableLogOnInfo tableLogonInfo = new CrystalDecisions.Shared.TableLogOnInfo();
        CrystalDecisions.Shared.ConnectionInfo connectionInfo = new CrystalDecisions.Shared.ConnectionInfo();

        connectionInfo.ServerName = _bbdd_servidor;
        connectionInfo.DatabaseName = _bbdd_nombre;
        connectionInfo.UserID = _bbdd_usuario;
        connectionInfo.Password = _bbdd_clave;
        connectionInfo.IntegratedSecurity = _bbdd_seguridadIntegrada;

        // Tablas del informe
        foreach (CrystalDecisions.CrystalReports.Engine.Table table in rpt.Database.Tables)
        {
          if (table.LogOnInfo.ConnectionInfo.DatabaseName == connectionInfo.DatabaseName)    // Esto permite cambiar el login solo en tablas de la misma BBDD, no interfiere si son otras como por ejemplo XML
          {
            tableLogonInfo = table.LogOnInfo;
            tableLogonInfo.ConnectionInfo = connectionInfo;
            table.ApplyLogOnInfo(tableLogonInfo);
            if (table.Location.Contains("."))
              table.Location = _bbdd_nombre + ".dbo.[" + table.Location + "]";
          }
        }

        // Tablas de los SubInformes
        for (int i = 0; i < rpt.Subreports.Count; i++)
        {
          foreach (CrystalDecisions.CrystalReports.Engine.Table table in rpt.Subreports[i].Database.Tables)
          {
            if (table.LogOnInfo.ConnectionInfo.DatabaseName == connectionInfo.DatabaseName)    // Esto permite cambiar el login solo en tablas de la misma BBDD, no interfiere si son otras como por ejemplo XML
            {
              tableLogonInfo = table.LogOnInfo;
              tableLogonInfo.ConnectionInfo = connectionInfo;
              table.ApplyLogOnInfo(tableLogonInfo);
              if (table.Location.Contains("."))
                table.Location = _bbdd_nombre + ".dbo.[" + table.Location + "]";
            }
          }
        }

        tableLogonInfo = null;
        connectionInfo = null;
      }
      catch (Exception ex)
      {
        throw new Exception(ex.InnerException.ToString());
      }

    }

    #endregion

  }
}
La utilización de la clase genérica anterior, podría resumirse en el código siguiente, que sería el que utilizaríamos en nuestra aplicación:
System.Collections.Hashtable htFormulas = new System.Collections.Hashtable();
htFormulas.Clear();
htFormulas.Add("Codigo",dt.Rows[i]["ParametrosGeneracion"].ToString());
InformesCrystalReports.ReportToPDF(dt.Rows[i]["FicheroOrigen"].ToString(), htFormulas, ficheroDestinoPDF);
Eso crea un PDF con el resultado de ejecutar el informe, pero como hemos incluido otros métodos, podríamos haber hecho que directamente saliera por impresora, o bien recoger el objeto resultante para mostrarlo por pantalla en un contenedor de report.

Ficheros INI en .NET

Actualmente suelen utilizarse para almacenar configuraciones archivos XML o incluso JSON, pero existen ciertos escenarios en los que nos encontramos que debemos trabajar con ficheros .ini. Un fichero ini es un fichero de texto en el que almacenar configuraciones, que pueden ir incluidas dentro de grupo. Mejor verlo con un ejemplo:
[BaseDatos]
; Base de datos
BD_Servidor=TuServidorSQL
BD_Nombre=NombreDeTuBBDD
BD_SegIntegrada=Si
BD_Usu=
BD_Clave=
El fichero anterior crear un grupo, BaseDatos, para incluir dentro configuraciones relativas a la misma. Un fichero ini puede contener tantos grupos como sean necesarios.

Para trabajar con ficheros ini desde .NET, basta con definir en nuestro proyecto una clase como la siguiente:
using System;
using System.Runtime.InteropServices;
using System.Text;

namespace Ini
{
  /// <summary>
  /// Create a New INI file to store or load data
  /// </summary>
  public class IniFile
  {
    public string path;

    [DllImport("kernel32")]
    private static extern long WritePrivateProfileString(string section, string key, string val, string filePath);
    [DllImport("kernel32")]
    private static extern int GetPrivateProfileString(string section, string key, string def, StringBuilder retVal, int size, string filePath);

    /// <summary>
    /// INIFile Constructor.
    /// </summary>
    /// <PARAM name="INIPath"></PARAM>
    public IniFile(string INIPath)
    {
      path = INIPath;
    }

    /// <summary>
    /// Write Data to the INI File
    /// </summary>
    /// <PARAM name="Section"></PARAM>
    /// Section name
    /// <PARAM name="Key"></PARAM>
    /// Key Name
    /// <PARAM name="Value"></PARAM>
    /// Value Name
    public void IniWriteValue(string Section, string Key, string Value)
    {
      WritePrivateProfileString(Section, Key, Value, this.path);
    }

    /// <summary>
    /// Read Data Value From the Ini File
    /// </summary>
    /// <PARAM name="Section"></PARAM>
    /// <PARAM name="Key"></PARAM>
    /// <PARAM name="Path"></PARAM>
    /// <returns></returns>
    public string IniReadValue(string Section, string Key)
    {
      StringBuilder temp = new StringBuilder(255);
      int i = GetPrivateProfileString(Section, Key, "", temp, 255, this.path);
      return temp.ToString();
    }

  }
}
Su uso podría ser como sigue:
IniFile ini = new IniFile(Application.StartupPath + @"\config.ini");

Globales.bbdd_servidor = ini.IniReadValue("BaseDatos", "Servidor");
Globales.bbdd_nombre = ini.IniReadValue("BaseDatos", "Nombre");
Globales.bbdd_seguridadIntegrada = (ini.IniReadValue("BaseDatos", "SeguridadIntegrada").ToUpper()=="SI")?true:false;
Globales.bbdd_usuario = ini.IniReadValue("BaseDatos", "Usuario");
Globales.bbdd_clave = ini.IniReadValue("BaseDatos", "Clave");
Es todo.

Sencilla clase de acceso a datos (SQL Server) con .NET

Una de las cosas más básicas que necesitaremos hacer con nuestros proyectos .NET, es sin duda conectarnos a una fuente de datos. .NET dispone de una serie de clases que nos facilitan esta tarea. Habitualmente suelo utilizar SQL Server (aunque cualquier fuente de datos es aceptada), y a continuación vamos a crear una pequeña clase, muy simple, para obtener acceso a datos en este gestor.
El ejemplo crea un clase muy simple, que puede ser compilada como una DLL y que nos puede ayudar a ralizar aplicaciones en 3 capas. La clase que aquí vamos a crear, es simple pero genérica, de forma que podrá ser utilizada desde cualquier aplicación sin modificación alguna (salvo las funcionalidades extra que queramos añadirle).
public class AccesoDatos
{
public static string cadenaConexion = string.Empty;
private SqlConnection conexion;
private SqlDataAdapter da;

public static void SetCadenaConexion(string connString)
{
  if (connString != string.Empty)
    cadenaConexion = connString;
  else
    cadenaConexion = String.Empty;
}

public AccesoDatos()
{
  conexion = new SqlConnection(cadenaConexion);
}

~AccesoDatos()
{
  da.Dispose();
  conexion.Close();
  conexion.Dispose();
}


public DataSet GetDataSet(string SQL)
{
  try
  {
    da = new SqlDataAdapter(SQL, conexion);
    DataSet ds = new DataSet();
    da.Fill(ds);
    return ds;
  }
  catch (Exception ex)
  {
    throw ex;
  }
}

public void UpdateDataSet(DataSet ds)
{
  try
  {
    SqlCommandBuilder cb = new SqlCommandBuilder(da);
    da.InsertCommand = cb.GetInsertCommand();
    da.DeleteCommand = cb.GetDeleteCommand();
    da.UpdateCommand = cb.GetUpdateCommand();
    da.InsertCommand.Connection = conexion;
    da.DeleteCommand.Connection = conexion;
    da.UpdateCommand.Connection = conexion;
    da.Update(ds);
    cb.Dispose();
  }
  catch (Exception ex)
  {
    throw ex;
  }
}

public DataTable GetDataTable(string SQL)
{
  try
  {
    SqlDataAdapter comando = new SqlDataAdapter(SQL, conexion);
    DataSet ds = new DataSet();
    comando.Fill(ds);
    comando.Dispose();
    return ds.Tables[0];
  }
  catch (Exception ex)
  {
    throw ex;
  }
}

public SqlDataReader GetDataReader(string SQL)
{
  try
  {
    conexion.Open();
    SqlCommand comando = new SqlCommand(SQL, conexion);
    SqlDataReader dr = comando.ExecuteReader();
    //conexion.Close();
    comando.Dispose();
    return dr;
  }
  catch (Exception ex)
  {
    throw ex;
  }
}

public string GetScalarValue(string SQL)
{
  try
  {
    conexion.Open();
    SqlCommand comando = new SqlCommand(SQL, conexion);
    string sv = (string)comando.ExecuteScalar();
    conexion.Close();
    comando.Dispose();
    return sv;
  }
  catch (Exception ex)
  {
    throw ex;
  }
}

public int ExecuteSQL(string SQL)
{
  try
  {
    SqlCommand comando = new SqlCommand();
    int resul = 0;
    comando.CommandText = SQL;
    comando.CommandType = CommandType.Text;
    comando.Connection = conexion;
    conexion.Open();
    resul = comando.ExecuteNonQuery();
    conexion.Close();
    return resul;
  }
  catch (Exception ex)
  {
    throw ex;
  }
}

public string GetField(string tabla, string clavePrimaria, string valorBuscado, string campoDevuelto)
{
  DataTable dt = new DataTable();
  string SQL = "";

  try
  {
    SQL = "SELECT " + campoDevuelto + " FROM " + tabla + " WHERE " + clavePrimaria + "='" + valorBuscado + "'";
    dt = this.GetDataTable(SQL);
    if (dt.Rows.Count > 0)
      return dt.Rows[0][campoDevuelto].ToString();

    return "";
  }
  catch (Exception ex)
  {
    return "Error en la búsqueda " + ex.Message;
  }
}


#region " Metodos de obtención de nueva clave primaria "

public string GetPrimaryKey(string tabla, string campo, short longitud)
{
  try
  {
    // SQL para obtener ultimo codigo
    string SQL = "SELECT TOP 1 " + campo + " FROM " + tabla + " ORDER BY " + campo + " DESC";
    SqlDataReader dr = this.GetDataReader(SQL);
    string codigo;

    // Obtener codigo
    if (dr.HasRows)
    {
      dr.Read();
      codigo = Convert.ToString(dr[campo]);
    }
    else
      codigo = "0";

    codigo = Convert.ToString(int.Parse(codigo) + 1);

    // Rellenar de ceros y devolver
    codigo = this.Ceros(codigo, longitud);   // dtTabla.Columns(cCampo).MaxLength)

    dr.Close();
    dr.Dispose();
    return codigo;
  }
  catch (Exception e)
  {
    return e.Message;
  }
}

private string Ceros(string texto, short ceros)
{
  string cadena = texto;
  string _ceros;
  if (ceros > texto.Length)
    _ceros = new string('0', ceros - cadena.Length) + cadena;
  else
    _ceros = cadena.Substring(0, ceros);
  return _ceros;
}

public int GetLastId(string tabla)
{
  return Convert.ToInt32(GetScalarValue("SELECT ident_current('" + tabla + "')"));
}

#endregion
Espero que os sirva.

Lectura de XML con PHP

Con PHP es sumamente sencillo leer y parsear ficheros XML. Para ver como se hace, lo mejor es hacerlo con un ejemplo.

Supongamos el siguiente fichero XML, que puede ser utilizado por un WebService, y que contiene nombres de método para el WebService y la sentencia SQL que debe ejecutar en cada uno de ellos:
<?xml version="1.0" encoding="utf-8" ?>
<OpcionesDisponibles>
  <opcion>
    <nombre>GetPaises</nombre>
    <SQL>SELECT cod_pais,pais FROM paises order by pais</SQL>
  </opcion>
  <opcion>
    <nombre>GetPais</nombre>
    <SQL>SELECT cod_pais,pais FROM paises WHERE cod_pais='#PARAMETRO_1#'</SQL>
  </opcion>
</OpcionesDisponibles>
Ahora, supongamos que desde PHP deseamos tener una función que dado el nombre del método, nos devuelva la SQL a ejecutar. La solución es tan simple como la siguiente:
// Dado un nombre de opción, buscar en el xml su SQL correspondiente
function getSQL($nombreOpcion)
{
  $xmlOpciones = simplexml_load_file('opciones.xml');
  $sql = ""; 
  foreach ($xmlOpciones->opcion as $opcion)
  {
    $nombre = $opcion->nombre;
    if ($nombre == $nombreOpcion)
    {
      $sql = $opcion->SQL;
      break;
    }
  }
  return $sql;
}
Así de sencillo.

Enviar email con PHP

En esta entrada se muestra una sencilla forma para enviar un email desde PHP. Para ello, se recibirán por parámetro (normalmente desde la llamada de un formulario html) el nombre del usuario, su dirección de email y el mensaje. Con estos datos, nuestro archivo php para enviar correo podría ser el siguiente:
<?php
  // Validar parametros
  if ($_REQUEST['nombre'] == '' || $_REQUEST['email'] == '' || $_REQUEST['mensaje'] == '')
    die ('Parámetros incorrectos');
    
  // Email
  $to = 'tu_direccion_de_email_de_destino';
  $subject = '[tu_asunto_por_defecto]';   
  $header = 'From:tu_email_de_origen';
  // Mensaje   
  $message = 'Mensaje de '.$_REQUEST['nombre'].'('.$_REQUEST['email'].')';
  $message .= $_REQUEST['mensaje'];
  // Enviar
  $retval = mail ($to,$subject,$message,$header);
  if( $retval == true )  
  {
     echo "Mensaje enviado correctamente...";
  }
  else
  {
     echo "El mensaje no se pudo enviar...";
  }
?>
Este método es muy simple, y nos puede servir para empezar, pero a partir de el pueden hacerse cosas más complejas (copias ocultas, ficheros adjuntos, etc). Eso ya queda a las necesidades de cada situación.

Envio de email con .NET

Esta entrada muestra un método genérico realizado en C# que se puede utilizar para el envío de emails desde nuestra aplicación. No necesita mayor explicación, así que, manos a la obra.
public static void EnviarMail(string cuentaDe, string cuentaSMTP, string cuentaUsuario, string cuentaClave, string para, string asunto, string texto, string paraCopia = "", string paraOculto = "", string ficheroAdjunto = "", string tituloMail = "[TituloPorDefecto] ")
{
  try
  {
    System.Net.Mail.MailMessage correo = new System.Net.Mail.MailMessage();
    System.Net.Mail.SmtpClient smtp = new System.Net.Mail.SmtpClient();

    // SMTP
    smtp.Host = cuentaSMTP;
    smtp.Port = 25;
    //smtp.EnableSsl = True
    if (cuentaUsuario != "")
      smtp.Credentials = new System.Net.NetworkCredential(cuentaUsuario, cuentaClave);

    // De
    correo.From = new System.Net.Mail.MailAddress(cuentaDe);
    // Para
    if (para.Contains(";"))
    {
      string[] mails = para.Split(';');
      for (int i = 0; i < mails.Length; i++)
        correo.To.Add(mails[i]);
    }
    else
      correo.To.Add(para);

    // Para copia
    if (paraCopia.Contains(";"))
    {
      string[] mails = paraCopia.Split(';');
      for (int i = 0; i < mails.Length; i++)
        correo.CC.Add(mails[i]);
    }
    else if (paraCopia != "")
      correo.CC.Add(paraCopia);

    // Para oculto
    if (paraOculto.Contains(";"))
    {
      string[] mails = paraOculto.Split(';');
      for (int i = 0; i < mails.Length; i++)
        correo.Bcc.Add(mails[i]);
    }
    else if (paraOculto != "")
      correo.Bcc.Add(paraOculto);

    // Correo
    correo.Subject = tituloMail + asunto;
    correo.Body = texto;
    correo.IsBodyHtml = true;
    correo.Priority = System.Net.Mail.MailPriority.Normal;
    
    // Adjunto
    if (ficheroAdjunto != "")
    {
      if (System.IO.File.Exists(ficheroAdjunto))
      {
        System.Net.Mail.Attachment attach = new System.Net.Mail.Attachment(ficheroAdjunto);
        correo.Attachments.Add(attach);
      }
    }
    // Enviar
    smtp.Send(correo);

  }
  catch (Exception ex)
  {
    Log(ex.Message);
  }
}
Y ya está.

Consumir un WebService JSON desde JavaScript con Ajax

En muchas ocasiones se nos hace necesario, desde una página HTML, realizar la llamada a un webservice para obtener una serie de resultados. Una buena forma de conseguir esto es utilizar Ajax. Existen múltiples formas de realizar la llamada, y en este post indicaremos simplemente una de ellas. Espero que os sea de utilidad.
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Test consumir WebService</title>
  <script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

  <script type="text/javascript">
    // Procesar resultados del webservice
    function webServiceResult(data)
    {
      $("#resultado_json").empty();    

      $("#resultado_json").append("Nombre: "+data.name+"<br>");
      $("#resultado_json").append("Pais: "+data.sys.country+"<br>");
      $("#resultado_json").append("Longitud: "+data.coord.lon+"<br>");
      $("#resultado_json").append("Latitud: "+data.coord.lat+"<br>");
      $("#resultado_json").append("Tiempo: "+data.weather[0].main+"<br>");
      $("#resultado_json").append("&nbsp;&nbsp;"+data.weather[0].description+"<br>");
      $("#resultado_json").append("Humedad: "+data.main.humidity+"<br>");
      var tmin = Math.round(10*(data.main.temp_min -273.15)) / 10;
      var tmax = Math.round(10*(data.main.temp_max -273.15)) / 10 ;
      $("#resultado_json").append("Temp. minima: "+tmin+"<br>");
      $("#resultado_json").append("Temp. maxima: "+tmax+"<br>");
    }
      
    // Llamada al webservice
    function callWebService()
    {
      try
      { 
        $.ajax({
          url: "http://api.openweathermap.org/data/2.5/weather",
          data:
          {
              q: "Cordoba,es",
          },
          type: "get",
          async: true,
          contentType: "text/json; charset=utf-8",
          dataType: "jsonp",        
          success: function(msg) { webServiceResult(msg) },
          error: function(jqXmlHttpRequest, textStatus, errorThrown) { alert("Error leyendo datos."); }
        });
      } 
      catch (err) 
      {
        alert(err);
      }
    }
  </script>
  
</head>

<body>

<button type="button" onclick="javascript:callWebService();">Llamar a WS</button>

<div id="resultado_json"></div>

</body>
</html>
Un ejemplo más elaborado, e incluso cargando los datos a consultar desde un fichero JSON, se puede consultar aqui.

Consumir un WebService XML desde .NET

En esta entrada publicamos una función que nos permite consumir un webservice XML y pasarlo a un DataTable, de forma que podamos trabajar con los datos de manera muy sencilla. La función es la siguiente:
Public Shared Function GetDataTable_WebServiceXML(opcion As String, listaParametros As String) As Data.DataTable
  Try
    Dim url As String = "http://url.com/WebService.asmx/GetOpcion"
    url += "?nombre=" + opcion + "&parametros=" + listaParametros

    Dim request As HttpWebRequest
    Dim response As HttpWebResponse = Nothing
    Dim ds As New Data.DataSet

    request = DirectCast(WebRequest.Create(url), HttpWebRequest)
    response = DirectCast(request.GetResponse(), HttpWebResponse)
    ds.ReadXml(response.GetResponseStream())

    If ds.Tables.Count > 0 Then
      Return ds.Tables(0)
    Else
      Return New Data.DataTable
    End If

  Catch ex As Exception
    Throw New Exception(ex.Message)
  End Try
End Function
Con el código de esta pequeña función, obtendremos un datatable con los resultados obtenidos. Si fuera necesario podríamos modificarla para devolver un DataSet o directamente el contenido del XML.

Log sencillo en .NET

En muchas ocasiones tenemos la necesidad de hacer un seguimiento de procesos en tiempo de ejecución, bien para depurar errores, bien para dejar un registro de lo ocurrido, o por cualquier otra necesidad. Para conseguir esto, habitualmente se utilizan ficheros logs.

Esta entrada muestra como crear un log muy básico y sencillo (no se da menos) para nuestras aplicaciones .NET. Se trata de un procedimiento muy simple y nada sofisticado, pero que nos puede servir de punto de partida para, por ejemplo, crear una clase de log personalizada, o para extenderlo según nuestras necesidades. Manos a la obra:

En VB
Public Shared Sub Log(texto As String)
  Dim fichero_log As New IO.StreamWriter(System.AppDomain.CurrentDomain.BaseDirectory + "/seguimiento.log", True)
  fichero_log.WriteLine(texto)
  fichero_log.Flush()
  fichero_log.Close()
End Sub
En C#
public static void Log(string msg)
{
  if (msg.Length > 0)
  {
    using (StreamWriter sw = File.AppendText(System.AppDomain.CurrentDomain.BaseDirectory+"\\seguimiento.log"))
    {
      sw.WriteLine("[{0} {1}] {2}", DateTime.Now.ToShortDateString(), DateTime.Now.ToShortTimeString(), msg);
      sw.Flush();
    }
  }
} 

Registros de SQL hacia XML/JSON

Nuestra aplicación de gestión en la empresa está desarrollada en .NET y utilizando base de datos SQL Server. Se trata de una aplicación de escritorio, y debido a ciertas necesidades, hemos construido un WebService para hacer disponibles ciertos datos desde el exterior, para que sean consumidos por la web de eCliente y por varias apps.

Hemos configurado el webservice para que devuelva los datos en el formato deseado, XML o JSON, así quien lo consuma podrá elegir cómo quiere los datos. En eCliente utilizamos .NET, por lo que se hace más sencillo obtener los resultados en un XML (directamente creamos un DataSet a partir de él se hace mucho más fácil su gestión), y en las apps lo consumimos con JSON.

En ambos casos nos hemos encontrado con un problema: tenemos registros que tienen caracteres que, bien hacen que se rompan el XML, o bien el HTML, debido a que contienen caracteres no permitidos para estos formatos.

Para evitar este problema, hemos creado una función escalar en SQL Server que nos reemplaza los caracteres no deseados para que el XML/JSON sea correcto. La función es la siguiente:
USE [BaseDatos]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[CampoToHTML] (@texto varchar(256)) returns varchar(max)
begin
   declare @resul varchar(max)
   set @resul = @texto
   set @resul = REPLACE(@resul, char(34), char(39))
   set @resul = REPLACE(@resul, 'ñ' COLLATE Latin1_General_CS_AS,'n')
   set @resul = REPLACE(@resul, 'á' COLLATE Latin1_General_CS_AS,'a')
   set @resul = REPLACE(@resul, 'é' COLLATE Latin1_General_CS_AS,'e')
   set @resul = REPLACE(@resul, 'í' COLLATE Latin1_General_CS_AS,'i')
   set @resul = REPLACE(@resul, 'ó' COLLATE Latin1_General_CS_AS,'o' )
   set @resul = REPLACE(@resul, 'ú' COLLATE Latin1_General_CS_AS,'u')
   set @resul = REPLACE(@resul, 'Ñ' COLLATE Latin1_General_CS_AS,'N')
   set @resul = REPLACE(@resul, 'Á' COLLATE Latin1_General_CS_AS,'A')
   set @resul = REPLACE(@resul, 'É' COLLATE Latin1_General_CS_AS,'E')
   set @resul = REPLACE(@resul, 'Í' COLLATE Latin1_General_CS_AS,'I')
   set @resul = REPLACE(@resul, 'Ó' COLLATE Latin1_General_CS_AS,'O')
   set @resul = REPLACE(@resul, 'Ú' COLLATE Latin1_General_CS_AS,'U')
   set @resul = REPLACE(@resul, '&' COLLATE Latin1_General_CS_AS,'y')
   return @resul
END
De esta forma, la consulta SQL la haríamos de la siguiente forma:
SELECT id,dbo.CampoToHTML(apellidos) FROM Clientes
Esto es todo.

Consumir un webservice desde PHP (XML/JSON)

Cuando trabajamos con PHP, es muy habitual encontrarnos con la necesidad de realizar la llamada a un webservice para obtener una serie de resultados para después trabajar con ellos. Existen múltiples formas de conseguir esto, y en este post vamos a mostrar dos posibilidades muy sencillas para conseguirlo. Mostraremos dos ejemplos, uno que recibe los resultados en formato XML y otro en formato JSON. Una vez recibidos los resultados, recorremos la información obtenida para mostrarla por pantalla.

Si el webservice devuelve resultados en XML
<?php
  // Llamada al WebService
  $client = new SoapClient("http://www.webservicex.net/country.asmx?WSDL");
  $result = $client->GetCountries();
  $xml = $result->GetCountriesResult;

  // procesar xml
  $xml = simplexml_load_string($xml);
  foreach($xml->Table as $table) 
  {
    $output .= "<p>$table->Name</p>";
  }
  print_r($output);
?>

Si el webservice devuelve resultados en JSON
<?php
// Función para llamar al webservice y devolver el resultado en un array
function callWebService($method)
{
  $url ='http://api.geonames.org/citiesJSON?north=44.1&south=-9.9&east=-22.4&west=55.2&lang=de&username=demo';
  $json = file_get_contents($url);
  $array = json_decode($json,true);
  return $array;
}
Esa misma función puede optimizarse en una sóla línea:
function callWebService($method)
{
  return json_decode(file_get_contents('http://api.geonames.org/citiesJSON?north=44.1&south=-9.9&east=-22.4&west=55.2&lang=de&username=demo'),true);;
}
La llamada podría se la siguiente:
$resul = callWebService();
foreach($resul['geonames'] as $city)
{
    $cities .= '<p>'.$city['name'].'</p>';    
}
print_r ($cities);

Espero os sea de utilidad. Si deseas obtener más información, puedes consultar la documentación de php.

lunes, 2 de febrero de 2015

Instalación de un SSD en Windows: Optimizando el rendimiento

Los discos de estado sólido o SSD son una gran alternativa para mejorar el rendimiento de un ordenador. Estos discos tienen un acceso mucho más rápido que un disco mecánico tradicional, y mejoran sustancialmente la carga de los programas. Debido a su tamaño actual, lo recomendable es utilizar un disco SSD para el sistema operativo y las instalaciones de software, y un disco mecánico más grande para el almacenamiento de información.

El precio de los SSD es cada vez más asequible, a la vez que mejoran sus prestaciones. Recientemente he adquirido el Crucial MX 1000 de 256 Gb. Una vez instalado un SSD (para sacarle el máximo rendimiento es recomendable una conexión SATA3, aunque en SATA2 también mejoran la velocidad de acceso de una forma más que notable), debido a que disponen de un ciclo de escrituras limitado, es conveniente para alargar su vida realizar una serie de procesos. Estos son los pasos recomendables si utilizas Windows:

Configuración previa

Lo primero que hay que hacer, sino está así ya que suele ser lo habitual, es configurar la Bios de la placa base para que emplee el sistema de transmisión de datos AHCI en lugar del tradicional IDE. Ello es debido a que el estándar IDE es bastante más lento (133 MB/s) que las velocidades máximas de transmisión que se alcanzan con los actuales con los actuales SATA 2 (300 MB/s) y SATA 3 (600 MB/s), con lo que se estaría reduciendo considerablemente el rendimiento de la unidad.

Otro dato a tener en cuenta es el alineamiento de los sectores del SSD. A pesar que los discos duros mecánicos tradicionalmente emplean un alineamiento de 512 bytes por sector, los SSD emplean un alineamiento de 4 kB por sector. Por ello, es recomendable particionar y formatear la unidad antes de instalarle el sistema operativo, dado que estos valores no se podrán modificar con el mismo ya instalado.

Configuración del disco sólido

Una vez realizada la instalación de Windows 7, procederemos a realizar los siguientes cambios:

Desactivar hibernación.
A no ser que realmente queráis que vuestro sistema pueda hibernar, si se tiene un SSD es conveniente desactivar esta característica de Windows dado que supone un gasto innecesario de ciclos de escritura en estas unidades, ya que cada vez que el sistema entra en este estado, toda la información almacenada en la RAM se escribe el la unidad que almacena el sistema operativo. Para hacer ésto, lo primero es entrar en el Panel de Control de Windows e ir la la sección de Sistema. Una vez dentro, ir a Opciones de energía. Ahí, pulsar sobre la opción "ostrar planes adicionales. Seleccionar Alto rendimiento, y Cambiar configuración del plan, que sale a su derecha. En la opción Poner el disco en supensión, debería aparecer Nunca.

Desactivar superfetch.
Superfetch es una característica de Windows que precarga los programas de uso más habitual en la memoria de Windows para así acelerar su ejecución. Dada la rapidez de acceso que tienen los discos SSD, no es necesario tener esta característica activada, liberarando además espacio en RAM. Para desactivar esta característica, hacer click con el botón derecho sobre el icono de Equipo en el escritorio y seleccionar Administrar. En la ventana que se abre, seleccionaremos Servicios. Buscamos la entrada Superfetch, se le hace doble click y se deshabilita el inicio.

Desactivar la caché de escritura
La caché de escritura mejora considerablemente el rendimiento de los discos duros mecánicos, pero en algunos modelos de estado sólido (los que llevan controladoras SandForce) no sirve de nada, por lo que es mejor desactivarla. Para ello, hacer click con el botón derecho de ratón en la unidad C y seleccionar Propiedades. Ir a la pestaña Hardware. Seleccionar la unidad SSD pulsar el botón de Propiedades. En la ventana que aparece seleccionar Deshabilitar la caché de escritura.

Desacativar la indexación de la unidad
La indexación permite a Windows conocer en todo momento dónde se han guardado los archivos en la superficie del disco duro. En el caso de los discos mecánicos es muy útil pero en los discos sólidos no tiene sentido por su forma de funcionamiento (no hay que mover las cabezas lectoras), así que puede desactivarse. Como en el paso anterior, haremos click con el botón derecho del ratón sobre el icono de la unidad C y seleccionamos Propiedades. En la ventana que se abra, quitaremos el tick de selección en Permitir que los archivos de esta unidad tengan el contenido indizado además de las propiedades de archivo.

Windows pedirá confirmación, seleccionar la opción de carpeta, subcarpetas y ficheros. Cuando salga la ventana de permisos restringidos, simplemente le damos a Continuar. Si sale una ventana de error, pulsar Ignorar todo. Este proceso tardará un tiempo y una vez finalizado, la desindexación está terminada.

Desactivar desfragmentador
Windows lleva activado de serie un desfragmentador cuya función es mantener los discos en estado óptimo y que se ejecuta cada vez que el disco está inactivo. Dado que el desfragmentador crea una tarea en que los datos se mueven dentro del propio disco, con sus correspondientes ciclos de lectura y escritura, no nos interesa tenerlo activado en una unidad de estado sólido.Además, en un SSD el acceso no es secuencial. Para desactivar la desfragmentación volvemos a entrar en las caraterísticas del disco y seleccionamos la pestaña de Herramientas. En ella, seleccionamos Defragmentar ahora. En la ventana que aparecerá, seleccionamos la opción del programador y allí lo desactivamos.

Ojo que al hacer ésto desactivamos esta programación para todos los discos duros, así que deberemos de estar al tanto por nuestra cuenta de su estado.

Con estos pasos tan sencillos habremos configurado correctamente nuestra unidad de estado sólido y conseguiremos que su vida útil sea más prolongada.

Fuente: Dodeka.

domingo, 1 de febrero de 2015

Plugins para Sublime Text

Si trabajos con SublimeText, éste puede extenderse con plugins para hacernos la vida más fácil. En este post hablaremos de dos plugins muy interesantes.

Para poder instalar estos plugins, haremos uso de "Package control". Tenemos las instrucciones de instalación en esta página. Básicamente abrimos la consola de SublimeText (View-Show console) y pegamos el texto que se nos indica en la página de instalación. Reiniciamos SublimeText y ya tenemos instalado este plugin.

SublimeCodeIntel (intellisense)
Este plugin sirve para que cuando vamos escribiendo (según el lenguaje en el que estemos programando), SublimeText nos vaya mostrando las distintas posibilidades existentes (digamos que es un auto-completar para nuestro código fuente, al estilo de VisualStudio). Por ejemplo, si estamos editando un CSS, y dentro de body escribimos b, se nos sugiere una lista de posibles valores que empiezan por la letra b (background,bottom, border,...). Conforme vayamos escribiendo, se nos irá filtrando la lista a las posibilidades que tenemos. De esta manera, además de ahorrar tiempo, evitaremos errores de escritura y nos evita tener que aprender de memoria todas las posibilidades.


El plugin se llama SublimeCodeIntel,y su instalación es muy sencilla:
  • Abrir SublimeText y ejecutar la paleta de comandos (Ctrl+Mayus+P).
  • Seleccionar "Package control: Install Package"
  • En la lista resultante, seleccionar SublimeCodeIntel. Esperamos a que se instale y reiniciamos SublimeText.
La ventaja de este método es que siempre obtendremos la última versión del plugin. Más información sobre este plugin se puede encontrar en su página oficial.

Emmet
Emmet es un plugin para crear estructuras html con muy pocas pulsaciones de teclado. Para ello se siguen una serie de reglas que se pueden consultar en la página oficial.

Su instalación se consigue, una vez instalado Package Control como se indica más arriba, mediante los siguientes sencillos pasos:
  • Abrir SublimeText y ejecutar la paleta de comandos (Ctrl+Mayus+P).
  • Seleccionar "Package control: Install Package"
  • En la lista resultante, seleccionar Emmet. Esperamos a que se instale y reiniciamos SublimeText.
Esto es todo.

 

Copyright @ 2015 Tosblama