lunes, 19 de agosto de 2013

ADO.NET Helper para Oracle (Update)

Todos los programas que utilizan las clases e interfaces del proveedor predeterminado de ADO.NET para Oracle que se encuentran en el ensamblado System.Data.Oraclient deben actualizarse por las clases e interfaces que se encuentran en el ensamblado Oracle.DataAccess.Client, que es el proveedor nativo de Oracle para ADO.NET, este ensamblado se descarga del siguiente enlace Oracle Data Provider for .NET

A continuación el código actualizado de las clases Helper de ADO.NET que publique en este post.

El código de la clase OracleDataBase

El código de la clase OracleDataBaseCommand

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using Oracle.DataAccess.Client;

namespace OracleHelper
{
    internal sealed class OracleDataBaseCommand
    {

internal static int Insert(string commandText, Dictionary parameters, 
System.Data.CommandType cmdtype)
{
            
int resp = 0;
try
{
    using (OracleConnection conn = OracleDataBase.GetInstance().GetConnection())
    {
        using (OracleCommand cmd = new OracleCommand(commandText, conn))
        {
            cmd.CommandType = cmdtype;
            if (parameters != null)
            {
                foreach (KeyValuePair pair in parameters)
                {
                    cmd.Parameters.Add(new OracleParameter(pair.Key, pair.Value));
                }
            }
            resp = cmd.ExecuteNonQuery();
        }
    }
}
catch (OracleException ex)
{
    Logger.LogWriteError(ex.Message);
    throw ex;
}
return resp;
}
internal static int Update(string commandText, Dictionary parameters, 
System.Data.CommandType cmdtype)
{
            
try
{
    return Insert(commandText, parameters, cmdtype);
}
catch (OracleException ex)
{
    Logger.LogWriteError(ex.Message);
    throw ex;
}
}
internal static OracleDataReader GetReader(string commandText, Dictionary parameters,
System.Data.CommandType cmdtype)
{
OracleDataReader reader = null;
try
{
    OracleConnection conn = OracleDataBase.GetInstance().GetConnection();
    using (OracleCommand cmd = new OracleCommand(commandText, conn)) {
        if (parameters != null)
        {
            foreach (KeyValuePair pair in parameters)
            {
                cmd.Parameters.Add(new OracleParameter(pair.Key, pair.Value));
            }
        }
        reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
    }
}
catch (Exception ex) {
    Logger.LogWriteError(ex.Message);
    throw ex;
}
return reader;
}

internal OracleDataReader GetParameterizedReader(string commandText, OracleParameter[] parameters, 
System.Data.CommandType cmdtype)
{
OracleDataReader reader = null;
OracleConnection conn = OracleDataBase.GetInstance().GetConnection();
using (OracleCommand cmd = new OracleCommand(commandText, conn))
{
    cmd.CommandType = cmdtype;
    if (parameters != null)
        cmd.Parameters.AddRange(parameters);
    reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
}
return reader;
}
}
}

Como ejemplo del uso del helper, tenemos el código de la clase CustomerDAC cuya funcionalidad es tener los métodos CRUD para una entidad Customer.

El código de la clase Customer

Para finalizar el código de una aplicación de consola que muestra el uso de la clase CustomerDAC.

miércoles, 14 de agosto de 2013

ADO.NET Helper para Oracle

Hace bastante tiempo que hice unas clases de ADO.NET para Oracle (un tipo helper de manera elemental). quizá no es la manera más optima de acceder a una base de datos, pero al menos en ambientes restringidos en donde no es posible instalar frameworks u otro proveedor de ADO.NET para Oracle que no sea el que viene predeterminado por .NET.

Aquí esta la clase para manejar la conexión, se llama OracleDataBase

Fig 1. Clase para manejar la conexion a la base de datos.

Utilizo otra clase llamada OracleDataBaseCommand para auxiliarme con los comandos.
Fig 2. Clase auxiliar para ejecutar los comandos en la base de datos.

