Skip to content

Zona Horaria al Serializar DateTime en WebService

duratimezone_600px_animation[1]

Puede que alguna vez les haya pasado que al serializar un DateTime por un servicio web (o WCF), este haya llegado al otro extremo representando una hora distinta. ¿Cómo puede ser esto si la estrucutra DateTime de C# es totalmente agnóstica de la zona horaria?

Bueno, esto ocurre cuando el serializador no es agnóstico de la zona horaria y es exactamente lo que pasa cuando se transfieren DataSets (y por tanto DataTables) por un servicio, el serializador del DataSet se encarga de levantar la información de zona horaria del servidor y serializar las fechas teniendo en cuenta esto.

Para evitar que el DataSet serialice la información de la Zona Horaria, se debe hacer una rápida configuración a las columnas de tipo DateTime y fijar la propiedad “DateTimeMode” en “DataSetDateTime.Unspecified”.

A continuación les dejo un par de métodos de extensión que les aliviarán el trabajo, sólo deberán llamar al método “ConfigurarZonaHoraria” desde cualquier DataTable o DataSet para autimáticamente configurar todas las columnas de tipo DateTime. Asegúrense de realizar esta llamada cada vez que van a retornar un DataSet por un servicio.


///
<summary>
/// Configura las columnas de tipo DateTime para que sean agnósticas de la zona horaria al momento de la serialización.
/// </summary>

/// <param name="dt"></param>
/// <returns></returns>
public static DataTable ConfigurarZonaHoraria(this DataTable dt)
{
    foreach (DataColumn dc in dt.Columns)
    {
        if (dc.DataType == typeof(DateTime))
        dc.DateTimeMode = DataSetDateTime.Unspecified;
    }

    return dt;
}

///
<summary>
/// Configura las columnas de tipo DateTime para que sean agnósticas de la zona horaria al momento de la serialización.
/// </summary>

/// <param name="ds"></param>
/// <returns></returns>
public static DataSet ConfigurarZonaHoraria(this DataSet ds)
{
    foreach (DataTable dt in ds.Tables)
        dt.ConfigurarZonaHoraria();

    return ds;
}

 

Anuncios

Obtener fecha de últimos cambios de tablas, funciones, procedimientos de almacenado y vistas

Si deseamos obtener un listado de los objetos de nuestra base de datos, la fecha de creación y la fecha de eliminación, podemos hacerlo con una sencilla consulta

Los tipos de objeto que lista son

  • SYSTEM_TABLE
  • VIEW
  • SQL_STORED_PROCEDURE
  • FOREIGN_KEY_CONSTRAINT
  • SERVICE_QUEUE
  • USER_TABLE
  • PRIMARY_KEY_CONSTRAINT
  • INTERNAL_TABLE
  • SQL_SCALAR_FUNCTION
  • UNIQUE_CONSTRAINT

SELECT *
FROM sys.objects
ORDER BY modify_date desc

Hacer Join de 2 DataTables

Por motivos de pega tuve que hacer el join de 2 DataTables con largos dinámicos, pero un campo en común. En mi caso el campo Fecha, de tipo DateTime. La solución original estaba restringida a comparar por la columna id de tipo int, después de modificarla un poco quedó algo así.

public DataTable DataTableJoiner<T>(DataTable dt1, DataTable dt2, string nombreCampo)
{
    using (DataTable targetTable = dt1.Clone())
    {
        var dt2Query = dt2.Columns.OfType<DataColumn>().Select(dc => new DataColumn(dc.ColumnName, dc.DataType, dc.Expression, dc.ColumnMapping));
        var dt2FilterQuery = from dc in dt2Query.AsEnumerable()
                                where targetTable.Columns.Contains(dc.ColumnName) == false
                                select dc;
        targetTable.Columns.AddRange(dt2FilterQuery.ToArray());
        var rowData = from row1 in dt1.AsEnumerable()
                        join row2 in dt2.AsEnumerable() on row1.Field<T>(nombreCampo) equals row2.Field<T>(nombreCampo)
                        select row1.ItemArray.Concat(row2.ItemArray.Where(r2 => row1.ItemArray.Contains(r2) == false)).ToArray();
        foreach (object[] values in rowData)
            targetTable.Rows.Add(values);
        return targetTable;
    }
}

