viernes, 17 de abril de 2015

Mi primer libro: MVC 4 para .Net desde cero en Español

Hola a todos. Luego de un año de trabajo, terminé de escribir primer libro técnico: MVC 4 con .Net desde cero: Guía práctica para implementar MVC 4 con C# y Visual Studio 2012/2013 para Amazon Kindle e Impreso. ¡Espero lo compren y les guste!

Valor Kindle: a sólo 9.99 USD

Comprar aquí: http://www.amazon.com/MVC-con-Net-desde-cero-ebook/dp/B00V45GWA2/ref=sr_1_8?ie=UTF8&qid=1429322559&sr=8-8&keywords=mvc+4

Valor Impreso: Chile $15.000. Otros países € 32.20 EUR / US $35.00

Chile: comprar por MercadoLibre: http://articulo.mercadolibre.cl/MLC-426603239-libro-mvc-4-en-espanol-_JM o contactarme en mi correo hernaldog@gmail.com ya que vivo aquí.

Link a la tienda Amazon que te corresponde según tu país:


Otros países (España, Argentina, México, otros): Comprar aquí: http://www.amazon.com/MVC-con-Net-desde-cero/dp/1511532130/ref=sr_1_4?ie=UTF8&qid=1429464182&sr=8-4&keywords=mvc+4+spanish



Tabla de contenidos
1. Introducción 
1.1 Introducción a ASP.NET MVC 
1.2 Creando una aplicación MVC 4 
1.3 Estructura de una aplicación MVC en Visual Studio 
1.4 Ejercicios 
1.5 Resumen 

2. Controladores 
2.1 El rol del controlador 
2.2 Un ejemplo: la famosa tienda de música MVC 
2.3 Un Controlador básico 
2.4 Ejercicios 
2.5 Resumen 

3. Vistas 
3.1 ¿Por qué usar una vista? 
3.2 Seleccionando una Vista 
3.3 Vistas fuertemente tipadas 
3.4 View Model 
3.2 Agregar un Vista 
3.3 El motor de vista Razor 
3.4 Ejercicios 
3.5 Resumen 

4. Modelos 
4.1 El Modelo de Music Store 
4.2 Crear el administrador de almacenamiento 
4.3 Ejercicios 
4.4 Resumen 

5. Los HTML helpers 
5.1 El tag form 
5.2 HTML helpers 
5.3 Otros helpers 
5.4 Rendereando los Helpers 
5.5 Ejercicios 
5.6 Resumen 

6 Membrecía, Autorización y Seguridad 
6.1 Usando el atributo Authorize en un login 
6.2 El atributo Authorize para usarlo con roles 
6.3 Cuando usar roles y cuando usuarios 
6.4 Login externo con OAuth y OpenID 
6.4 Puntos de seguridad en una Web App 
6.5 CustomErrors en el web.config 
6.6 Usando la opción Retail 
6.7 Usar un logging dedicado 
6.8 Resumen de las típicas amenazas y links 
6.9 Ejercicios 
6.10 Resumen 

7 Ajax 
7.1 jQuery 
7.2 Ajax Helpers 
7.3 Validación en el lado del Cliente 
7.4 Detrás los helpers 
7.5 Mejorando el rendimiento de Ajax 
7.6 Ejercicios 
7.7 Resumen 

8 Enrutamiento 
8.1 Localizadores de recursos 
8.2 Introducción al Routing 
8.3 Parámetro para “agarrarlos todos” 
8.4 Múltiples parámetros URL en un segmento 
8.5 Uso del StopRountingHandler e IgnoreRoute 
8.6 Depurando Rutas 
8.7 Más ejemplos de la generación de URLs con la clase Route 
8.8 Personalizar constraints de las rutas 
8.8 Usando Routing con Web Forms 
8.9 Ejercicios 
8.10 Resumen 

9 Web API 
9.1 Definición de una Web API 
9.2 Comenzando un proyecto con Web API 
9.3 Escribiendo un controlador API 
9.4 Configurando Web API 
9.5 Agregando rutas a la Web API 
9.6 Binding de Parámetros 
9.7 Filtros en los request 
9.8 Habilitando Dependency Injection (DI) 
9.9 Explorando una Web API 
9.10 Traceando la aplicación 
9.11 Ejemplo de una Web API 
9.12 Ejercicios 
9.13 Resumen

miércoles, 1 de abril de 2015

Como enviar un CSV de VB6 por .NET y de .NET a Google SpreadSheets

La tarea no es fácil, vamos a generar un CSV en VB6 y de ahí consumir una componente .NET y de ahí pasar a GoogleSpreadSheets. Esto es porque investigando, pasar desde VB6 a GoogleSpreadSheets directamente es medio complicado, por lo que usaremos .NET como "puente".