Su utilización dentro de una clase que sirva para persistir o extraer datos sería de la siguiente manera.

Fig 3. Clase que utiliza las clases auxiliares para guardar un objeto.

Fig 4. Clase principal que crea, actualiza y consulta un cliente en la base de datos.

viernes, 2 de agosto de 2013

Entendiendo Unboxing en C# con GTK#

Es la operación en donde un tipo por referencia se convierte a un tipo simple se denomina Unboxing, esta conversión al igual que el Boxing puede ser explícita o implícita según se requiera.

A diferencia del Boxing el Unboxing requiere muchos menos recursos ya que no requiere la reserva de memoria adicional porque solamente se obtiene un apuntador hacia el tipo simple contenido dentro del objeto.

Internamente el proceso de Unboxing lleva los siguientes pasos:

  1. Se comprueba si existe una referencia hacia un objeto creado mediante Boxing.
  2. Si esto es verdadero se obtiene un apuntador para el tipo simple contenido dentro del objeto.
  3. Se le copia el valor del objeto al tipo simple también se copian los campos del objeto del managed heap al stack.
  4. Si no existe una referencia al objeto se lanza una excepción del tipo NullReferenceException.
  5. Si el tipo simple a la que se le asigna el valor del objeto no tiene la suficiente longitud para almacenar el valor del objeto se lanza una excepción del tipo InvalidCastException.

Como ejemplo del uso de esta técnica el siguiente programa GTK# solicita dos números de doble precisión y determina cuál es el mayor de ellos.

Al ejecutar el programa se muestra la siguiente ventana:

Al ingresar ambos valores y pulsar el botón "Ok" el programa comparará ambos números.

En el metódo HandleBtnOkClicked se encuentra toda la funcionalidad, es aquí donde utilizamos la técnica de Unboxing

void HandleBtnOkClicked (object sender, EventArgs e)
{
 lblError.Text = string.Empty;
 try
 {
 double x,y,temp;
 x = Double.Parse (txtNumber1.Text);//Unboxing string to double
 y = Convert.ToDouble(txtNumber2.Text);//Unboxing 
 if(y > x)
 {
  temp = x;
  x = y;
  y = temp;
 }
 lblMsg.Text = x.ToString() + " greater than " + y.ToString();//Boxing
}catch(FormatException ex){
 lblMsg.Text = string.Empty;
 lblError.Text = ex.Message;    
}
}

En la siguientes líneas, de un tipo por referencia como la propiedad Text del Widget Entry, lo convertimos a un tipo por valor:

 double x,y,temp;
 x = Double.Parse (txtNumber1.Text);//Unboxing string to double
 y = Convert.ToDouble(txtNumber2.Text);//Unboxing 

Es una buena práctica utilizar el código que realiza el Unboxing dentro de un bloque try/catch, esto para manejar la excepción en caso de que el valor del tipo por referencia (objeto) no sea del mismo tipo que el tipo simple que lo almacenará.

jueves, 1 de agosto de 2013

Entendiendo Boxing en C# con GTK#

Todos los tipos de datos en C# heredan de la clase base Object, tanto los tipos de datos por valor (ejem: primitivos) como los tipos por referencia (ejem:clases), esta herencia permite convertir un tipo de dato por valor a uno por referencia y a la inversa, a estas operaciones se les conoce como Boxing y Unboxing.

Estas operaciones son importantes dentro del sistema de tipos de C# ya que unifican la forma de trabajar con ellos, puesto que será posible invocar a métodos comunes en cada uno de ellos sin importar su tipo.

El siguiente programa GTK# muestra estos conceptos, el programa emula a un sencillo administrador de cuenta bancaria solicita una cantidad para deposito o retiro y según el tipo de movimiento pone un signo de `+’ si es deposito y un signo de ‘-‘si es retiro.

El programa consta de una estructura MovementItem y de la clase GtkBoxingSample.

El código fuente de la estructura MovementItem.

Listado 1. Clase MovementItem en C#.