Conocer todas las tablas, sus columnas y tipos en una Base de Datos SQL Server 2005+

Para listar todas las tablas, sus columnas y los tipos de dato de cada columna en un SQL SERVER 2005+ sólo corran el siguiente script:

SELECT t.TABLE_NAME, c.COLUMN_NAME, c.DATA_TYPE
FROM INFORMATION_SCHEMA.TABLES t
INNER JOIN INFORMATION_SCHEMA.COLUMNS c ON t.TABLE_NAME = c.TABLE_NAME AND t.TABLE_SCHEMA = c.TABLE_SCHEMA
WHERE t.TABLE_NAME <> 'sysdiagrams'
ORDER BY t.TABLE_NAME, c.COLUMN_NAME

Validación del módulo 11 (RUT y Patente en Chile)

Hace bastante tiempo que no escribo una entrada en el blog, llegó la hora de revivirlo 🙂

El módulo 11 es un código de control usado para la validación de un dato al añadir un dígito verificador, en chile es usado para validar el RUN (Rol Único Nacional), RUT (Rol Único  Tributario) y la Patente de los vehículos (no sé si se utilizará para otras cosas).

Esta entrada está enfocada en la validación del módulo 11 y la aplicación en C# para validar un RUN/RUT y una Patente chilena.

El módulo 11: El método denominado módulo 11 detecta errores en un solo dígito e intercambios simples o dobles. Se basa en aplicar un factor de chequeo ponderado a cada dígito del número original.

El Registro Civil (chile) ofrece manuales para validar correctamente el RUT/RUN y la patente.

Para validar el módulo 11 tenemos el siguiente código (gentileza de Germán Aroca):

public class Mod11Validator
{
    public int Numero { get; set; }
    public string DigitoVerificador { get; set; }

    public Mod11Validator(int numero, string digitoVerificador)
    {
        this.Numero = numero;
        this.DigitoVerificador = digitoVerificador.ToUpper();
    }

    public bool ObtenerValidezMod11()
    {
        return this.DigitoVerificador == CalcularDigitoVerificador(this.Numero);
    }

    private string CalcularDigitoVerificador(int numero)
    {
        string cadenaNumero = numero.ToString();
        int calculador = 0;

        int[] factores = {3, 2, 7, 6, 5, 4, 3, 2};
        int indiceFactor = factores.Length - 1;

        for (int i = cadenaNumero.Length - 1; i >= 0; i--)
        {
            calculador = calculador + (factores[indiceFactor]*int.Parse(cadenaNumero.Substring(i, 1)));
            indiceFactor--;
        }

        string digitoVerificador;
        int resultado = 11 - (calculador%11);

        if (resultado == 11)
        {
            digitoVerificador = "0";
        }
        else if (resultado == 10)
        {
            digitoVerificador = "K";
        }
        else
        {
            digitoVerificador = resultado.ToString();
        }

        return digitoVerificador;
    }
}

Para validar el RUT/RUN por lo tanto solo debemos llamar a este método de la siguiente forma

Mod11Validator mod11 = new Mod11Validator(numero, dv);
bool esValido = mod11.ObtenerValidezMod11();

Para la validación de patente es un tema distinto, dado que la validación de la patente lleva letras, estas deben ser convertidas en dígitos de acuerdo al manual del registro civil. Entiendo que traspasar todas estas letras a dígitos es realmente aburrido y tedioso, les dejo adjunto 2 archivos CSV con las conversiones de patentes nuevas y antiguas Cortesía de Carlos Muñoz.

Para utilizar estos archivos correctamente con la clase que embedo a continuación, solo deben añadir los archivos a un archivo de recursos y luego mapearlos (ver líneas destacadas en el código). Por cierto, para leer los archivos utilizo el siguiente código:  Leer CSV (archivo separado por coma)