Usaremos:

  • Visual Studio 2013
  • .Net Framework 2.0.
  • Visual Basic 6

  • Configuración en la cuenta Google


    En la cuenta de correo que usará Google Spreadsheets:
  • Crear un proyecto en: https://console.developers.google.com Y activar la API Drive API.
  • Activar aplicaciones no seguras: https://www.google.com/settings/security/lesssecureapps, esto porque no usaremos OAuth 2.0, sino un acceso directo del código con el correo/clave.

  • Codificar la componente .Net


    Instala Google_Data_API_Setup_*.msi, en mi caso bajé Google_Data_API_Setup_2.2.0.0.msi.
    Las dll quedarán en por defecto en:

    C:\Program Files (x86)\Google\Google Data API SDK\Redist\

    Fuente: https://developers.google.com/google-apps/spreadsheets/.

    Usaremos .Net Framework 2.0, así que en Visual Studio 2013 crear un proyecto tipo Librería de Clases usando esta versión del Framework.

    Referencia a las DLL:

  • Google.GData.Client
  • Google.GData.Documents
  • Google.GData.Extension
  • Google.GData.Spreadsheets

  • Como dije, sácalas de: C:\Program Files (x86)\Google\Google Data API SDK\Redist\

    Configura el proyecto

    En el archivo AssemblyInfo.cs configura:
    [assembly: ClassInterface(ClassInterfaceType.None)] 
    [assembly: AssemblyKeyFileAttribute("C:\\TFS\Google.snk")]  // C#
    [assembly: Guid("837f7063-8219-4f83-abe5-aa4240c7ac4e")]    //en c# automático
    

    En las propiedades del proyecto:

  • Build: Registrar la interoperabilidad COM
  • Aplicación -> Información del ensamblado -> Crear ensamblado visible a travéz de COM


  • Crea una interfaz (para este ejemplo es útil solo si estás consumiendo desde VB6):
    using System;
    using System.Collections.Generic;
    using System.Runtime.InteropServices;
    using System.Text;
    
    namespace GoogleSpread
    {
        [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)]
        public interface IFunciones
        {
            
            int SendCVSToGoogle(string ruta, string file);        
        }    
    }
    

    Crea la clase Funciones:
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Runtime.InteropServices;
    using System.Text;
    using Google.GData.Spreadsheets;
    using Google.GData.Client;
    using Google.GData.Documents;
    using System.Configuration;
    using Google.GData.Client.ResumableUpload;
    
    namespace GoogleSpread
    {
    
        [ComSourceInterfaces("GoogleSpread.IFunciones")]
        public class Funciones : IFunciones
        {
    
            public Funciones()
            {
            }
    
            public int SendCVSToGoogle(string ruta, string archivoCVS)
            {
                try
                {
                    //logging...
    
                    DocumentsService service = new DocumentsService("MyDocumentsListIntegration-v1");              
    
                    string user = ConfigurationManager.AppSettings["correo"];
                    string pass = ConfigurationManager.AppSettings["pass"];
                    service.setUserCredentials(user, pass);
    
                    if (user == null || user == String.Empty || pass == null || pass == String.Empty)
                    {
                        throw new Exception("Usuario o clave incorrecta");
                    }
    
                    //logging hasta aquí ok...
    
                    Authenticator authenticator = new ClientLoginAuthenticator("TestApi", Google.GData.Client.ServiceNames.Documents, service.Credentials);
    
                    // Instantiate a DocumentEntry object to be inserted.
                    DocumentEntry entry = new DocumentEntry();
    
                    // Set the document title
                    entry.Title.Text = archivoCVS;
                    entry.IsSpreadsheet = true;
    
                    string rutaFull = ruta + "\\" + archivoCVS;
                    if (!File.Exists(rutaFull))
                        throw new Exception("Archivo ["+ rutaFull +"] no existe.");
    
                    entry.MediaSource = new MediaFileSource(rutaFull, "text/csv");
    
                    Uri createUploadUrl = new Uri("https://docs.google.com/feeds/upload/create-session/default/private/full");
                    AtomLink link = new AtomLink(createUploadUrl.AbsoluteUri);
                    link.Rel = ResumableUploader.CreateMediaRelation;
                    entry.Links.Add(link);
    
                    entry.Service = service;
    
                    ResumableUploader uploader = new ResumableUploader();
    
                    //logging archivo se subirá...
    
                    // Set the handlers for the completion and progress events
                    uploader.AsyncOperationCompleted += new AsyncOperationCompletedEventHandler(OnDone);
                    uploader.AsyncOperationProgress += new AsyncOperationProgressEventHandler(OnProgress);                
    
                    // Start the upload process
                    uploader.InsertAsync(authenticator, entry, new object());
    
                    
                    return 1;
                }
                catch (Exception ex)
                {
                    //logging error
                    return -1;
                }
            }
    
            static void OnDone(object sender, AsyncOperationCompletedEventArgs e)
            {            
                DocumentEntry entry = e.Entry as DocumentEntry;
                //logging subido ok
            }
    
            static void OnProgress(object sender, AsyncOperationProgressEventArgs e)
            {
                int percentage = e.ProgressPercentage;
                //logging subido un tanto %
            }
        }
    }
    

    Crea un proyecto Test, por ejemplo un winForm con un botón.
    Agrega un archivo de configuración App.config con el correo gmail y la clave:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
       <appSettings>
            <add key="correo" value="correo@gmail.com"/>
            <add key="pass" value="clave"/>
        </appSettings>
    </configuration>
    

    Agrega el código para que hacer clic sobre el botón invoque a la componente .Net y testearla más facilmente. Recuerde referenciar primero el proyecto:

    private void button1_Click(object sender, EventArgs e)
    {
        FuncionesGoogle f = new FuncionesGoogle();
        f.SendCVSToGoogle("c:\\test", textbox2.text);
    }
    

    Prueba la componente, ejecutando el proyecto Test:

    Revisa el Log, si lo implementaste, si se guardó que se subió ok, ve a ver a http://spreadsheet.google.com y ve si se subió:

    Si abres el archivo de prueba se verá el contenido:


    Llamado desde VB6


    NOTA: Asumo que es otro PC.

    Crea una carpeta con permiso de escritura, por ejemplo C:\temp.

    Adentro deja los archivos:

  • GoogleSpread.dll y GoogleSpread.tlb
  • Además las DLL:
    Newtonsoft.Json.dll, Google.GData.AccessControl.dll, Google.GData.Client.dll, Google.GData.Extensions.dll, Google.GData.Spreadsheets.dll, Google.GData.Documents.dll.
    Nota: Estas 6 dll se obtienen al instalar Google_Data_API_Setup_2.2.0.0.msi obtenido de: https://code.google.com/p/google-gdata/downloads/list y que por defecto quedan en: C:\Program Files (x86)\Google\Google Data API SDK\Redist.

  • Además se debe copiar el archivo .config generado que luego lo configuraremos.

  • Referencia al tlb

    Referencia al archivo tlb:


    Seleccionar el archivo .tlb y luego presionar Aceptar.

    Llamar a la función de .Net entregandole una carpeta y un archivo csv:

    Set objNET = New GoogleSpread.FuncionesGoogle
    objNET.SendCVSToGoogle "C:\", "test.csv"
    

    Genera el .exe de salida, llamalo "testvb.exe" y dejalo en C:\temp.

    En la carpeta C:\temp entonces tenemos las 6 dll, el assembly .dll y el .tlb, el archivo tesvb.exe y el .config (con la configuración del correo) que lo debes llamar test.exe.config para que el assembly .Net lo lea correctamente.

    Ahora ejecuta el testvb.exe y cruza los dedos.

    Bueno, espero les sirva.

    jueves, 22 de enero de 2015

    TreeList con Kendo UI for ASP.NET MVC

    Este post va dirigido a los que hemos trabajado con Kendo UI para ASP.NET MVC y no hemos podido hacer un simple TreeList en menos de 1 hora.

    Primero, tenía instalada la versión de Kendo 2014.2.1008 y escribía en mi vista:
    @(Html.Kendo().TreeList
    

    Y arrojaba error ya que no encontraba el .TreeList, luego viendo lo ejemplo de otras versiones me di cuenta que está incluido en la versión 2014.3.1316 por lo que recomiendo bajar esta. Además que actualmente es la última (por lo que averigüé el TreeList se agregó a Kendo en la versión 2014.3.1119).

    La documentación es mucha, de hecho me perdí tanto leer, al menos en Telerik encontré:

    http://docs.telerik.com/kendo-ui/api/javascript/ui/treelist
    http://docs.telerik.com/kendo-ui/aspnet-mvc/helpers/treelist/overview
    http://demos.telerik.com/kendo-ui/treelist/index

    Luego tenemos los ejemplos que vienen en el ZIP del Kendo comercial (pagado):

    - telerik.ui.for.aspnetmvc.2014.3.1316.commercial.zip, para MVC/ASPX, tiene muchos ejemplos, uno de TreeList que es muy complejo quizá para alguien que quiere hacer su primer TreeList.
    - telerik.kendoui.professional.2014.3.1316.commercial.zip, para JQuery, con otros muchos ejemplos pero el código de los controladores se leen de un servicio en línea por ejemplo:
    var service = "http://demos.telerik.com/kendo-ui/service";
    

    Luego de ver mucho código, partí de cero y era más simple de lo que pensaba.

    Primero tenemos una vista que llama a otra vista Header para la carga de los JS/CSS necesario, como JQuery/Kendo:
    <! DOCTYPE html >
    <html>
    <head>
        @Html.Partial("~/HeaderCssJs.cshtml")
    </head>
    
        
    
        <div class="demo-section k-header">
            @(Html.Kendo().TreeList<models .miviewmodel="">()
            .Name("treelist")
            .Columns(columns =>
            {
                columns.Add().Field(e => e.FirstName).Width(220).TemplateId("photo-template");
                columns.Add().Field(e => e.LastName).Width(160);
                columns.Add().Field(e => e.Address).Title("Address");            
            })
            .Filterable()
            .Sortable()
            .DataSource(dataSource => dataSource
                .Read(read => read.Action("Read", "Home"))
                .ServerOperation(false)
                .Model(m =>
                {
                    m.Id(f => f.EmployeeId);
                    m.ParentId(f => f.ReportsTo);
                    m.Field(f => f.FirstName);
                    m.Field(f => f.LastName);
                    m.Field(f => f.ReportsTo);
                })
            )
            .Height(540)
            )
        </div>
    <style>
            .employee-photo {
                display: inline-block;
                width: 40px;
                height: 40px;
                border-radius: 50%;
                background-size: 40px 44px;
                background-position: center center;
                vertical-align: middle;
                line-height: 41px;
                box-shadow: inset 0 0 1px #999, inset 0 0 10px rgba(0,0,0,.2);
            }
    
            .employee-name {
                display: inline-block;
                vertical-align: middle;
                line-height: 41px;
                padding-left: 10px;
            }
        </style>
    </body>
    
    En el Controlador tenemos 3 actions.

    El que carga inicialmente la Vista:
    public ActionResult Index()
    {
        return View();
    }
    
    El Reader que se llama desde el Evento del TreeList:
    public JsonResult Read([DataSourceRequest] DataSourceRequest request)
    {
       var result = GetDetalle().ToTreeDataSourceResult(request);           
       return Json(result, JsonRequestBehavior.AllowGet);
    }
    
    El que carga el enumerador. Lo mejor es que no importa el orden que se hagan los Add, el Tree List de Kendo se encarga de ordenar los hijos:
    private IEnumerable GetDetalle()
    {
        List<MiViewModel> lista = new List< MiViewModel >();
        lista.Add(new MiViewModel () { EmployeeId = 9, FirstName = "xxxxx", LastName = "yyy", hasChildren = false, ReportsTo = 3, Address = "999", Color = "rojo" });
        lista.Add(new MiViewModel () { EmployeeId = 1, FirstName = "naldo", LastName = "Gonz", hasChildren = true, ReportsTo = null, Address = "123", Color = "rojo" });
        lista.Add(new MiViewModel () { EmployeeId = 2, FirstName = "pepe", LastName = "Super", hasChildren = false, ReportsTo = 1, Address = "456", Color = "rojo" });
        lista.Add(new MiViewModel () { EmployeeId = 3, FirstName = "juan", LastName = "Man", hasChildren = false, ReportsTo = null, Address = "789", Color = "verde" });
        return from a in lista
               select a;
    }
    
    En mi caso tengo una carpeta “img” con dos imágenes “rojo.jpg” y “verde.jpg”.

    Por ultimo la clase MiViewModel:
    using System.Web;
    
    namespace Models
    {
        public class MiViewModel
        {
            public MiViewModel() { }       
            public int? EmployeeId { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public string Pos { get; set; }
            public string Color { get; set; }
            public string Address { get; set; }
            public bool hasChildren { get; set; }        
            public int? ReportsTo { get; set; }       
        }        
    }
    

    El resultado es:


    Ahora veamos algunas cosillas que podemos agregar. Por ejemplo cambiar el texto que muestra el botón de arriba, en vez de "Add New Record", ponemos "Crear":
    @(Html.Kendo().TreeList<Modelo>()
    .Toolbar(toolbar => toolbar.Create().Text("Crear"))
    
    Como hacer botones propios en el header del TreeList
    En la definición del TreeList:
    .Toolbar(toolbar =>
    {
       toolbar.Custom().Name("botoncrear").Text("Crear");
       toolbar.Custom().Name("mycommand").Text("View X");
    })
    
    Luego en la sección scripts ponemos el handler del botón que lee una propiedad del modelo por ejemplo (en base a la celda seleccionada) y lo muestra en un alert:
    
    
    Como dejar una fila seleccionable (al hacer clic en una celda toda la fila queda seleccionada):
    @(Html.Kendo().TreeList<Modelo>()
      .Selectable(true)
    
    Como obtener dicha fila seleccionada con jQuery:
    var treeList = $("#contenedor").data("kendoTreeList");
    var row = treeList.select();       
    
    Como cambiarle el estilo a una fila seleccionada, por ejemplo seleccionarla y dejarla con estilo oblique (parecido a Italic):
    var treeList = $("#contenedor").data("kendoTreeList");
    var row = treeList.select();            
    row.css('font-style', 'oblique');
    

    Espero les sirva.

    jueves, 29 de mayo de 2014

    Agregar o Editar Binding en IIS con AppCmd.exe o C#

    Hola a todos, he estado jugando con IIS 8 (Windows 8) y me he puesto a ver como agregar y editar un Binding mediante línea de comandos o por código C#.

    Sabemos que un Binding es un elemento que permite configurar información requerida por los request que se quieren comunicar con un sitio Web. Un sitio web en IIS puede tener N bindings. Es especialmente útil cuando quieres que diferentes direcciones de sitios web usen un sitio web (archicos y clases .net) unico. Por ejemplo, un holding de empresas que poseen diferentes URL pero el servidor que las procesa es único.
    Un binding se conforma de: IP:Puerto:HostName

    Vamos por partes, primero creamos un sitio Web vacio y dejamos el binding por defecto:

    Ahora, agregamos otro binding para ese mismo sitio desde el mismo IIS para hacer la prueba de lo facil que es usando Add:

    Ahora empieza lo interesante, vamos agregar un tercer binding por línea de comandos usando appcmd.exe.
    Viendo el sitio oficial de Technet, no dice mucho, da un ejemplo básico de como usarlo en la línea de comandos.
    Ejecuta el Command prompt en Modo Administrador sino con algunas instrucciones te puede dar el error:
    Cannot read configuration file due to insufficient permissions 
    

    Partamos, ejecutando la ayuda del comando SET de appcmd:
    > C:\Windows\System32\inetsrv\appcmd.exe set site "test" /?
    
    Ahí verás las opciones de SET, arriba se ven las opciones para Binding:
    Luego ejecuta:
    > C:\Windows\system32>C:\Windows\System32\inetsrv\appcmd.exe set site /site.name:test /+bindings.[protocol='http',bindingInformation='*:80:mitest']
    

    Donde: test es mi sitio web ya configurado, http es mi protocolo y *:80:mitest es el binding en si: * es todas las IP, 80 es el puerto y mitest es el nombre del nuevo binding.
    Con el + agregas binding a la colección, con el - eliminas.
    No puedes ejecutar directamente > appcmd set ... como en el ejemplo ya que no está en las variables de ambiente pordefecto, así que debes usar la rura completa C:\Windows\inetsrv.

    Ahora ahora todo ok, tenemos 3 binding.

    Si tratamos de modificar un binding por línea de comandos, nos encontramos con el primer problema, no tiene ID (un BindingID), tienes que buscarlo, para luego modificarlo. Si ejecutaste la ayuda de SET, te darás cuenta que no hay algun comando que ayude (al menos no lo encontré).

    Ahora veamos como agregar por linea de comandos pero ejecutado por código C#:
    protected void Button1_Click(object sender, EventArgs e)
    {
     string windir = Environment.GetEnvironmentVariable("windir");
     
     string comando = windir +"\\System32\\inetsrv\\appcmd.exe set site /site.name:test /+bindings.[protocol='http',bindingInformation='*:80:mitest']";
         
     ExecuteCommandAsync(comando);
    }
    
    public void ExecuteCommandSync(object command)
    {
        try
        {
            System.Diagnostics.ProcessStartInfo procStartInfo = new System.Diagnostics.ProcessStartInfo("cmd", "/c " + command);
           
            procStartInfo.RedirectStandardOutput = true;
            procStartInfo.UseShellExecute = false;                
            procStartInfo.CreateNoWindow = true;
            procStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
    
            System.Diagnostics.Process proc = new System.Diagnostics.Process();
            proc.StartInfo = procStartInfo;
            proc.Start();
            
            string result = proc.StandardOutput.ReadToEnd();
            
            Console.WriteLine(result);
            Debug.WriteLine(result);
        }
        catch (Exception objException)
        {
            Debug.WriteLine("Error:" + objException.Message);
        }
    }
    
    public void ExecuteCommandAsync(string command)
    {
        try
        {
            
            Thread objThread = new Thread(new ParameterizedThreadStart(ExecuteCommandSync));
            
            objThread.IsBackground = true;
            
            objThread.Priority = ThreadPriority.Normal;
            
            objThread.Start(command);
        }
        catch (ThreadStartException objException)
        {
            Debug.WriteLine("Error:" + objException.Message);
        }
        catch (ThreadAbortException objException)
        {
            Debug.WriteLine("Error:" + objException.Message);
        }
        catch (Exception objException)
        {
            Debug.WriteLine("Error:" + objException.Message);
            
        }
    }
    
    El problema acá, es un tema muy comentado en foros de ayuda como StackOverflow, ya que da un maldito error de permisos:
    Cannot read configuration file due to insufficient permissions.
    
    Ya sea ejecutando el Visual Studio como administrador, o dando permisos en la carpeta inetsrv no pude sacar este error. Así que no lo dejé ahí y busque otra vía.

    Ya que todo lo anterior no funcionó, queda la alternativa de encontrar alguna clase maestra. Vamos a listar, agregar y modificar via código C# con la clase ServerManager.
    Primero, en tu proyecto en Visual Studio debes referenciar a la DLL: C:\Windows\System32\inetsrv\Microsoft.Web.Administration.dll.
    Lo otro es que el Pool confugurado en el sitio Web en el IIS, en mi caso uso DefaultAppPool, debe estar correctamente configurado lo que es Identity, sino te dará errores de permiso.
    Por ultimo usamos la clase ServerManager donde podemos manipular AppPools, Sitios o Binding.

    Por ejemplo, con ServerManager tenemos todo el control y podemos recorrer los Sitios y los AppPools de IIS:
    using (ServerManager serverManager = new ServerManager())
    {
        if (serverManager.ApplicationPools == null)
            return;
    
        for (int i = 0; i < serverManager.Sites.Count; i++)
        {
            Microsoft.Web.Administration.ApplicationPoolCollection appPoolCollection = serverManager.ApplicationPools[i];
        }
    
        if (serverManager.Sites == null)
            return;
    
        for (int i = 0; i < serverManager.Sites.Count; i++)
        { 
           Microsoft.Web.Administration.BindingCollection bindingCollection = serverManager.Sites[i].Bindings;
        }
    }
    
    Si queremos listar todos los bindings e información de todos los sitios instalados en IIS, es simple:
    public void ListBinding()
    {
        Label1.Text = "";
        using (ServerManager serverManager = new ServerManager())
        {      
            if (serverManager.Sites == null)
                return;
    
            for (int i = 0; i < serverManager.Sites.Count; i++)
            {
                Debug.WriteLine(Environment.NewLine);
                Debug.WriteLine(serverManager.Sites[i].Id.ToString() + " - " + serverManager.Sites[i].Name);
    
                Label1.Text += "\r\n" + "[" + serverManager.Sites[i].Id.ToString() + " - " + serverManager.Sites[i].Name + "]";
    
                Microsoft.Web.Administration.BindingCollection bindingCollection = serverManager.Sites[i].Bindings;
    
                for (int j = 0; j < bindingCollection.Count; j++)
                {
                    Debug.WriteLine(bindingCollection[j].Host +" " + bindingCollection[j].BindingInformation);
    
                    Label1.Text += " Binding (" + j.ToString() + "): " + bindingCollection[j].Host + " " + bindingCollection[j].BindingInformation;                
                }
            }
        }
    }
    
    Este es el código que permite agregar un binding a un sitio dado. En mi caso uso un "keyName" que es el nombre del nuevo elemento (un nuevo sub-sitio que puede cambiar su información de binding). También requiere el ID del sitio Web:
    public void AddBinding(int id, SiteBinding siteBinding, string KeyName)
    {
        using (ServerManager serverManager = new ServerManager())
        {
            if (serverManager.Sites == null)
                return;
    
            for (int i = 0; i < serverManager.Sites.Count; i++)
            {
                if (serverManager.Sites[i].Id == id)
                {
                    Microsoft.Web.Administration.BindingCollection bindingCollection = serverManager.Sites[i].Bindings;
                    
                    Microsoft.Web.Administration.Binding binding = serverManager.Sites[i].Bindings.CreateElement("binding");
    
                    binding["protocol"] = siteBinding.Protocol;
    
                    binding["bindingInformation"] = string.Format(@"{0}:{1}:{2}", siteBinding.IPAddress, siteBinding.Port.ToString(), siteBinding.HostName);
    
                    bool existe = false;
                    for (int j = 0; j < bindingCollection.Count; j++)
                    {
                        if (bindingCollection[j].Host == siteBinding.HostName)
                        {
                            existe = true;
                            break;
                        }
                    }
    
                    if (existe == false)
                    {
                        bindingCollection.Add(binding);
                        serverManager.CommitChanges();
                    }
    
                    if (bindingNameBase.ContainsKey(newKeyName) == false)
                    {
                        bindingNameBase.Add(newKeyName, siteBinding.HostName);
                    }
    
                    
                }
            }
        }
    }
    
    El código para editar un binding existente de un sitio web existente. Uso el hashtable para mantener los IDs de los Binding:
    public void EditBinding(int id, SiteBinding siteBinding, string keyName)
    {
        using (ServerManager serverManager = new ServerManager())
        {
            if (serverManager.Sites == null)
                return;
    
            for (int i = 0; i < serverManager.Sites.Count; i++)
            {
                if (serverManager.Sites[i].Id == id)
                {
                    Microsoft.Web.Administration.BindingCollection bindingCollection = serverManager.Sites[i].Bindings;
    
                    // se elimina el binding
                    Microsoft.Web.Administration.Binding bindingTmp = null;
                    for (int j = 0; j < bindingCollection.Count; j++)
                    {
                        if (bindingCollection[j].Host == bindingNameBase[keyName].ToString())
                        {
                            bindingTmp = bindingCollection[j];                                
                            break;
                        }
                    }
    
                    if (bindingTmp != null)
                    {
                        bindingCollection.Remove(bindingTmp);
    
                        //se crea de nuevo
                        Microsoft.Web.Administration.Binding binding = serverManager.Sites[i].Bindings.CreateElement("binding");
                        binding["protocol"] = siteBinding.Protocol;
                        binding["bindingInformation"] = string.Format(@"{0}:{1}:{2}", siteBinding.IPAddress, siteBinding.Port.ToString(), siteBinding.HostName);
    
                        bool existe = false;
                        for (int j = 0; j < bindingCollection.Count; j++)
                        {
                            if (bindingCollection[j].Host == siteBinding.HostName)
                            {
                                existe = true;
                                break;
                            }
                        }
    
                        if (existe == false)
                        {
                            bindingCollection.Add(binding);
                            serverManager.CommitChanges();
    
                            bindingNameBase[keyName] = siteBinding.HostName;
                        }
                    }                        
                }
            }
        }
    }
    
    Este código se puede refactorizar, (para los más quisquillosos), es solo una muestra de la capacidad de la clase ServerManager.

    En resumen, tenemos:

    Con IIS Manager:
    • Se puede agregar un binding.
    • Se puede modificar un binding.
    • Se puede eliminar un binding.

    Con AppCmd.exe desde Command Promt:
    • Se puede agregar un binding.
    • No se puede modificar un binding de forma fácil (se debe poder pero no lo encontré, si alguien conoce la solución, que lo coloque en los comentarios).
    • Se puede eliminar un binding.

    Con AppCmd.exe desde C#:
    • No se puede hacer nada, da error de permisos. Si alguien conoce la solución, que lo coloque en los comentarios.
    Con ServerManager desde C#:
    • Se puede agregar un binding.
    • Se puede modificar un binding.
    • Se puede eliminar un binding.
    Espero les haya servido. Nos vemos!

    Links útiles:

    miércoles, 26 de febrero de 2014

    Como reducir la Cyclomatic Complexity (Complejidad Ciclomática) de un método

    Este artículo no explica que es la Complejidad Ciclomática o CC, sino como se puede reducir. ¿Porqué reducirla? Permite reducir el riesgo de la mantención del código ante algún cambio principalmente entre otras maravillas.

    Hay mucha teoría acerca de las ventajas de aplicar esto, léelo por tu cuenta por ejemplo en https://es.wikipedia.org/wiki/Complejidad_ciclom%C3%A1tica.

    Visual Studio 2010, 2012 y 2013 tienen un excelente Analizador de Código, donde arroja un warning o error de acuerdo a ciertas reglas. Por ejemplo por defecto indicará un warning cuando la CC sea mayor a 25.
    PHP tiene una herramienta similar llamada PHPLOC.

    Tips en General

    • Crea métodos privados usando nombres claros para el código duplicado.
    • Crea métodos privados con nombres claros que contengan funcionalidades encapsuladas no directamente relacionadas con la lógica de negocio. Cómo por ejemplo:

    1. Acceso a datos
    2. Llamadas a Web services
    3. Código de patrones de diseño, etc.
    4. Crea métodos privados con nombres claros para encapsular cada pieza con lógica de dominio
    5. Refactoriza cada método usando las siguientes reglas:

    A. Realiza validación de campos o datos de entrada primero y devolver un error o lanza excepciones si falla alguna la validación.
    B. Realiza la validación de los datos del negocio y retorna un error o lanza si la validación falla
    C. Recién ahora ejecuta la operación misma del negocio
    D. Agrega comentarios de porqué haces tal cosa y no otra
    E. Aplica las siguientes reglas de oro:

    - No declares las variables al inicio del método, en vez de ello declara las variables en el punto exacto que las necesites.
    - No reutilices la misma variable para múltiples propósitos.
    - Evita las siguientes prácticas que muchos desarrolladores aplican jurando que ellos optimizan el código para mejor performance (los compiladores actuales no lo necesitan). Estas acciones reducen las lecturas pero no dan ninguna mejora de performance:
    - No intentes minimizar el número de llamadas a métodos.
    - De nuevo, no declares las variables al inicio del método, sino en el punto que la requieras.
    - Permite varios returns desde un método, puede ayudar a veces a mejorar el CC.
    - Si usas Using (StreamReader x...) no hagas adentro un x.Close() ya que el método Close() se gatilla automáticamente con el Dispose() del Using.
    - No uses estas formas de evaluar equivalencia:
    - if (10 == n)
    - if (n.Equals(10))
    - No uses string.Concat para concatenar strings, en cambio usa "+".

    Ejemplo

    Con tal de ejemplificar lo anterior, tomemos un caso de una aplicación bancaria:

    Este método realiza el siguiente algoritmo:

    Revisa si el monto solicitado a retirar es menor a la cantidad almacenada en su cuenta corriente
    Si es así, realiza el retiro
    Si no,
      Chequea si tiene línea de crédito para ver si el puede realizar un retiro
    Si no tiene cupo en la línea de crédito, rechaza la solicitud
    Si no,
      Calcula el máximo de dinero que pueda sacar sin necesidad de aprobación de un ejecutivo. Este cálculo depende del tipo de cuenta
      Si el total de dinero en la línea le alcanza, libera el pago
      Si no, iniciar el flujo de aprobación por un ejecutivo

    Este método de negocio usa las propiedades AccountRepository y TransactionHistoryRepository de la clase para realizar el acceso a datos y la propiedad CreditHistoryServiceAgent para invocar a los Web services.
    Este método usa un patrón de diseño estratégicamente para realizar cierta lógica de negocio según cada tipo de cuenta. La instancia se crea usando el patrón Abstract Factory y se aplica el factory correcto usando el tipo cuenta que está cargada en un diccionario.
    Este método también inicializa el flujo de aprobación si se requiere.

    Este es el método principal Withdraw(...) que permite retiro de dinero:
    /// 
    /// Withdraw money from a bank account
    /// 
    /// withdrawal request
    /// A response that indicate the results of the processing of request
    /// Throws when the input request object is null
    /// Throws when the requested account no is invalid
    public TransactionResponse Withdraw(WithdrawRequest request)
    {
        TransactionResponse response = null;
     
        //Check if the request is null
        if (request != null)
        {
            using (TransactionScope transactionScope = new TransactionScope())
            {
                //Get account
                Account account = AccountRepository.GetAccount(request.AccountNo);
     
                //Check if account is null
                if (account != null)
                {
                    //Check if amount is less than the account balance
                    if (request.Amount < account.Balance)
                    {
                        //Reduce the amout from the balance
                        account.Balance -= request.Amount;
                        AccountRepository.Save(account);
     
                        //Create history entry
                        TransactionHistory history = new TransactionHistory();
                        history.TransactionType = (int)TransactionType.Withdraw;
                        history.AccountId = account.AccountId;
                        history.Time = DateTime.Now;
                        history.Amount = request.Amount;
                        TransactionHistoryRepository.Add(history);
     
                        //Return success response
                        response = new TransactionResponse()
                        {
                            ResponseCode = (int)TransactionResponseCodes.Succeeded,
                            ResponseMessage = Resources.TransactionCompleted,
                        };
                    }
                    else
                    {
                        //Check if OD can be issued based for the customer
                        PersonIdentity identify = new PersonIdentity()
                        {
                            FullName = account.Owner.FullName,
                            NationalIdentityCardNo = account.Owner.NationalIdentityCardNo,
                        };
                        bool canIssueOD = CreditHistoryServiceAgent.CanIssueOD(identify);
     
                        if (canIssueOD)
                        {
                            //Process OD
     
                            //Retrieve the amount that can be withdrawn without any approval
                            IAccountStrategyFactory stratergyFactory;
                            if (!accountStratergyFactories.TryGetValue(account.Type, out stratergyFactory))
                            {
                                throw new ArgumentException("Invalid Account Type");
                            }
     
                            IAccountWithdrawalStrategy withdrawalStratergy = stratergyFactory.GetWithdrawalStrategy();
                            decimal withdrawalLimit = withdrawalStratergy.GetODLimitWithoutApproval(account);
     
                            //check if the amount is less than the withdrawal limit
                            if (account.Balance - request.Amount > withdrawalLimit)
                            {
                                //reduce the amount from account balance
                                account.Balance -= request.Amount;
                                AccountRepository.Save(account);
     
                                //create transaction history entry
                                TransactionHistory history = new TransactionHistory();
                                history.TransactionType = (int)TransactionType.Withdraw;
                                history.Time = DateTime.Now;
                                history.Amount = request.Amount;
                                TransactionHistoryRepository.Add(history);
     
                                //Return success response
                                response = new TransactionResponse()
                                {
                                    ResponseCode = (int)TransactionResponseCodes.Succeeded,
                                    ResponseMessage = Resources.TransactionCompleted,
                                };
                            }
                            else
                            {
                                //Initiate approval workflow
                                IWithdrawalApprovalWorkflow workflow = WorkflowFactory.GetWithdrawApprovalWorkflow();
                                workflow.Request = request;
                                workflow.Start();
     
                                //Return pending approval response
                                response = new TransactionResponse()
                                {
                                    ResponseCode = (int)TransactionResponseCodes.PendingApproval,
                                    ResponseMessage = Resources.PendingApproval,
                                };
     
                            }
                        }
                        else
                        {
                            //Return request failed response
                            response = new TransactionResponse()
                            {
                                ResponseCode = (int)TransactionResponseCodes.Failed,
                                ResponseMessage = Resources.FailedDueToBadCreditHistory,
                            };
                        }
                    }
                }
                else
                {
                    throw new ArgumentException("Invalid Account No");
                }
     
                //Mark the transaction to be commited
                transactionScope.Complete();
            }
        }
        else
        {
            throw new ArgumentNullException("request");
        }
     
        return response;
    }
    

    Refactorizando

    1. Elimina el código duplicado. Crea un nuevo método que guarde en TransactionHistoryRepository y lo llamas 2 veces de Withdraw(...):
    private TransactionResponse PerformWithdrawal(WithdrawRequest request, TransactionResponse response, Account account)
    {
        //Reduce the amout from the balance
        account.Balance -= request.Amount;
        AccountRepository.Save(account);
     
        //Create history entry
        TransactionHistory history = new TransactionHistory();
        history.TransactionType = (int)TransactionType.Withdraw;
        history.AccountId = account.AccountId;
        history.Time = DateTime.Now;
        history.Amount = request.Amount;
        TransactionHistoryRepository.Add(history);
     
        //Return success response
        response = new TransactionResponse()
        {
            ResponseCode = (int)TransactionResponseCodes.Succeeded,
            ResponseMessage = Resources.TransactionCompleted,
        };
        return response;
    }
    

    2. Saca la funcionalidad que no está relacionada directamente con la lógica de negocio hacia métodos separados, esto ayuda a mejorar la mantenibilidad del código, por lo tanto se tiene un Withdraw(...) y un PerformWithdrawal(...) con llamadas a otros métodos:
    public TransactionResponse Withdraw(WithdrawRequest request)
    {
        TransactionResponse response = null;
     
        //Check if the request is null
        if (request != null)
        {
            using (TransactionScope transactionScope = new TransactionScope())
            {
                //Get account
                Account account = AccountRepository.GetAccount(request.AccountNo);
     
                //Check if account is null
                if (account != null)
                {
                    //Check if amount is less than the account balance
                    if (request.Amount < account.Balance)
                    {
                        response = PerformWithdrawal(request, response, account);
                    }
                    else
                    {
                        if (CanIssueODBasedOnCreditHistory(account))
                        {
                            //Process OD
     
                            decimal withdrawalLimit = GetODLimitWithoutApproval(account);
     
                            //check if the amount is less than the withdrawal limit
                            if (account.Balance - request.Amount > withdrawalLimit)
                            {
                                response = PerformWithdrawal(request, response, account);
                            }
                            else
                            {
                                response = StartWithdrawApprovalWorkflow(request, response);
                            }
                        }
                        else
                        {
                            //Return request failed response
                            response = GetFailedResponse(response);
                        }
                    }
                }
                else
                {
                    throw new ArgumentException("Invalid Account No");
                }
     
                //Mark the transaction to be commited
                transactionScope.Complete();
            }
        }
        else
        {
            throw new ArgumentNullException("request");
        }
     
        return response;
    }
     
    private TransactionResponse PerformWithdrawal(WithdrawRequest request, 
    TransactionResponse response, Account account)
    {
        //Reduce the amout from the balance
        account.Balance -= request.Amount;
        AccountRepository.Save(account);
     
        CreateWithdrawalHistoryEntry(request, account);
     
        //Return success response
        return GetSuccessResponse(ref response);
    }
    

    3. Crea métodos privados con nombres claros para cada pieza de la lógica del dominio. Esto ayuda a simplificar la lógica y hacer el código mantenible. El método Withdraw(...) se vería así:
    public TransactionResponse Withdraw(WithdrawRequest request)
    {
        TransactionResponse response = null;
     
        //Check if the request is null
        if (request != null)
        {
            using (TransactionScope transactionScope = new TransactionScope())
            {
                //Get account
                Account account = AccountRepository.GetAccount(request.AccountNo);
     
                //Check if account is null
                if (account != null)
                {
                    //Check if amount is less than the account balance
                    if (request.Amount < account.Balance)
                    {
                        response = PerformWithdrawal(request, response, account);
                    }
                    else
                    {
                        response = ProcessAsOD(request, response, account);
                    }
                }
                else
                {
                    throw new ArgumentException("Invalid Account No");
                }
     
                //Mark the transaction to be commited
                transactionScope.Complete();
            }
        }
        else
        {
            throw new ArgumentNullException("request");
        }
     
        return response;
    }
     
    private TransactionResponse ProcessAsOD(WithdrawRequest request, TransactionResponse 
    response, Account account)
    {
        if (CanIssueODBasedOnCreditHistory(account))
        {
            //Process OD
     
            decimal withdrawalLimit = GetODLimitWithoutApproval(account);
     
            //check if the amount is less than the withdrawal limit
            if (account.Balance - request.Amount > withdrawalLimit)
            {
                response = PerformWithdrawal(request, response, account);
            }
            else
            {
                response = StartWithdrawApprovalWorkflow(request, response);
            }
        }
        else
        {
            //Return request failed response
            response = GetFailedResponse(response);
        }
        return response;
    }
    
    

    4. Refactoriza usando las reglas indicadas:
    - Valida la entrada y retorna una excepción si hay error.
    - Valida los datos del negocio y retorna una excepción si hay error.
    - Ejecuta la operación de negocio en si.
    - Comenta lo que creas prudente.
    public TransactionResponse Withdraw(WithdrawRequest request)
    {
        TransactionResponse response = null;
     
        //Validate input parameters
        if (request != null)
        {
            throw new ArgumentNullException("request");
        }
     
        //Perform business logic in a transaction
        using (TransactionScope transactionScope = new TransactionScope())
        {
            //Get account
            Account account = AccountRepository.GetAccount(request.AccountNo);
            if (account != null)
            {
                throw new ArgumentException("Invalid Account No");
            }
     
            if (request.Amount < account.Balance)
            {
                //If the requested amount is less than the balance then 
                //the withdrawal can be performed directly
                response = PerformWithdrawal(request, response, account);
            }
            else
            {
                //Otherwise the request has to be performed as an OD
                response = ProcessAsOD(request, response, account);
            }
     
            //Mark the transaction to be committed
            transactionScope.Complete();
        }
     
        return response;
    }
     
    private TransactionResponse ProcessAsOD(WithdrawRequest request, TransactionResponse 
    response, Account account)
    {
        //Check if the requested customer can receive and OD based on his credit history
        //Reject the request if he has a bad credit history
        if (CanIssueODBasedOnCreditHistory(account))
        {
            return GetFailedResponse(response);
        }
     
        //Check if the requested amount is less than the OD limit for the account type
        //If so perform the request
        //Else initiate the approval workflow to get branch managers approval
        decimal withdrawalLimit = GetODLimitWithoutApproval(account);
        if (account.Balance - request.Amount > withdrawalLimit)
        {
            return PerformWithdrawal(request, response, account);
        }
        else
        {
            return StartWithdrawApprovalWorkflow(request, response);
        }
    }
     
    private TransactionResponse PerformWithdrawal(WithdrawRequest request, 
    TransactionResponse response, Account account)
    {
        //Reduce the amout from the balance
        account.Balance -= request.Amount;
        AccountRepository.Save(account);
     
        CreateWithdrawalHistoryEntry(request, account);
     
        return GetSuccessResponse(ref response);
    }
    

    5. Aplica las siguientes reglas:
    - Declara las variables sólo cuando las usarás y no al inicio del método.
    - No uses la misma variable 2 o más veces.

    Ejemplo, si tenemos un código correctamente escrito:
    public double FindEffortInManDays(RequirementDefinition requirements, 
                           ComplexityFactors complexity, EnvironmentFactors environment)
    {
        double functionPoints = CalculateFunctionPoints(requirements);
        double adjustedFunctionPoints = CalculateAdjustedFunctionPoints(functionPoints, complexity);
        double effortInManDays = CalculateEffort(adjustedFunctionPoints, environment);
        return effortInManDays;
    }
    
    Si aplicamos lo de declarar las variables al inicio:
    public double FindEffortInManDays3(RequirementDefinition requirements, 
                       ComplexityFactors complexity, EnvironmentFactors environment)
    {
        double functionPoints = 0.0;
        double adjustedFunctionPoints = 0.0;
        double effortInManDays = 0.0;
     
        functionPoints = CalculateFunctionPoints(requirements);
        adjustedFunctionPoints = CalculateAdjustedFunctionPoints(functionPoints, complexity);
        effortInManDays = CalculateEffort(adjustedFunctionPoints, environment);
        return effortInManDays;
    }
    
    Mira lo que pasa si cambias de orden una línea como se se ve abajo, el resultado será totalmente distinto, sin que el compilador lo note:
    public double FindEffortInManDays31(RequirementDefinition requirements, 
    ComplexityFactors complexity, EnvironmentFactors environment)
    {
        double functionPoints = 0.0;
        double adjustedFunctionPoints = 0.0;
        double effortInManDays = 0.0;
     
        adjustedFunctionPoints = CalculateAdjustedFunctionPoints(functionPoints, complexity);
        functionPoints = CalculateFunctionPoints(requirements);
        effortInManDays = CalculateEffort(adjustedFunctionPoints, environment);
        return effortInManDays;
    }
    
    Un impacto similar es reutilizando una misma variable. En el ejemplo de abajo, se usa una misma variable para mantener dos cantidades que lógicamente son distintas:
    public double FindEffortInManDays2(RequirementDefinition requirements, 
    ComplexityFactors complexity, EnvironmentFactors environment)
    {
        double functionPoints = CalculateFunctionPoints(requirements);
        functionPoints = CalculateAdjustedFunctionPoints(functionPoints, complexity);
        double effortInManDays = CalculateEffort(functionPoints, environment);
        return effortInManDays;
    }
    
    El siguiente código tiene otro impacto, ya que se reutiliza un misma variable y cambia un orden del código, dando un resultado erróneo sin que el compilador lo delate:
    public double FindEffortInManDays21(RequirementDefinition requirements, 
    ComplexityFactors complexity, EnvironmentFactors environment)
    {
        double functionPoints = CalculateFunctionPoints(requirements);
        double effortInManDays = CalculateEffort(functionPoints, environment);
        functionPoints = CalculateAdjustedFunctionPoints(functionPoints, complexity);
        return effortInManDays;
    }
    

    Otros tips

    1. No intentes bajar el número de llamados a métodos

    El compilador C# puede optimizar el performance a medida que recorre el código, reduciendo el número de llamadas a métodos no ganas performance. Sin embargo, dividiendo un gran método en múltiples llamados a métodos chicos, haces el código mas mantenible y reusable. Lo que si, asegúrate de nombrarlos apropiadamente.

    2. Si usas using (var x = new StreamReader) no hagas adentro un x.Close() ya que este se gatilla automáticamente con el mismo Dispose() del using.
    using( var reader = new StreamReader(file) )
    {
        variable = reader.ReadToEnd();
        reader.Close(); // Esta linea está de más!!!
    }
    
    Una alternativa es cerrarlo manualmente pero sólo si no usas using:
    reader = new StreamReader(file);
    variable = reader.ReadToEnd();
    reader.Close();
    

    3. No uses las siguientes formas de validad igualdad

    if (10 == n)
    if (n.Equals(10))


    Escribe las condiciones como:
    if  (n == 10)

    Sin embargo, a pesar del impacto en la lectura, debemos comparar los string con string.Equals como se muestra abajo:

    if (string.Equals(string1, string2, StringComparison.Ordinal))

    Esto porque .Net aplicará las reglas de cultura específicas para comprar la igualdad de los strings, algo que no pasa si sólo nos limitamos a usar el símbolo "==".
    Ahora, StringComparison.Ordinal podría darle resultado inesperado, ya que se basa en la cultura actual del usuario (navegador del usuario, en caso de una aplicación web).

    Por lo tanto siempre tenemos que ser específicos en el método de comparar cadenas. StringComparison.Ordinal ve los caracteres escondidos incluso los que no se ven en el navegador (como caracteres especiales ejemplo U+200E) y los compara, en cambio StringComparison.InvariantCultureIgnoreCase los ignora.

    Los tipos de comparación son:
    StringComparison.CurrentCulture
    StringComparison.CurrentCultureIgnoreCase
    StringComparison.InvariantCulture
    StringComparison.InvariantCultureIgnoreCase
    StringComparison.Ordinal
    StringComparison.OrdinalIgnoreCase
    

    Una buena explicación está de cada caso en MSDN:
    http://msdn.microsoft.com/en-us/library/system.stringcomparison(v=vs.110).aspx

    4. Usa "+" en vez de string.Concat

    Esto es lo correcto:
    a = b + c + d;

    Ya que si usas esto:
    a = string.Concat(a, b, c);

    Es una optimización que el compilador mismo se encarga de hacer por lo que no es necesario hacerlo de nuevo. Además el símbolo "+" es mas legible que "Concat".

    Este artículo lo hice basándome en este http://ruwandotnet.wordpress.com/2012/03/13/59/ (en inglés)

    jueves, 14 de noviembre de 2013

    Capítulo 1 de libro examen para certificación 70-536 en español

    El Examen 70-536 de nombre TS: Microsoft .NET Framework - Application Development Foundation de Microsoft, liberado el 21 de Marzo del 2008, y que actualmente no está vigente, pero sin embargo, es uno de los mejores libros (a mi gusto) donde están los fundamentos de la programación en .Net, específicamente del Framework 2.0 y por ende una sólida base para los que trabajamos con las siguientes versiones del Framework como la versión 4.0 o 4.5.

    Cuando estudié para esta certificación que finalmente no di (de flojo), lo hice con el libro oficial MCTS Self-Paced Training Kit (Exam 70-536): Microsoft® .NET Framework Application Development Foundation, Second edition que en Amazon está en 43,54 dolares unos 21 mil, más el envío a Chile dan un costo total aproximado calculo de unos 28 mil, pero que pillas en Google fácilmente con la palabra clave "70-536 pdf", bueno cuando estudié, realizaba la traducción/transcripción del libro ya que para mí me salía más fácil entender. Este es el primer capítulo, lecciones 1, 2, 3 y 4 traducido al español y que quiero compartir con ustedes ya que lo tenía botado en mi viejo baúl en dropbox:

    Leyenda
    F: Framework

    CAPÍTULO 1 : Fundamentos del Framework

    Lección 1: Tipos por Valor

    • String no es por Valor, sino por Referencia.
    • Al usarse una variable por valor en un procedimiento, se hace una copia de esta, por lo que si se hacen cambios adentro, no se ve afectado la variable al salir
    • Son valores chicos, usan 16 bytes o menos cada uno. Son altamente performantes
    • Tipos de datos numéricos y bool son tipos por valor
    • Son datos datos que están directos en memoria, en una zona llamada "Pila"
    • Hay 3 tipos:

    1. Build-in: los da el Framework
    A. Numéricos: optimizado para Int32, UInt32 y Double
      System.Sbyte -128 a 127
      System.Byte 0 a 255
      System.Int32 / Int C#, Integer en VB –2147483648 a 2147483647   -> Int32 es una estructura y no una clase (ya que esta ultima es un tipo de dato por referencia)
      System.UInt32 0 a 4294967295
      Int64 –9223372036854775808 a 9223372036854775807
      Single –3.402823E+38 a 3.402823E+38
      Double –1.79769313486232E+308 a 1.79769313486232E+308   (más preciso)
      Decimal: –79228162514264337593543950335 a 79228162514264337593543950335   (cientifico/estadistico)
              IntCtr permite que el largo lo defina el SO por ej. Windows 32 o 64 bits, puede ser mas largo o mas corto que el numero que que lo del int
        B. Otros
        System.Char letras unicode
        System.Boolean true/false
        System.IntPr         puntero a memoria
        System.DateTime      momentos en el tiempo

        - Se autodeclaran, tienen implícito un constructor, no requieren new. El constructor le asigna un nulo o 0, pero siempre se debe inicializar. Ejemplo: Bool test = false;
        - C# es case-sensitive, Visual Basic no.
        - Declarar un objeto nulo cuando no se quiere inicializar, se puede usar la sentencia Nullable:
           Nullable b = null;   
           //en C# permite igual   
           bool? b = null;   
           //o también
           int? i = null;
        
        - Para ver si un objeto nullable tiene valor, usar HasValue:
           if (b.HasValue)
        

        2. User-defined
        - Son Estructuras o structs, contienen otros tipos de datos, por ej:
           System.Drawing.Point p = new System.Drawing.Point(20, 30);   // Point es Value Type
        
        - Se pueden crear con la palabra "struct", estas SOLO tienen tipos por VALOR
            struct Cycle
            { 
             int _val, _min, _max;
         
             public Cycle(int min, int max)
             {
             _val = min;
             _min = min;
             _max = max;
             }
        
        - Se puede convertir a tipo por Referencia cambiando Struct por Class, se puede tener 2 variables apuntando a la misma dirección de memoria, quedando en una zona llamada HEAP de la memoria.
        - Al hacer esto se almacena fuera de los 12 bytes de la pila.
        - Usualmente las estructuras son mas eficientes que las clases. Deberías definir estructuras si el tipo anda mejor como tipo por valor que por referencia.
        - Se debe escoger una estructura SOLO si: Lógicamente es como tener un simple valor, tiene un tamaño menor a 16 bytes, no cambiara luego de su creación, no usa tipos por referencia SOLO por Valor.

        3. Enumeraciones
           public enum Genders : int { Male, Female };
           public Genders gender;
        

        Lección 2: Tipos por Referencia

        -La mayoría de los tipos del F son por referencia.
        -Se guarda la dirección de los datos en memoria, no los datos en si, un "puntero" en la pila, en una zona llamada HEAP.
        -El F maneja lo que esta en el HEAP con el Garbaje collector o GC, este escanea la memoria en busca de valores no referenciados.
        -El GC se gatilla cuando el F lo necesita o cuando se ejecuta GC.Collect.
        -Al tener una variable por referencia, si tengo otra que le asigno el valor, en verdad es la copia de la dirección no del valor. Ejemplo:
          struct Numbers
          {
           public int val;
           public Numbers(int _val)
           { val = _val; }
           public override string ToString()
           { return val.ToString(); }
          }  
          Numbers n1 = new Numbers(0);   // -> Numerse es una variable por referencia
          Numbers n2 = n1;
          n1.val += 1;
          n2.val += 2;
          Console.WriteLine("n1 = {0}, n2 = {1}", n1, n2);  // -> N1: 1  N2: 2
          
          Si lo cambiamos a Clase: 
          
          // C#
          Class Numbers
          {
          ... 
        
        La salida es ----> N3: 3 N2: 3 ya que ambos apuntan a la misma dirección no al valor en si. La modificar la referencia, se modifican todas las copias de la referencia.
        Weak refference: es una referencia débil o que puede ser eliminada por el Garbaje Collector a pesar de usar usada y no asignada a null que es cuando el garbaje collector debería eliminarla.
        - Hay 2500 tipo por referencia build-in en el F. No todos derivan del System.ValueType.
        - Tipos por referencia mas comunes: System.Object, System.String, Systen.Text.TextBuilder, System.Array, System.IO.Stream, System.Exception.
        - No todos los tipo de objetos que derivan de System.Object son tipo de objetos por referencia. Puede derivar objeto de tipo por Valor.
        - Strings y String Builders: proveen manipulación de datos.
        - Es mejor usar StringBuilder en vez de String cuando se quiere armar un gran string con pedazos de string mas chicos. No así cuando se quiere buscar o reemplazar.
        -String soporta 32,767 bytes.
           string s = "this is some text to search";  
           s = s.Replace("search", "replace");
        
        -Los string son inmutables en .Net, es decir, cualquier cambio en ellos genera un nuevo string y el viejo pasa a ser marcado como listo para eliminar por el GC. Ejemplo:
           // C#
           string s;
           s = "wombat"; // "wombat"
           s += " kangaroo"; // "wombat kangaroo"
           s += " wallaby"; // "wombat kangaroo wallaby"
           s += " koala"; // "wombat kangaroo wallaby koala" 
        - Solo el último vale y tiene una referencia, los otros se eliminaran por el GC.
        - No tener esos string intermedios ayuda al performance ya que evita que el GC trabaje.
        - Para evitar string intermedios, se puede usar:
        - Métodos Concat, Join o Format de la clase String.
        - Usar clase StringBuilder para trabajar con varios "mutables" strings.
           System.Text.StringBuilder sb = new System.Text.StringBuilder(30);
           sb.Append("wombat"); // Build string.
           sb.Append(" koala");
           string s = sb.ToString(); // Copy result to string.
        
        -La clase String permite sobreescribir operadores de System.Object:
           suma vb + o &  C# +
           igualdad vb =  C# ==
           no igualdad vb <>  c# !=
           asignación vb y c# =
        
        - Como crear y ordenar Arreglos:
           int[] ar = { 3, 1, 2 };   
           Array.Sort(ar);   
           //mostramos los resultados:  
           Console.WriteLine("{0}, {1}, {2}", ar[0], ar[1], ar[2]);
        

        Como crear y usar Streams
        - Los streams se usan para escribir datos en el disco o enviar paquetes por red.
        - El tipo base es System.IO.Stream de otros tipos.
        - Tipos stream para red se ubican en System.Network.Sockets
        - Stream encriptados estan en System.Security.Cryptography namespace.
        - Tipos stream comunes: FileStream, MemoryStream, StreamReader, StreamWriter.
        - StreamReader y StreamWriter te permite escribir y leer archivos del disco.
           use namespace System.IO
           // Escribe en un txt
           StreamWriter sw = new StreamWriter("text.txt");
           sw.WriteLine("Hello, World!");
           sw.Close();   
           // Lee un archivo y muestra
           StreamReader sr = new StreamReader("text.txt");
           Console.WriteLine(sr.ReadToEnd());
           sr.Close();
        

        Como lanzar y capturar Excepciones
        - Las excepciones son "eventos" inesperados que interrumpen la normal ejecución de un assembly.
        - Las excepciones no evitan que un software se caiga sino que sirve para uno se adelante y las capture y maneje
        - Ejemplo:
           use namespace System.IO
           try
           {
            StreamReader sr = new StreamReader(@"C:\boot.ini");
            Console.WriteLine(sr.ReadToEnd());
           }
           catch (Exception ex)
           {
            // If there are any problems reading the file, display an error message
            Console.WriteLine("Error reading file: " + ex.Message);
        
            //Este caso puede arrojar error: file not found error, insufficient privileges error
            //o un error al leer el archivo. Y la ejecucion "salta" al Catch.
           }
        
        - La clase base Exception es muy útil ya que contiene el mensaje de error y mas datos de la aplicación.
        - Uno puede hacer Excepciones propias derivando de System.ApplicationException.
        - Se pueden tener múltiples clases Exceptions para responder diferente a algún tipo de error, el F ejecutara el primer bloque Catch que coincida con el tipo de Exception definido.
        - Se deben ordenar las excepciones desde las mas especificas a la mas global, generalmente Exception.
           Try
            Dim sr As StreamReader = New StreamReader("text.txt")
            Console.WriteLine(sr.ReadToEnd)
            Catch ex As System.IO.FileNotFoundException
             Console.WriteLine("The file could not be found.")
            Catch ex As System.UnauthorizedAccessException
             Console.WriteLine("You do not have sufficient permissions.")
            Catch ex As Exception
             Console.WriteLine("Error reading file: " + ex.Message)
           End Try
        

        -El bloque try/catch soporta un bloque Finally que se ejecuta bien caiga o no en algún Catch. Se usa para cerrar Streams, o limpiar algún objeto.
           StreamReader sr = new StreamReader("text.txt");
           try
           {
            Console.WriteLine(sr.ReadToEnd());
           }
           catch (Exception ex)
           {
            // If there are any problems reading the file, display an error message
            Console.WriteLine("Error reading file: " + ex.Message);
           }
           finally
           {
            // Close the StreamReader, whether or not an exception occurred
            sr.Close();
           }
        

        - Fijarse que el primer bloque StreamReader sr ... se movió afuera del bloque try esto porque el bloque Finally NO pueda acceder a variables declaradas dentro del bloque Try.
        - En general se declaran la variables afuera del bloque try y en el bloque try se programa todo el codigo.
        - Usar un bloque try/catch tiene una baja penalidad en performance pero la tiene.
        - Ejemplo obtener tipo con IF (Object.GetType().IsValueType)
           SByte a = 0;
           Byte b = 0;
           Int16 c = 0;
           Int32 d = 0;
           Int64 e = 0;
           string s = "";
           Exception ex = new Exception();
           object[] types = { a, b, c, d, e, s, ex };
           foreach ( object o in types )
           {
            string type;
            if (o.GetType().IsValueType)
             type = "Value type";
            else
             type = "Reference Type";
            Console.WriteLine("{0}: {1}", o.GetType(), type );
           }
        

        - Unir elementos de un arreglo de string con espacio:
           string s = "Microsoft .NET Framework 2.0 Application Development Foundation";
           string[] sa = s.Split(' ');
           x = string.Join(" ", sa); -> vuelve a estar como antes.
        

        Lección 3: Construyendo clases

        ¿Qué es un Herencia?

        - La consistencia de clases se hace gracias a la herencia e interfaces.
        - La herencia sirve para crear clases a partir de otras o crear nuevos tipos a partir de otros.
        - Por ejemplo la clase Bitmap herada Image pero la extiende, por lo que tiene mas funcionalidades.
        - Puedes crear tu propia Excepcion heredando de System.ApplicationException.
        - Ejemplo obtener tipo con IF (Object.GetType().IsValueType)
           class DerivedException : System.ApplicationException
           {
             public override string Message
             {
               get { return "An error occurred in the application."; }
             }
           }
        
           //Puedes lanzar la exepcion con throw new 
           // C#
           try
           {
              throw new DerivedException();
           }
           catch (DerivedException ex)
           {
              Console.WriteLine("Source: {0}, Error: {1}", ex.Source, ex.Message);
           }
        
        - La nueva excepción tiene .Source por ejemplo ya que aunque no este implementado, al heredar de System.ApplicationException la tiene.
        - La herencia permite el intercambio de clases, por ejemplo si tenemos 5 clases que heredan de System.Drawing.Brush, si el método Graphics.DrawRectangle requiere un objeto System.Drawing.Brush, se le debe pasar alguno de los que heredan y no el padre.

        ¿Que es una interface?

        - Interfaces o "contrato" definen un conjunto común de miembros que todas las clases que la implementan deben proveer.
        - Por ejemplo la interfaz IComparable define el método CompareTo el cual permite comparar 2 clases. Entones todas las clases que implementen la interface IComparable, ya sean clases propias del F o clases tuyas, pueden ser comparadas por la igualdad.
        - La interfaz "IDisposable" es una interface que solo provee un método Dispose.
        - Permite que otros assemblys creen una instancia de tu clase y puedan liberar cualquier recurso que la instancia haya consumido.
        - Usa interface para definir un conjunto de miembros que DEBEN ser implementados por tipos asociados.
        - Permite a uno escribir código para liberar manualmente los recursos.
        - Ejemplo:
           class BigClass : IDisposable
           {
           }
        
        - Luego con Visual Studio, coloca el boton derecho en IDisposable -> Implement Interface -> Implement Interface y Visual Studio automáticamente coloca el código:
           public void Dispose()
           {
              throw new NotImplementedException();
           }
        
        - Otras interfaces muy usadas IComparable, IDisposable, IConvertible, ICloneable, IEquatable, IFormattable.
        - puedes crear tus propias interfaces por si quieres crear múltiples clases que tengan un comportamiento similar.
        - Ejemplo interfaz con 3 miembros:
           // C#
           interface IMessage
           {
           // Send the message. Returns True is success, False otherwise.
           bool Send();
           // The message to send.
           string Message { get; set; }
           // The Address to send to.
           string Address { get; set; }
           }
        
        - Si quieres implementar esa interfaz en tu clase, Visual Studio genera el siguiente template:
           class EmailMessage : IMessage 
           {
            public bool Send()
            {
            throw new Exception("The method or operation is not implemented.");
            }
            public string Message
            {
            get
            {
            throw new Exception("The method or operation is not implemented.");
            }
            set
            {
            throw new Exception("The method or operation is not implemented.");
            }
            }
            public string Address
            {
            get
            {
            throw new Exception("The method or operation is not implemented.");
            }
            set
            {
            throw new Exception("The method or operation is not implemented.");
            }
            }
           }
        
        - Visual Studio permite extraer o armar una Interfaz a partir de los métodos de una clase, con el 2do. botón sobre la clase dada (que puede o no heredar de una interfaz) -> Refactor -> Extract Interface -> seleccionas que miembro quieres extraer, ok
        - Las clases pueden implementar diferentes interfaces a la vez por ejemplo las interfases IComparable, IDisposable:
           class EmailMessage : IMessage, IComparable, IDisposable

        Clases Parciales
        - Son nuevas en .Net 2.0.
        - Permiten dividir una clase en múltiples archivos, con el beneficio de esconder detalles y enfocarse en los aspectos claves.
        - Un Windows form tiene una clase parcial ya que tiene el código generado por el diseñador en la clase media oculta form.Designer.cs. En Visual Basic hay que escoger View All files, en C# esta enable por defecto.
        - Las Clases parciales no son parte del examen.

        ¿Qué es Generics?

        - Son tipos partes del F, permite definir un tipo dejando detalles sin especificar.
        - En vez de especificar los tipos de parámetros o tipos de miembros en clases, puedes dejar que el código lo haga. Esto permite al consumidor de código especificar el tipo que el requiera.
        - Es nuevo en .Net 2.0 y hay muchas preguntas de estas en el examen.
        - Hay clases genéricas por defecto en el namespace de System.Collections.Generic. Incluyendo: Dictionary, Queue, SortedDictionary, y SortedList.
        - Hay tipos genéricos: Nullable y EventHandler (Boolean y Point no lo son).
        - Estas clases funcionan similar a aquellas no genéricas de System.Collections, pero tiene mejoras en performance y son de tipo seguro (safe type), esto quiere decir que no tiene que ver con seguridad, sino que siempre se accede a los objetos asignados de forma compatible.

        ¿Por qué usar Generic?
        - F 1.0 y 1.1 no soportan generic, en vez de eso se usan clases Object para parámetros y miembros que debías castear a otra Clase.
        - Tiene 2 ventajas:
          A. Reduce errores de runtime: el compilador no puede detectar errores si se usa la clase object, por ejemplo si se castea un string a Object y luego de ese Object a int, el compilador no hara catch del error, sino que el runtime lanzará una excepción. Al usar Generic, el compilador detecta el error antes de iniciarse. Adicionalmente puede escribir constraint para limitar el número de clases que usan generic.
          B. Mejora de Performance: Usar Casting requiere lo que se llama "Boxing" o "Unboxing" lo que hace trabajar al procesador y baja el performance, al usar Generic no se usa casting.
        NOTA del autor del libro: En la vida real, en pruebas hechas no hay mejoras sustanciales en performance, bajo 100.000 iteraciones. y son de pocos segundos, por lo que se recomienda usar generic no tanto por performance, pero si por ser "safe type".
        - Ejemplo de clase que usa Object y otra que usa Genérica.
           // C#
           class Obj
           {
            public Object t;
            public Object u;
            public Obj(Object _t, Object _u)  // -> tiene 2 tipos Object
            {
             t = _t;
             u = _u;
            }
           }
           class Gen     // -> tiene los tipos T y U
           {
            public T t;
            public U u;
            public Gen(T _t, U _u)
            {
             t = _t;
             u = _u;
            }
           }
        
        - El código que consume determina si es T y U es Entero o String.
        - Hay una limitante: el código solo funciona si el desarrollo esta hecho para cada instancia de Generic, int o string ya que estamos limitados a la clase Object, igualmente se puede llamar a ToString() o GetHashCode métodos dentro de la clase pero no + o >.
        - Consumir un tipo Generico: aqui se debe especificar el tipo:
           // Add two strings using the Obj class
           Obj oa = new Obj("Hello, ", "World!");
           Console.WriteLine((string)oa.t + (string)oa.u);
           // Add two strings using the Gen class
           Gen ga = new Gen("Hello, ", "World!");
           Console.WriteLine(ga.t + ga.u);
           
           // Add a double and an int using the Obj class
           Obj ob = new Obj(10.125, 2005);
           Console.WriteLine((double)ob.t + (int)ob.u);
           // Add a double and an int using the Gen class
           Gen gb = new Gen(10.125, 2005);
           Console.WriteLine(gb.t + gb.u);
        
        
        Si lo ejecutas la clase con Gen es un poco mas rapido ya que no requiere Box/Unbox de la clase Object.
        - Los errores surgen en compilación y no en run-time. Es mas fácil determinar errores de compilación que errores de ejecución:
           // C#
           // Add a double and an int using the Gen class
           Gen gc = new Gen(10.125, 2005);
           Console.WriteLine(gc.t + gc.u);
           // Add a double and an int using the Obj class
           Obj oc = new Obj(10.125, 2005);
           Console.WriteLine((int)oc.t + (int)oc.u);   // aqui hay error en (int)oc.t ya que es double, al compilar no se da cuenta
        

        Como usar constraint en Generic
        -Interface: permite solo tipos que implementen interfaces especificas para usar en tu generic.
        -Class: permite solo tipos que implementen clases especificas para usar en tu generic.
        -Constructor: permite decir que requiere tipos que usen genéricos para implementar un constructor con parámetros.
        -Tipo por valor o referencia: Requiere que los tipos que usan tu generic sean de tipo valor o referencia.
        - Se puede usar la clausula where para aplicar la constraint. Por ejemplo la constraint para X interfaz:
           // C#
           class CompGen
           where T : IComparable
           {
            public T t1;
            public T t2;
            public CompGen(T _t1, T _t2)
            {
             t1 = _t1;
             t2 = _t2;
            }
            public T Max()
            {
             if (t2.CompareTo(t1) < 0)
              return t1;
             else
              return t2;
            } 
           }
        
        - Si estas creando un clase genérica y necesitas hacer dispose de los objetos genéricos: Usar una Constraint que requiera un tipo Genérico que implemente la interfaz IDisposable. Y no implementar la interfaz IDisposable directamente.

        Eventos
        - La mayoría de las aplicaciones no son lineales, tienen eventos.
        - Un evento es un mensaje que envía un objeto en señal de una acción, que puede ser un clic del mouse, o gatillado por algún otro programa.
        - El objeto que gatilla el evento se llama event sender y el que captura el evento event receiver y lo maneja (lo que se conoce como handle).
        - El objeto que envía no sabe que objeto recibe (handle) el evento por lo que el F provee un guía o puntero entre quien envía y quien recibe, se llama Delegado.
        - Un Delegado es una clase que tiene el control de la referencia. A diferencia de otras clases, una clase delegado tiene una firma o "signature" y esta puede referenciar solo a métodos que coinciden con dicha firma.
        - Una firma o signature en C# es el nombre del método + el tipo del método + Valor/Referencia/Resultado de Parámetros. Ejemplo:
           interface ITest
           {
              void F();                     // F()
              void F(int x);                // F(int)
              void F(ref int x);            // F(ref int)
              void F(int x, int y);         // F(int, int)
              int F(string s);              // F(string)
           }
        
        - Un delegado es el equivalente a una función type-safe o a un callback.
        - Una declaración del delegado es suficiente para definir un clase delegada, esta declaración reemplaza la firma del delegado y el Common Language Runtime provee la implementación.
        - Ejemplo de una declaración de delegado:
           // C#
           public delegate void AlarmEventHandler(object sender, EventArgs e);
        
        - La firma estándar de un delegado que manejada eventos es definir un método que no retorne valor dado, entonces se usa "void" y donde el primer parámetro es la instancia que gatilla el evento, como no sabemos es tipo "Object", y el segundo es de tipo "EventArgs" que mantiene los datos del evento. Si el evento gatillado no tiene datos de evento, el segundo parámetro solo seria una instancia de EventArgs.
        - El delegado EventHandler es predifinido para los casos que se quiere un manejador de eventos que no genera datos de evento.
        - Para asociar al evento con el método que gatillo el evento, agrega una instancia del delegado en el evento. El event handler sera llamado si el evento ocurre a menos que remuevas el delegado.
        - Como responder a un evento: Se debe hacer 2 cosas: A. Crear un método que responda al evento. Este método debe coincidir con al firma del Delegado. Típicamente retorna un "void" y acepta 2 parámetros un "Objeto" y un "EventAgrs" o (clase derivada)
           // C#
           private void button1_Click(object sender, EventArgs e)
           {
             // Method code
           }
        
        B. Agregar el manejador de eventos (en este caso usamos button1_Click) que indica que método debería recibir el evento (apunta al método anteriormente creado):
           // C#
           this.button1.Click += new System.EventHandler(this.button1_Click);
        
        - NOTA: el .NET Framework 2.0 incluye una nueva version generica del tipo "EventHandler".
        - Cuando el evento ocurre, el método especificado se ejecutara.

        Como lanzar un evento
        - Crear un delegado:
           public delegate void MyEventHandler(object sender, EventArgs e);
        - Crear un miembro de un Evento de tipo que coincida con el Delegado creado:
           public event MyEventHandler MyEvent;
        - Invocar el delegado dentro del método cuando lanzas el evento, con VB es muy simple, C# no tanto:
           'VB
            Dim e As EventArgs = New EventArgs
            RaiseEvent MyEvent(Me, e)
            // C#
            MyEventHandler handler = MyEvent;
            EventArgs e = new EventArgs();
            if (handler != null)
            {
             // Invokes the delegates.
             handler(this, e);
            }
            // Note that C# checks to determine whether handler is null.
            // This is not necessary in Visual Basic
        

        Atributos
        - describen un tipo, método, propiedad/miembro con la idea de programar la técnica de Reflection.
        - algunos usos comunes de los atributos son:
        A. especificar privilegios de seguridad que requiere una clase
        B. especificar privilegios para reducir los riesgos de seguridad
        C. declarar capacidades, como soportar serialización -> transformar una clase a ciertos bytes con un formato determinado, por ejemplo transformar a XML. Solo se serializa métodos puplicos.
        D. describe un assembly dándole un titulo, descripción y copyright.
        - Los atributos derivan de Systenm.Attribute.
        - Los atributos se especifican <> en VB o con [] con C#.
        Ejemplo:
           // C# - AssemblyInfo.cs
           [assembly: AssemblyTitle("ch01cs")]
           [assembly: AssemblyDescription("Chapter 1 Samples")]
           [assembly: AssemblyConfiguration("")]
        
        - Visual Studio automáticamente genera atributos como el titulo, descripción, compañía y versión. Deberías editar esos valores en los proyectos que generes.
        - Otro atributo es definir si la clase es serializable, con el atributo Serializable:
           ' VB
            Class ShoppingCartItem
           End Class
           // C#
           [Serializable]
           class ShoppingCartItem
           {
           }
        
        - Sin el atributo Serializable, la clase no se puede serializar.
        - El siguiente código indica que la clase requiere leer el archivo C:\boot.ini, ya con esto, si hay un error (de privilegio u otro), el runtime puede lanzar una excepción ANTES de ejecutar el bloque de código.
        - Ejemplo:
           // C#
           using System;
           using System.Security.Permissions;
           [assembly:FileIOPermissionAttribute(SecurityAction.RequestMinimum, Read=@"C:\boot.ini")]
           namespace DeclarativeExample
           {
            class Class1
            {
             [STAThread]
             static void Main(string[] args)
             {
              Console.WriteLine("Hello, World!");
             }
            }
           }
        

        ¿Qué es el Type Forwarding (Tipo forwardng)?
        - Es nuevo de F 2.0.
        - Es un atributo (implementado en TypeForwardedTo) que te permite mover el tipo de un assembly (Assembly A) a otro (Assembly B), sin necesidad de recompilar los clientes que consumen el assembly A.
        - Luego que un componente (assembly) viaja y es usado por una aplicación, puedes usar Type Forwarding para mover un tipo de una componente a otro assembly y enviar la componente actualizada y las clientes no necesitan ser recompiladas.
        - Type Forwarding: Es utilizado para cuando por ejemplo, tienes una clase en una dll, pero con el paso del tiempo re estructuras tu aplicación y resulta que esa clase ya no queda en esa dll sino en otra. ¿entonces tienes que ir en cada uno de tus clientes a reinstalar la aplicación? No, entonces puedes utilizar TypeForwarding para indicarle al CLR que busque esa clase en otra dll diferente de la dll original.
        - Para mover el tipo de una class library a otro:
        A. Agrega el atributo TypeForwardedTo a la clase de origen.
        B. Corta de la definición del tipo de la source class library.
        C. Pega la definición del tipo en class library destino.
        D. Recompila ambas librerías.
        - Ejemplo: El siguiente código muestra el atributo que es usado para mover TypeA de la class library a un DestLib:
           // C#
           using System.Runtime.CompilerServices;
           [assembly:TypeForwardedTo(typeof(DestLib.TypeA))]
        

        Lección 4: Conversion entre tipos

        Conversiones en VB y C#

        - Por defecto, VB permite conversión implícita, C# no ya que pierde precisión.
        - Para desactivar la conversión implícita en VB agrega "Option Strict On" en casa archivo o en las propiedades del proyecto en VS.
        - Tanto VB y C# permiten conversión implícita si el tipo destino coincide o se acomoda con el tipo de origen. A esto se le conoce como "widening conversion", ej:
           // C#
           int i = 1;
           double d = 1.0001;
           d = i; // Conversion allowed.
        
        - Si el rango del origen excede en precision al tipo destino, esta operación es llamada "narrowing conversion", requiere una conversión explicita.
        - Métodos de conversiones Explicitas:
           
           Tipo System          VB        C#         Convert
           System.Convert                            entre tipos que implmenten IConvertible
                                Ctype    (type)cast 
           type.ToString()
           type.Parse 
           type.TryParse,                            -> nuevo en F 2.0
           type.TryparseExact                        -> nuevo en F 2.0
                               CBool, Cint, CStr, etc  
                               DirectCast
                               TryCast               -> nuevo en F 2.0
        
        - Las conversiones narrowing fallan si el rango del tipo de origen excede el rango del tipo de destino, por lo que se recomienda usar bloques try, o usar TryParse/TryCast para validar la salida.

        Boxing y UnBoxing
        - Hay que tratar de evitarlos si tiene un código que se repite mucho y usas Boxing/UnBoxing.
        Boxing: Convertir un tipo por valor a uno por referencia.
        - Se debe evitar para no tener overhead o "costo de tiempo extra".
        - Boxing se gatilla igualmente mucho llamas métodos virtuales cuya estructura herede de "System.Object", como "ToString".
           // C#
           int i = 123;
           object o = (object) i;
        
        UnBoxing: Convertir un tipo por referencia a uno por valor.
           // C#
           object o = 123;
           int i = (int) o;
        
        - Para evitar este uso:
          -implementar versiones especificas de procedimientos que acepten ese especifico tipo de dato. La mejor opción es crear métodos sobrecargados que uno que acepte el parámetro OBJECT.
          -usar generics en lo posible en vez de usar parámetros Objects.
          -sobreescribe los miembros virtuales "ToString", Equals y GetHash cuando se definan las estructuras.

        Como implementar la Conversión en Tipos Custom.
        A. Definir operadores de conversión para simplificar las conversiones narrowing y widering.
          - Te permite asignar directamente asignar desde un tipo por valor a tu tipo custom.
          - Usa la clausula Widening/implicit úsala para conversiones que no pierdan precisión.
          - Usa la clausula Narrowing/explicit úsala para conversiones que pueden perder precisión.
        B. Sobreescribir ToString() para proveer conversiones PARA string y sobreecribe Parse para proveer conversión DESDE string.
        C. Implementar System.IConvertible para permitir la conversión a través de System.Convert. Usa esta técnica para permitir conversiones de la cultura.
        D. Implementa una clase TypeConverter para permitir una conversión en tiempo de diseño para ser utilizada en la ventana de Propiedades de VS. Conversiones en tiempo de diseño esta fuera del examen.
        Operadores de Conversión
        - Es nuevo en F 2.0.
        - Por ejemplo asignacion DESDE Enteros y HASTA Enteros usando implicit/explicit, sobreescribiendo ToString y evitando hacer boxing:
         // C#
         struct TypeA
         {
          public int Value;
          // Allows implicit conversion from an integer.
          public static implicit operator TypeA(int arg)
          {
           TypeA res = new TypeA();
           res.Value = arg;
           return res;
          }
          // Allows explicit conversion to an integer
          public static explicit operator int(TypeA arg)
          {
           return arg.Value;
          }
          // Provides string conversion (sin boxing).
          public override string ToString()   // -> To sobreescribimos String, evitamos Boxing
          {
           return this.Value.ToString();   // retornamos algo diferente al nombre del tipo (type name).
          }
         }
         //Ahora podemos asignar Enteros directamente:
         // C#
         TypeA a; int i;
         // Widening conversion is OK implicit.
         a = 42; // Rather than a.Value = 42
         // Narrowing conversion must be explicit.
         i = (int)a; // Rather than i = a.Value
         Console.WriteLine("a = {0}, i = {0}", a.ToString(), i.ToString());
        
        - Para implementar la interface "System.IConvertible":
        - Agrega la interfaz "IConvertible" a la definicion del tipo.
        - Con VS hazlo automáticamente y VS insertara 17 declaraciones de miembros, incluyendo "GetTypeCode", "ChangeType" y "ToType" para cada tipo base.
        - No es necesario implementar todos los métodos como "ToDateTime".
        - Para métodos inválidos solo lanza una excepcion "InvalidCastException"
        - Luego de implementar la Interfaz "IConvertible", el tipo custom puede ser convertido usando el estandar "System.Convert":
           // C#
           TypeA a; bool b;
           a = 42;
           // Convert using ToBoolean.
           b = Convert.ToBoolean(a);
           Console.WriteLine("a = {0}, b = {1}", a.ToString(), b.ToString());
        

        Términos importantes del capítulo


        - boxing: Convertir un tipo por valor a uno por referencia. "overhead" o "costo de tiempo extra".
        cast
        constraint
        contract
        exception
        filtering exceptions
        garbage collection
        generic type
        heap
        interface
        narrowing: Conversión con perdida de precisión. Por ejemplo de Int32 a Int16.
        nullable type
        signature: Es una "firma" que consiste en nombre del método + el tipo del método + Valor/Referencia/Resultado de Parámetros. Ejemplo void F(int x); int X(ref int z)
        stack
        structure
        unboxing: Convertir un tipo por referencia a uno por valor.
        widening: Conversión sin perdida de precisión. Por ej de Int16 a Int32.

        Fin Traducción capítulo 1 del libro MCTS Self-Paced Training Kit (Exam 70-536): Microsoft® .NET Framework Application Development Foundation, Second edition