El código fuente de la clase GtkBoxingSample.

Listado 2. Clase GtkBoxingSample para mostrar boxing en C#.

Al ejecutar el programa se verá la siguiente ventana con un saldo inicial de 1000

Al ingresar un cantidad y dar click en el botón "Deposit", se mostrará el movimiento con un signo "+" sumando el monto al saldo.

Lo mismo sucederá con el botón "WithDraw", solo que se muestra el movimiento con un signo "-" restando el monto al saldo.

Se usan las técnicas de Boxing/Unboxing en el método ChangeBalance

Listado 3. Boxing/unboxing en C#.

En .NET toda estructura o tipo simple es un tipo por valor y como la propiedad Text del Widget Entry admite únicamente el tipo String que es un tipo por referencia, por lo que para mostrar la cantidad en el campo de texto Balance, utilizamos el Boxing como en la siguiente línea:

txtBalance.Text = Convert.ToString(balance); //boxing

Después para agregar un item en el ListStore utilizamos el método AppendValues(), el cuál recibe como argumento un arreglo de objetos, nuevamente tenemos que utilizar el Boxing para cada que cada tipo simple de la estructura se convierta en un tipo por referencia, esto se hace con la siguiente línea:

_store.AppendValues (item.Date.ToString("dd/MM/yyyy"), //boxing date to string
                Convert.ToString(item.Sign), //boxing char to string
                item.Amount.ToString()); //boxing double to string

Internamente el proceso de Boxing lleva los siguientes pasos:

  1. Se reserva una cantidad de memoria adicional al tipo de variable para las operaciones adicionales que diferencian a aun objeto de un tipo simple.
  2. Se crea una variable de objeto temporal en donde se almacenará el valor de la variable.
  3. Se copia el valor de la variable al valor del objeto creado.
  4. Se regresa la dirección del objeto ya que en este momento el tipo por valor es un tipo por referencia.

El Boxing puede ser llevado a cabo de forma implícita por ejemplo:

item.Date.ToString("dd/MM/yyyy");
O de manera explícita, por ejemplo:
object objAmount = amount;
objAmount.ToString();

El proceso de Boxing es costoso ya que requiere de una memoria adicional que afecta al performance de la aplicación, además ocasiona que el código IL producido sea mucho mayor.

jueves, 11 de julio de 2013

Entendiendo Nullable Types con C#

Existen dos tipos de datos en .NET: los tipos por referencia (reference types) y los tipos por valor (value types), como ejemplo de tipos por valor encontramos a las estructuras (struct), los enumerados (enum) y los todos los tipos simples como son los caracteres (char) y los tipos númericos (double,int,float,short,long), como ejemplo de tipos de referencia tenemos a la clase cadena (string), la clase object y la clases definidas por el usuario (user defined classes).

Una de las diferencias entre ellos es que los tipos por valor siempre deben tener un valor y nunca pueden contener un valor nulo (null), mientras que para los tipos por referencia siempre tienen el valor null al crearse o bien se les puede asignar durante la ejecución del programa.

El problema se presenta cuando se trabaja con fuentes de persistencia y se necesita asignar el valor de un tipo de dato de estas fuentes hacia un tipo de dato simple de C#, por ejemplo en un archivo XML o en una base de datos un valor entero puede representarse como vacío o null, entonces no es posible representar este valor en un tipo simple de C# ya que en el CLR un tipo por valor no puede ser nunca nulo.

Desde la versión 2.0 de .NET es posible trabajar con nullable types en donde los tipos simples tienen la capacidad de tener valores nulos, esto se logra agregándoles el modificador (?) al tipo de dato, así por ejemplo las siguientes variables además de ser del tipo simple son Nullable Types.

int? a = null;
boolean b? = null;