public class PatenteValidator
{
    public class PatenteAntigua
    {
        public static Dictionary<string, string> ValorLetras
        {
            get
            {
                DataTable dt =
                    Utils.Common.Files.FileTools.ByteBufferToTable(ValidatorResources.Resources.DiccionarioPatentesAntiguas, true);
                Dictionary<string, string> d = new Dictionary<string, string>();
                foreach (DataRow dr in dt.Rows)
                    d[dr[0].ToString()] = dr[1].ToString();

                return d;
            }
        }
    }

    public class PatenteNueva
    {
        public static Dictionary<string, string> ValorLetras
        {
            get
            {
                DataTable dt =
                    Utils.Common.Files.FileTools.ByteBufferToTable(ValidatorResources.Resources.DiccionarioPatentesNuevas, true);
                Dictionary<string, string> d = new Dictionary<string, string>();
                foreach (DataRow dr in dt.Rows)
                    d[dr[0].ToString()] = dr[1].ToString();

                return d;
            }
        }
    }

    public static bool ValidarPatente(string patente)
    {
        if (patente.Trim().Length != 8)
            return false;
        if (!patente.Trim().Contains("-"))
            return false;

        int aux;
        bool esPatenteAntigua = int.TryParse(patente.Substring(2,2), out aux);
        int numero;
        string dv;
        if(esPatenteAntigua)
        {
            Dictionary<string, string> valorLetras = PatenteAntigua.ValorLetras;
            string letras = patente.Substring(0, 2);
            if (!valorLetras.ContainsKey(letras.ToUpper()))
                return false;
            string valor = valorLetras[letras.ToUpper()];
            valor += patente.Substring(2, 4);
            dv = patente.Substring(7, 1);
            numero = int.Parse(valor);
        }
        else
        {
            Dictionary<string, string> valorLetras = PatenteNueva.ValorLetras;
            char[] letras = patente.Substring(0, 4).ToCharArray();
            string valor = string.Empty;

            foreach (char c in letras)
            {
                if (!valorLetras.ContainsKey(c.ToString().ToUpper()))
                    return false;
                valor += valorLetras[c.ToString().ToUpper()];
            }

            valor += patente.Substring(4, 2);
            dv = patente.Substring(7, 1);
            numero = int.Parse(valor);
        }

        Mod11Validator mod11Validator = new Mod11Validator(numero, dv);
        return mod11Validator.ObtenerValidezMod11();
    }
}

Leer CSV (archivo separado por coma) como DataTable

Para leer un archivo separado por coma simplemente usen este código 🙂

Lean el archivo como arreglo de bytes y pásenselo a este código. Hará la magia 😉

/// <summary>
/// Convierte el arreglo de bytes buffer en un DataTable.
/// </summary>
/// <param name="buffer">Arreglo de bytes a ser convertido.</param>
/// <param name="includeHeader">True si es que dentro del buffer se encuentra la fila de encabezado.</param>
/// <returns>Datatable con los datos</returns>
public static DataTable ByteBufferToTable(byte[] buffer, bool includeHeader)
{
    DataTable result = new DataTable();

    // Se asume que el separador de decimales es punto "." y el de miles "," (aunque este ultimo no se usa) 
    CultureInfo culture = System.Threading.Thread.CurrentThread.CurrentCulture;
    System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
    Dictionary<string, int> indexs = new Dictionary<string, int>();
    DataTable dt = new DataTable();
    char[] delimiter = new char[] { ';' };
    char[] zero = new char[] { '0' };

    using (StreamReader sr = new StreamReader(new MemoryStream(buffer)))
    {
        try
        {
            int rowsCompleted = 0;
            int lastLength = 0;
            bool readHeader = true;

            while (sr.Peek() > -1)
            {
                bool addLine = true;
                string line = sr.ReadLine();
                string[] lineArray = line.Split(delimiter);

                //Se chequea que tanto el orden como el nombre de las columnas correspondan según el orden dado
                if (readHeader)
                {
                    if (includeHeader)
                    {
                        int j = 0;
                        foreach (string column in lineArray)
                        {
                            DataColumn c = new DataColumn(column);
                            dt.Columns.Add(c);
                            indexs.Add(column, j);
                            j++;
                        }
                        //Se continua con la lectura del archivo
                        line = sr.ReadLine();
                        lineArray = line.Split(delimiter);
                    }
                    else
                    {
                        //Agrego columnas con nombre estandar, no se pasa a la siguiente linea del doc
                        for (int j = 0; j < lineArray.Length; j++)
                        {
                            DataColumn c = new DataColumn("Column" + j);
                            dt.Columns.Add(c);
                            indexs.Add("Column" + j, j);
                        }
                    }
                    //Se cambia el estado de esta variable para no volver a chequear el header
                    readHeader = false;
                }

                DataRow nuevaFila = dt.NewRow();
                if (lastLength > 0 && lastLength != lineArray.Length)
                {
                    continue;
                }
                lastLength = lineArray.Length;
                try
                {
                    foreach (DataColumn column in dt.Columns)
                    {
                        int index = indexs[column.ColumnName];
                        string colName = column.ColumnName;
                        string value = lineArray[index];
                        nuevaFila[colName] = string.IsNullOrEmpty(value) ? DBNull.Value + "" : value;
                    }
                }
                catch (Exception e)
                {
                    throw e;
                }
                if (addLine)
                {
                    dt.Rows.Add(nuevaFila);
                    rowsCompleted++;
                }
            }
            return dt;
        }
        finally
        {
            System.Threading.Thread.CurrentThread.CurrentCulture = culture;
        }
    }
}