A estos tipos también se les puede aplicar el operador (??) llamado null coalescing operator (operador de coalescencia) el cuál le aplica la comparativa al valor, si este es nulo entonces le asigna un valor predeterminado, por ejemplo si tienes un arreglo de objetos de tipo factura y necesitas que ciertas propiedades tengan al menos un valor predeterminado, aplicamos la comparativa utilizando el operador (??) para las que las propiedades con valores nulos tengan un valor predeterminado, esto se muestra en el siguiente programa:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestCoalescing
{
public class Invoice
{
public int InvoiceID { set; get; }
public DateTime? StartDate { set; get; }
public string MeasureUnit { set; get; }
public Decimal? TaxPercent { set; get; }
}

class Program
{
static void Main(string[] args)
{
Invoice[] invoices = { new Invoice {
  InvoiceID = 1,
  StartDate = new DateTime(1999,06,10),
  MeasureUnit = "Box"
 },
 new Invoice {
  InvoiceID = 2,
  StartDate = new DateTime(2012,06,06),
  TaxPercent = 13.00M
 },
    new Invoice {
        InvoiceID = 3
    }
};
foreach (Invoice i in invoices)
{
    PrintInvoice(i);
}
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
}

static void PrintInvoice(Invoice invoice)
{
Console.WriteLine("InvoiceID: {0}", invoice.InvoiceID);
Console.WriteLine("Start Date: {0}", invoice.StartDate ?? DateTime.Today);
Console.WriteLine("Measure Unit: {0}", invoice.MeasureUnit ?? "NONE");
Console.WriteLine("Tax percent: {0}", invoice.TaxPercent ?? 16.00M);
Console.WriteLine("----+----------+-----");
}

}
}

Dentro del método PrintInvoice se muestra la utilización del operador (??), para tres propiedades del objeto factura:

Console.WriteLine("Start Date: {0}", invoice.StartDate ?? DateTime.Today);
Console.WriteLine("Measure Unit: {0}", invoice.MeasureUnit ?? "NONE");
Console.WriteLine("Tax percent: {0}", invoice.TaxPercent ?? 16.00M);

Este operador no solo trabaja con nullable types sino también con tipos por referencia como es el caso de la propiedad MeasureUnit la cual es del tipo String un tipo por referencia, como en el siguiente fragmento de código:

Console.WriteLine("Measure Unit: {0}",invoice.MeasureUnit ?? "NONE");

Los nullable types en realidad corresponden a una estructura genérica del CLR llamada System.Nullable

miércoles, 5 de junio de 2013

Utilizando el control RadNumericTextBox de Telerik

El RadNumericTextBox es uno de los controles que pertenecen al conjunto  de controles RadInput de telerik, estos controles además de permitir al usuario ingresar y editar información textual como si fuera un control TextBox ASP .NET tradicional,  poseen propiedades adicionales como: eventos del lado del cliente, marca de agua, botón para aceptar, botones para selección de rango, mensaje predeterminado, tooltip, entre otras que extienden la funcionalidad básica proporcionada por el control TextBox ASP.NET estándar.

La funcionalidad principal del RadNumericTextBox es filtrar la entrada de datos permitiendo únicamente datos numéricos sin necesidad de programación Javascript o el uso de validators ASP .NET, este control además puede configurarse para utilizar determinados formatos o configuraciones regionales. Entre las propiedades de configuración se encuentran:

  • Type Indica el formato del texto, Number para formato númerico, Currency para formato moneda, Percent para un formato porcentaje.
  • Culture Establece el soporte para la globalización y localización del valor.
  • MinValue y MaxValue Indican los valores minimos y maximos de un rango de valores.
  • ShowSpinButtons Estable la propiedad para mostrar un control de selección de flechas para el incremento o decremento del valor.

Como ejemplo del uso de este control escribí una página ASP.NET que calcula los montos de un producto deacuerdo a su cantidad y su precio, mostrando caracteristicas utiles del RadNumericTextBox como la globalización y algunas funciones del lado del cliente. (client-side)

El código de la página ASP.NET
<%@ Page Language="C#" AutoEventWireup="true"
CodeBehind="Default.aspx.cs"
Inherits="TestRadNumericTextBox._Default" %>
<%@ Register Assembly="Telerik.Web.UI"
Namespace="Telerik.Web.UI" TagPrefix="telerik" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "
http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Test RadNumeric Text Box</title>
</head>
<body>
<form id="form1" runat="server">
<telerik:RadScriptManager ID="RadScriptManager1"
runat="server">
<Scripts>
<asp:ScriptReference Assembly="Telerik.Web.UI"
Name="Telerik.Web.UI.Common.Core.js">
</asp:ScriptReference>
<asp:ScriptReference Assembly="Telerik.Web.UI"
Name="Telerik.Web.UI.Common.jQuery.js">
</asp:ScriptReference>
<asp:ScriptReference Assembly="Telerik.Web.UI"
Name="Telerik.Web.UI.Common.jQueryInclude.js">
</asp:ScriptReference>
</Scripts>
</telerik:RadScriptManager>
<telerik:RadScriptBlock ID="RadScriptBlock1"
runat="server">
<script type="text/javascript">
function Calculate() {
var ctrlQuantity = $find("txtQuantity");
var ctrlPrice = $find("txtPrice");
var ctrlSubtotal = $find("txtSubtotal");
var ctrlTaxPercent = $find("txtTax");
var ctrlTaxAmount = $find("txtTaxAmount");
var ctrlTotal = $find("txtTotal");
var quantity = parseInt(ctrlQuantity.get_value());
var price = parseFloat(ctrlPrice.get_value());
var subtotal = quantity * price;
ctrlSubtotal.set_value(subtotal);
var percent = parseFloat(ctrlTaxPercent.get_value()) * .01;
var taxAmount = subtotal * percent;
ctrlTaxAmount.set_value(taxAmount);
var total = subtotal + taxAmount;
ctrlTotal.set_value(total);
}
</script>
</telerik:RadScriptBlock>
<div>
<telerik:RadComboBox ID="cmbCultureCurrency"
runat="server"
onselectedindexchanged="cmbCultureCurrency_SelectedIndexChanged"
EmptyMessage="Select your country"
AutoPostBack="true">
<Items>
<telerik:RadComboBoxItem Text="Brazil"
Value="pt-BR" />
<telerik:RadComboBoxItem Text="United Kingdom"
Value="en-GB" />
<telerik:RadComboBoxItem Text="United States"
Value="en-US" />
<telerik:RadComboBoxItem Text="Spain"
Value="es-ES"/>
</Items>
</telerik:RadComboBox>
</div>
<br />
<table>
<tr>
<td>Product</td>
<td>
<telerik:RadComboBox ID="cmbProduct"
EmptyMessage="Select a product"
runat="server">
<Items>
<telerik:RadComboBoxItem Value="1"
Text="Potatoes" />
<telerik:RadComboBoxItem Value="2"
Text="Beans" />
<telerik:RadComboBoxItem Value="3"
Text="Apples" />
<telerik:RadComboBoxItem Value="4"
Text="Oranges" />
</Items>
</telerik:RadComboBox>
</td>
</tr>
<tr>
<td>Quantity</td>
<td>
<telerik:RadNumericTextBox ID="txtQuantity"
Runat="server"
Type="Number"
DataType="System.Uint32">
<NumberFormat ZeroPattern="n"></NumberFormat>
</telerik:RadNumericTextBox>
</td>
</tr>
<tr>
<td>Tax</td>
<td>
<telerik:RadNumericTextBox ID="txtTax"
Runat="server"
Type="Percent"
MinValue="1"
MaxValue="20"
Value="16"
ShowSpinButtons="true">
<NumberFormat ZeroPattern="n %"></NumberFormat>
</telerik:RadNumericTextBox>
</td>
</tr>
<tr>
<td>Price</td>
<td>
<telerik:RadNumericTextBox ID="txtPrice"
Runat="server"
Type="Currency"
ClientEvents-OnBlur="Calculate">
</telerik:RadNumericTextBox>
</td>
</tr>