Redimensionar ancho de control según texto

Calcular el ancho de un control basado en el texto que contiene y la fuente que usa.

Para esto solo se debe usar el método de extensión:

http://www.extensionmethod.net/csharp/control/resize-to-text-width

Lo que hace es pedirle a graphics que calcule el ancho del string para el control según la fuente del mismo. También se puede modificar para que calcule el alto.

Cómo enviar excepciones personalizadas desde WCF al cliente

Comenzamos aclarando que en WCF, no debemos arrojar excepciones nativas de .Net ya que son contrarias a la naturaleza interoperable de los WCF, después de todo tu cliente puede ser Java o PHP que no tiene el concepto de estas  excepciones .NET

En su lugar, necesitamos arrojar hacia el cliente excepciones por defecto “FaultExceptions” la que funcionan como estándar para WCF.. pero vamos paso a paso.

Creamos el método que potencialmente va a arrojar nuestra excepción en el servidor:


public bool Read()
{
 if (IsUserValid() == false)
 {
 throw new FaultException<InvalidUserFault>("User invalid");
 }
}

En el lado del cliente, “trycatcheamos” la llamada al método de nuestro WCF, hay que tener en cuenta que el “Catch” ya no recibe una excepción genérica, sino la Exepción que nosotros definimos.

<code>try
{
   _client.Read();
}
catch (FaultException<InvalidUserFault> e)
{
    MessageBox.Show(e.Message);
    return;
}</code>

A continuación debemos declarar nuestro InvalidUserFault  como “data contract” y definir que mienbros deben transmitirse, puede ser: Un mensaje de error personalizado, el call stack, la innerException, etc.

[DataContract]
 public class BusinessFault
 {
   [DataMember]
   string Message {get; set;}
   [DataMember]
   string InnerException{get; set;}
}

Por último, para saber desde el lado cliente que nuestro servicio puede arrojar una excepción personalizada, debemos expresarlo en el método del servicio

[FaultContract(typeof(InvalidUserFault)]
[OperationContract]
public bool Read();

fuente original: http://stackoverflow.com/questions/1369882/wcf-web-service-custom-exception-error-to-client

Usar Valores de un enum como flags con FlagsAttribute

The other day, I was using the RegexOptions enum when creating a Regular Expression, and I thought it would be useful to dedicate a blog entry to using enumeration values as flags.

The following is a poor example, but it should be illustrative enough.

Imagine we have a Client class, and one of the Client properties is ClientState. The ClientState enum can be defined as follows:

Leer más en http://weblogs.asp.net/wim/archive/2004/04/07/109095.aspx

Llamar a Dispose solo marcará los recursos para su liberación ¿Verdad?

En mi ignorancia como desarrollador siempre supuse que el método Dispose de las clases que implementan IDisposable hacia una suerte de magia que liberaba recursos, bueno al parecer no es así tal como nos cuenta Eric Lippert en este post de StackOverflow.