<tr>
<td>Subtotal</td>
<td>
<telerik:RadNumericTextBox ID="txtSubtotal"
Runat="server"
Type="Currency"
DataType="System.Decimal"
ReadOnly="true">
<NumberFormat ZeroPattern="$n"></NumberFormat>
</telerik:RadNumericTextBox>
</td>
</tr>
<tr>
<td>Tax amount</td>
<td>
<telerik:RadNumericTextBox ID="txtTaxAmount"
runat="server"
ReadOnly="true">
</telerik:RadNumericTextBox>
</td>
</tr>
<tr>
<td>Total</td>
<td>
<telerik:RadNumericTextBox ID="txtTotal"
Runat="server"
Type="Currency"
DataType="System.Decimal"
ReadOnly="true">
</telerik:RadNumericTextBox>
</td>
</tr>
</table>
<div>Selected currency: <asp:Label ID="lblSelectedCurrency"
runat="server"></asp:Label></div>
</form>
</body>
</html>
El código de la clase CodeBehind
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Globalization;

namespace TestRadNumericTextBox
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{

}

protected void cmbCultureCurrency_SelectedIndexChanged(object sender, 
Telerik.Web.UI.RadComboBoxSelectedIndexChangedEventArgs e)
{
CultureInfo info = null;
if (string.IsNullOrEmpty(cmbCultureCurrency.SelectedValue))
    info = System.Threading.Thread.CurrentThread.CurrentCulture;
else
    {
    info = new CultureInfo(cmbCultureCurrency.SelectedValue);
    lblSelectedCurrency.Text = cmbCultureCurrency.SelectedValue;
    txtQuantity.Culture =
    txtPrice.Culture =
    txtTaxAmount.Culture =
    txtSubtotal.Culture =
    txtTaxAmount.Culture =
    txtTotal.Culture = info;
    }
}
}
}
        

En la página ASP.NET, en el bloque JavaScript, dentro de la función Calculate() obtengo la referencia a los controles mediante la función $find(“[nombre del control]”) después utilizo las funciones get_value() y set_value() para obtener o establecer el valor. Estas funciones son del lado del cliente, porque del lado del server se hace con la propiedad Text.

function Calculate() {
var ctrlQuantity = $find("txtQuantity");
var ctrlPrice = $find("txtPrice");
var ctrlSubtotal = $find("txtSubtotal");
var ctrlTaxPercent = $find("txtTax");
var ctrlTaxAmount = $find("txtTaxAmount");
var ctrlTotal = $find("txtTotal");
var quantity = parseInt(ctrlQuantity.get_value());
var price = parseFloat(ctrlPrice.get_value());
var subtotal = quantity * price;
ctrlSubtotal.set_value(subtotal);
var percent = parseFloat(ctrlTaxPercent.get_value()) * .01;
var taxAmount = subtotal * percent;
ctrlTaxAmount.set_value(taxAmount);
var total = subtotal + taxAmount;
ctrlTotal.set_value(total);
}

En el control txtQuantity utilizo la propiedad type=”Number” lo que le da el formato numérico al valor

<telerik:RadNumericTextBox ID="txtQuantity"
Runat="server"
Type="Number"
DataType="System.Uint32">
<NumberFormat ZeroPattern="n"></NumberFormat>
</telerik:RadNumericTextBox>

El control txtTax demuestra el uso de la propiedad type=”percent”, las propiedades para el valor mínimo (MinValue), valor máximo (MaxValue) y la propiedad ShowSpinButtons.

<telerik:RadNumericTextBox ID="txtTax"
Runat="server"
Type="Percent"
MinValue="1"
MaxValue="20"
Value="16"
ShowSpinButtons="true">
<NumberFormat ZeroPattern="n %"></NumberFormat>
</telerik:RadNumericTextBox>

Los controles txtSubtotal, txtTotal y txtPrice muestran el uso de la propiedad type=Currency para dar formato de tipo moneda al valor, además en el control txtPrice utilizo el evento ClientEvents-OnBlur del lado del cliente para invocar al método Calculate definido en el bloque JavaScript.

<telerik:RadNumericTextBox ID="txtPrice"
Runat="server"
Type="Currency"
ClientEvents-OnBlur="Calculate">
</telerik:RadNumericTextBox>

Por último en el code-behind utilizo la propiedad Culture para establecer el formato en los controles RadNumericTextBox según las preferencias de la cultura seleccionada.

Al ejecutar la página ASP.NET observaras los siguientes resultados,  si el país es  Brazil, las cantidades se formatean deacuerdo a la configuración de ese país.

Si el páis seleccionado es United Kingdom, lo mismo sucede con las cantidades y con las monedas en este caso aparece el símbolo de la libra (pound).

Si el país es United States,  en las cantidades aparece el símbolo del Dollar.

Y por último si la opción del país es Spain, aparece el símbolo del Euro.

viernes, 31 de mayo de 2013

Utilizando localización y globalización (localization and globalization) en .NET

La globalización y la localización (localization and globalization) son características que permiten que las aplicaciones .NET se adapten a múltiples idiomas y culturas utilizando las diferentes configuraciones regionales de cada uno de los equipos en donde se ejecutan, con el objetivo de que los usuarios visualicen de forma correcta los datos mostrados en la interfaz de usuario de acuerdo con sus preferencias locales.

El mundo se encuentra dividido en múltiples regiones y culturas, una cultura (cultura) en el contexto de .NET se refiere al idioma y al conjunto de preferencias regionales para calendarios, cadenas, números y monedas.

.NET cumple con el estándar RFC 1766 del IETF para la identificación de culturas mediante un identificador que se construye con las siguientes reglas: a) Existen códigos de idiomas o culturas neutrales que se representan por letras minúsculas como ejemplo la siguiente tabla:

Código Idioma
en Inglés
de Alemán
es Español
fr Francés

b) Además de los códigos de idiomas existen códigos de subculturas específicas que corresponden a una región geográfica, estas subculturas se representan en letras mayúsculas como ejemplo la siguiente tabla:

Subcultura Región
GB Great Britain
CA Canadá
MX México
BR Brasil

Los códigos de los idiomas (a) se combinan con los códigos de las regiones (b) asociándolos mediante un (-) entre ellos por lo que un mismo lenguaje puede ser hablado en diferentes regiones así por ejemplo: Español en España (es-ES), Español en México (es-MX), Español en Argentina (es-Ar) o Inglés en UK (en-UK), Inglés en USA (en-USA), Inglés en Canadá (en-CA) por mencionar algunos casos. Uno de los primeros pasos para implementar la localización y globalización en las aplicaciones .NET es el uso de la clase CultureInfo la cual encapsula información referente a una cultura específica, información acerca de su idioma, el calendario, los formatos de números y monedas, la dirección del texto entre otros valores. Como ejemplo escribí la rutina de un programa de nómina para una compañía que tiene sucursales en USA, UK, Canadá y Brasil, este programa calcula el salario bruto y el pago neto de un empleado de acuerdo a su tarifa por hora, también calcula el número de horas trabajadas y la retención del impuesto correspondiente. El código a continuación:

using System;
using System.Globalization;
using System.Threading;

namespace TestGlobalization
{
class Program
{
public static void Main(string[] args)
{
try
{
    DateTime startDate, finishDate;
    TimeSpan workingTime;
    int workingDays, taxRate;
    Decimal salaryRate, netIncome, grossSalary, taxDeduction;
    CultureInfo ci = null;
    Console.WriteLine("Please select your country:");
    Console.WriteLine("\n");
    Console.WriteLine("a) USA \tb) Canada");
    Console.WriteLine("c) UK  \td) Brasil");
    Console.WriteLine("\n");
    ConsoleKeyInfo option = Console.ReadKey(true);
    switch (option.Key) { 
        case ConsoleKey.A:
            ci = new CultureInfo("en-US");
            break;
        case ConsoleKey.B:
            ci = new CultureInfo("fr-CA");
            break;
        case ConsoleKey.C:
            ci = new CultureInfo("en-GB");
            break;
        case ConsoleKey.D:
            ci = new CultureInfo("pt-BR");
            break;
        default:
            ci = Thread.CurrentThread.CurrentCulture;
            break;
    }
    Thread.CurrentThread.CurrentCulture = ci;
    Console.Write("Start date: ");
    startDate = Convert.ToDateTime(Console.ReadLine());
    Console.Write("Finish date: ");
    finishDate = Convert.ToDateTime(Console.ReadLine());
    Console.Write("Salary rate: ");
    salaryRate = Convert.ToDecimal(Console.ReadLine());
    Console.Write("Tax rate %: ");
    taxRate = Convert.ToInt32(Console.ReadLine());
    workingTime = finishDate - startDate;
    workingDays = workingTime.Days;
    grossSalary = workingDays * salaryRate;
    taxDeduction = grossSalary * Convert.ToDecimal(taxRate * .01);
    netIncome = grossSalary - taxDeduction;
    Console.WriteLine("\n");
    Console.WriteLine("Start date:   \t{0:D}", startDate);
    Console.WriteLine("Finish date:  \t{0:D}", finishDate);
    Console.WriteLine("Working days: \t{0:N}", workingDays);
    Console.WriteLine("Gross Salary: \t{0:C}", grossSalary);
    Console.WriteLine("Net income:   \t{0:C}", netIncome);
}
catch (Exception ex) {
    Console.WriteLine(ex.Message);
}
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
}
}
}

Este código muestra la utilización de la clase CultureInfo primero invoca al ensamblado System.Globalization

using System.Globalization;

Segundo solicita al usuario que seleccione un país que está asociado con el código de una cultura soportada por el .NET Framework.

Console.WriteLine("a) USA \tb) Canada");
Console.WriteLine("c) UK  \td) Brasil");
Console.WriteLine("\n");
ConsoleKeyInfo option = Console.ReadKey(true);

La cultura seleccionada se establece en el hilo principal de ejecución, cuyo valor predeterminado es el de la configuración regional del sistema operativo. Esto lo hace el siguiente bloque de código:

switch (option.Key) { 
   case ConsoleKey.A:
        ci = new CultureInfo("en-US");
        break;
   case ConsoleKey.B:
        ci = new CultureInfo("fr-CA");
        break;
   case ConsoleKey.C:
        ci = new CultureInfo("en-GB");
        break;
   case ConsoleKey.D:
        ci = new CultureInfo("pt-BR");
        break;
   default:
        ci = Thread.CurrentThread.CurrentCulture;
        break;
    }

Al ejecutar la aplicación como resultado se pueden ver las diferencias entre los formatos de fechas, números y monedas de acuerdo a la cultura seleccionada.

Resultado de la clase CultureInfo con la opción “en-US”, el formato de fechas es mes, día y año. (mm/dd/yyyy)
Resultado de la opción "fr-CA", las fechas de entrada tienen el mismo formato que la cultura “en-US” , pero hay diferencia en el idioma de las fechas y en la moneda.
Resultado de la opción “en-GB”, (english-Great Britain) el formato de las fechas de entrada son diferentes al de USA y Canadá ya que utiliza día, mes y año (dd/mm/yyyy), para las cantidades muestra el símbolo de la libra esterlina.
Resultado de la clase CultureInfo con la opción “pt-BR” (portugués Brasil) se ve la diferencia en las fechas y en la moneda ya que antepone una R de Real la moneda de Brasil.

En caso de que el formato de algún dato introducido no coincida con el formato de la cultura establecida, el programa arrojará una exception, en este caso seleccione la opción USA e intenté introducir las fechas con el formato día, mes y año (dd/mm/yyyy) coincidió en la primera fecha (aunque no era la fecha que quería) y arrojo la excepción en la segunda fecha por tener un formato incorrecto.