Javier Valero

Maximum number of items that can be serialized or deserialized in an object graph is 65536. Change the object graph or increase the MaxItemsInObjectGraph quota.

 

Cuando nos aparece este error la solución es sencilla y pasa por ampliar el número de objetos que el Serializer utilizado en WCF pueda serializar.

Esta configuración se debe realizar en el apartado behavior.

El problema surge cuando el cliente que tenemos es una aplicación silverlight. ¿Por qué? pues porque en un .config de silverlight no podemos incluir el behavior.

La solución pese a poder parecer compleja, puesto que ambos extremos, cliente y servidor deben ser idénticos, es realmente sencilla.

Basta con configurar el behavior solo en el servidor y el cliente silverlight funcionará perfectamente.

Así, con añadir un behavior  en el web config del lado del servidor como el sliguiente

Code Snippet
  1. <behavior name="CustomEndPointBehavior">
  2.   <dataContractSerializer maxItemsInObjectGraph="2147483647" />
  3. </behavior>

           Y referenciarlo en el el endpoint del servicio en el web.config del lado del servidor

Code Snippet
  1. <endpoint
  2.   address=""
  3.   binding="basicHttpBinding"
  4.   bindingConfiguration="BindignConf"
  5.   name="Binding_name"
  6.   contract="IContract"
  7.   listenUriMode="Explicit"
  8.   behaviorConfiguration="CustomEndPointBehavior"/>

Tendremos solucionado el problema

The mapping and metadata information for EntityContainer 'x' no longer matches the information used to create the pre-generated views

 

Este error se produce cuanto trabajamos con pre-generated views en Entity Framework.

Este Post describe una forma de solucionar este error.

En el caso en particular que describo este es el escenario:

Base de Datos: Oracle, con distintos esquemas en función de cada entorno.

Proveedor de Datos: Devart

ORM: Entity Framework 4

Generación de pre-generated views: Mediante la plantilla T4

Entorno de compilación: Visual Studio Team Foundation Server 2010 Service Pack 1

Arquitectura: Proyecto basado en DDD utilizando la arquitectura n-layered de Codeplex.

El error se produce al desplegar una versión tras realizar una build en el TFS apuntando a un nuevo esquema de base de datos, cambio que realiza la propia build  con una custom activity sobre el fichero .edmx antes de compilar.

Pregenerated Views

Las pregenerated-views aportan una gran reducción de tiempo en proyectos que utilicen  Entity Framework.

La referencia a estas mejoras se pueden encontrar aquí:

http://msdn.microsoft.com/en-us/library/bb896240%28v=vs.90%29.aspx

El Problema

El error se produce por no ejecutar la plantilla T4 para regenerar las vistas después de haber modificado el fichero .edmx. Cuando se ejecuta la plantilla .tt mediante Run Custom Tool se genera un fichero .cs que incluye unas hash que Entity Framework utiliza para ver si ha variado la información de las vistas.

Esta hash se encuentra en el fichero .cs generado por la plantilla .tt

  1. namespace Edm_EntityMappingGeneratedViews
  2. {
  3.     
  4.     
  5.     /// <Summary>
  6.     /// The type contains views for EntitySets and AssociationSets that were generated at design time.
  7.     /// </Summary>
  8.     [System.Diagnostics.DebuggerNonUserCode()][System.CodeDom.Compiler.GeneratedCode("EF","v 4.0")]public sealed class ViewsForBaseEntitySetsD8764C9409F9061FB3ACFF9A2D927400C418BCF91616A573FD109E312323984A : System.Data.Mapping.EntityViewContainer
  9.     {
  10.         
  11.         /// <Summary>
  12.         /// The constructor stores the views for the extents and also the hash values generated based on the metadata and mapping closure and views.
  13.         /// </Summary>
  14.         public ViewsForBaseEntitySetsD8764C9409F9061FB3ACFF9A2D927400C418BCF91616A573FD109E312323984A()
  15.         {
  16.             this.EdmEntityContainerName = "EntitiesMant2";
  17.             this.StoreEntityContainerName = "MantStoreContainer";
  18.             this.HashOverMappingClosure = "b259ca6b447b5503d17e29d1089b9731f467487917f28b6e80b3ef67a296e833";
  19.             this.HashOverAllExtentViews = "576048e78b6e27609be586140ddb9e3dc2abdc665cf47ddc76534fc80c15a8b9";
  20.             this.ViewCount = 28;
  21.         }

 

El hash se genera en base al contenido del fichero .edmx con lo que cualquier cambio que hagamos en él dejara el hash invalidado y por lo tanto tendremos problemas.

Siguiendo la información reportada en el blog de Oleg Sych:

http://www.olegsych.com/2010/04/understanding-t4-msbuild-integration/#comment-3483

Y su referencia en MSDN

http://msdn.microsoft.com/en-us/library/ee847423.aspx

Encontré la solución que describo a continuación.

Resolución del Error

Para resolver el error es necesario:

Que todos los desarrolladores y en el servidor en el que hacemos la build, tengamos instalado:

Visual Studio 2010 SDK

Visual Studio 2010 Visualization & Modeling SDK

 

Una vez descargados modificaremos el fichero de proyecto (.csproj) que contiene el .edmx y la plantilla .tt, haciendo un unload y editándolo, agregando las siguientes entradas:

 

Fichero .csproj
  1. <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TextTemplating\v10.0\Microsoft.TextTemplating.targets" />
  2.   <PropertyGroup>
  3.     <TransformOnBuild>false</TransformOnBuild>
  4.     <TransformOutOfDateOnly>false</TransformOutOfDateOnly>
  5.     <OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles>
  6.     <!-- Other properties can be inserted here -->
  7.   </PropertyGroup>
  8.   <PropertyGroup>
  9.     <PreBuildEvent>"%25windir%25\Microsoft.NET\Framework\v4.0.30319\msbuild.exe" $(ProjectDir)Infrastructure.Data.proyecto.csproj /t:Transform /p:TransformFile=Model\DPTCampaignMant.proyecto.tt</PreBuildEvent>
  10.   </PropertyGroup>

Con estas entradas y el prebuildEvent conseguiremos que se ejecute la plantilla .tt volviendo a generar las vistas y cambiando los hash justo antes de que se ejecute la compilación.

WCF Guía Completa de Programación. CAP. II. Mi Primer servicio WCF

 

Introducción

Este en cualquier libro suele ser el típico ejemplo de “hola mundo”, sólo que en el caso de WCF creo que es bastante más divertido.

En el mejor de los casos para hacer una simple llamada vamos a tener que escribir un montón de código, esto que en un principio parece engorroso, a la larga cuando estemos construyendo aplicaciones complejas será una forma de poner orden a todo el mecanismo de desarrollo de aplicaciones distribuidas.

Componentes de una Aplicación WCF

Los que hayan trabajado con tecnologías antiguas basadas en COM o DCOM recordarán el duro camino de los CLSID para mantener las referencias entre clientes, servidores, servidores con servidores, etc.

la verdadera potencia de WCF es haber acumulado no sólo todas las tecnologías de aplicaciones distribuidas como Remoting, MSMQ, … Sino el haber utilizado el potencial de todo lo que se ha evolucionado en las arquitecturas internet B2B, SOAP…

Para estructurar una aplicación que exponga servicios y otra que los consuma, como veremos adelante es todo un mundo. De echo me atrevería a decir que el 70% de WCF es configuración o similar, mientras que el resto esta relacionado con el propio código.

En este capítulo no vamos a ver un ejemplo que sea el modelo a seguir para realizar una aplicación cliente o servidor WCF, pero si va a sentar las bases para entender todo lo que viene a continuación.

Por eso esta aplicación la escribiremos haciendo el menor número de líneas de código posibles y pasando por alto temas como poder extraer la configuración del código para llevarla a ficheros, ya que esto se verá más adelante.

WCF está totalmente relacionado con SOA, es más si no voy a a hacer una aplicación que necesite SOA debo plantearme si WCF es la solución. Estoy harto de ver proyectos donde la gente empieza a meter tecnologías a saco sin pensar si es lo correcto, sólo por estar a la última.

Debemos tener en cuenta que los principios de SOA son permitir el desarrollo de nuevos requerimientos de negocio sin tener que reconstruir toda la aplicación entera, así como poder tomar nuestros bloques de aplicación para construir bloques de procesos de negocio. Es decir que si alguien está pensando en utilizar WCF para cacharrear tecnológicamente lo debe pensar bien, puesto que el coste de horas de desarrollo es elevado y debe tener una contrapartida en el negocio que al final es quien contrata los sistemas.

Por otro lado el utilizar WCF en cierto modo nos abstraerá de tener que poseer determinados conocimientos como WSDL, etc. ya que WCF lo hará por nosotros.

Para definir WCF, hay que indicar que todo servicio está compuesto por al menos dos partes, el cliente y el servidor. El servidor expone los servicios y el cliente los consume, todo ello mediante el intercambio de mensajes.

Para que un cliente y un servidor se comuniquen es necesario establecer lo que se ha dado en llamar el ABC de WCF, que no es ni más ni menos que Address, Binding y Contract. O lo que es lo mismo.

  • Address: La dirección del servicio
  • Binding: El mecanismo de comunicación
  • Contract: Los servicios que se exponen.

Estas tres “cosas” juntas se denomina Endpoint que son los puntos de conexión entre cliente y servidor. Es decir un cliente envía y recibe la información a través de sus EndPoints, así como un servidor escucha y responde a través de sus EndPoints.

Unos ejemplos de estos tres elementos serían:

Address: http://servicios.acme.com:8001/serviciosuma

Binding:  BasicHTTPBinding

Contract: [OperationContract] int suma(int x,int y);

 

Construcción de un Sistema WCF Básico

Cómo he comentado anteriormente no se trata de hacer un servicio complejo con múltiples opciones de configuración, ficheros de configuración, distintos tipos de endpoints, etc. En este primer ejemplo se trata de probar unas mínimas piezas, ejecutar y decir “FUNCIONA!!” de tal forma que nos motive a seguir aprendiendo.

Otro tema a tener en cuenta es que los ejemplos que voy a poner en los distintos capítulos van a ser muy sencillos de entender, ya que lo importante no es averiguar que hace mi super ejemplo a nivel de negocio, sino entender la tecnología que se utiliza.

El Servidor

Pues para empezar lo primero que haremos será crear un Contrato, es decir construir algo que indique qué voy a exponer en mi servidor WCF.

Para ello crearemos un nuevo proyecto, lo haremos desde cero sin utilizar ninguna plantilla. Es el método que más me gusta, primero aprendemos por las bravas y luego wizards o similar, sino no hay quién entienda lo que se genera y como me enseño mi gran amigo Rido, cuando un wizard te genera algo que no entiendes, lo mejor es “quitarlo”. Qué Razón tienes Rido!!!

Un contrato de debe definir como una interface y en el proyecto debemos tener agregada la referencia a System.ServiceModel, que es el namespace que provee los objetos y atributos necesarios para construir servicios WCF.

image

Una vez agregada la referencia, podremos incluir la cláusula using Sytstem.ServiceModel en nuestro Proyecto.

En este primer proyecto, vamos a incluir una función muy sencilla en la que enviaremos  un par de enteros y la función nos devolverá la suma, resta, multiplicación o división, en función de lo que indiquemos. Es decir la típica calculadora.

Para ello definiremos primero el interface con los servicios que vamos a exponer.

[sourcecode language='csharp' ]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace IServicio
{
    [ServiceContract]
    public interface ICalculadora
    {
        [OperationContract]
        int sumar(int x, int y);

        [OperationContract]
        int restar(int x, int y);

        [OperationContract]
        int multiplicar(int x, int y);

        [OperationContract]
        decimal dividir(int x, int y);
    }
}
[/sourcecode]

En este código del interfaz, hemos de fijarnos en dos cosas.

El interfaz lleva el modificador [ServiceContract] que indica un nuevo grupo de operaciones o Contrato que estamos estableciendo.

También observamos como cada método lleva un modificador [OperationContract] que indica una Operación del Contrato que estamos estableciendo.

El Servicio

Una vez que hemos establecido nuestro Contrato hemos de implementar el servicio que desarrollará nuestro interfaz.

Lo haremos en un proyecto diferente que agregaremos a nuestra solución en el que implementaremos los cuatro OperationContract.

Este proyecto no presenta ninguna perculiaridad, salvo que agregamos como referencias el proyecto del interfaz y el espacio de nombres ServiceModel.

No se incluyen validaciones o similar por claridad del código, además de que el tratamiento de excepciones se verá más adelante.

El código completo del Servicio sería el siguiente:

[sourcecode language='csharp' ]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
    
namespace Contratos
{
    public class Calculadora : ICalculadora
    {
      public  int sumar(int x, int y)
        {
            return x + y;
        }

      public  int restar(int x, int y)
        {
            return x - y;
        }


      public  int multiplicar(int x, int y)
        {
            return x * y;
        }


     public    decimal dividir(int x, int y)
        {
            return x / y;
        }
    }
}
[/sourcecode]

El Host

Para poder utilizar un servicio desarrollado mediante WCF será necesario alojarlo en un componente, existen varias posibilidades, tales como una aplicación de consola, un servidor IIS,…

Para nuestro primer ejemplo utilizaremos la aplicación consola y crearemos toda la configuración por código.

Para ello a nuestra solución agregaremos un nuevo proyecto de Consola.

Para poder  hostear el servicio WCF, hemos de indicar los siguientes elementos en nuestro código.

  1. ADDRESS. Definiremos la URL en la que expondremos el servicio y le asignaremos un nombre. En nuestro caso Calculadora
[sourcecode language='csharp' ]
Uri miurl = new Uri("http://localhost:8080/Calculadora");
[/sourcecode]
  1. BINDING. Especificaremos el protocolo de comunicación a nuestro servicio
[sourcecode language='csharp' ]
BasicHttpBinding elbinding = new BasicHttpBinding();
[/sourcecode]
  1. CONTRACT. Especificaremos el contrato de operaciones expuesto por nuestro servicio
[sourcecode language='csharp' ]
ServiceHost elhost = new ServiceHost(typeof(Calculadora), miurl);
[/sourcecode]

Después de estos tres pasos, del ya comentado ABC de WCF abriremos el Host mediante una instrucción Open e implementaremos un ReadLine sobre la consola para que no se nos cierre y se quede escuchando por la URL establecida.

 

[sourcecode language='csharp' ]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using Contratos;



namespace HostServicio
{
    class Program
    {
        static void Main(string[] args)
        {
            Uri miurl = new Uri("http://localhost:8080/Calculadora");
            ServiceHost elhost = new ServiceHost(typeof(Calculadora), miurl);

            BasicHttpBinding elbinding = new BasicHttpBinding();
            elhost.AddServiceEndpoint(typeof(ICalculadora), elbinding, "");

            elhost.Open();

            try
            {
                Console.WriteLine("Servicio alojado en " + miurl.AbsoluteUri);
                Console.ReadLine();
            }
            finally
            {
                elhost.Close();
            }
        }
    }
}
[/sourcecode]

Tras haber creado el Host, el siguiente paso es arrancarlo, para ello haremos un Build de la solución y arrancaremos la aplicación de consola desde la carpeta Bin\Debug ejecutando como Administrador si estamos trabajando con Windows 7, Vista,….

image

También podemos abrir la consola “como administradores” y navegar hasta la carpeta para ejecutar el host a mano

image

En cualquiera de los casos nuestra aplicación quedará levantada indicandonos el mensaje que hemos introducido.

image

         Para verificar que nuestro servicio está expuesto desde el internet explorer introduciremos la URL que indicamos en el apartado Address del código de la aplicación HOST.

image

En esta imagen podemos observar quela aplicación servidor ha desplegado nuestro servicio pero que el Metadata esta deshabilitado actualmente, es decir, que las operaciones no están expuestas vía WSDL, esto se puede desactivar por configuración, pero en nuestro caso de momento lo vamos a hacer por código mediante la inclusión de las siguientes líneas:

[sourcecode language='csharp' ]
 ServiceMetadataBehavior metadatos = new ServiceMetadataBehavior();
            metadatos.HttpGetEnabled = true;
            elhost.Description.Behaviors.Add(metadatos); 
[/sourcecode]

Con las que indicamos que queremos mostrar el Metadata, es decir mostramos la información del interface Contrato mediante reflexión de los servicios que hayamos expuesto mediante el modificador [OperationContract]

Una vez incluidas estas líneas, si recompilamos y volvemos a introducir la url del servicio en el navegador, nos aparecerá una ventana que nos indica que mediante la herramienta svcutil podemos generar varios ficheros tales como el PROXY, necesario para nuestro cliente.

image

El código completo por lo tanto del Host sería el siguiente:

[sourcecode language='csharp' ]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using Contratos;
using System.ServiceModel.Description;



namespace HostServicio
{
    class Program
    {
        static void Main(string[] args)
        {
            Uri miurl = new Uri("http://localhost:8080/Calculadora");
            ServiceHost elhost = new ServiceHost(typeof(Calculadora), miurl);

            BasicHttpBinding elbinding = new BasicHttpBinding();
            elhost.AddServiceEndpoint(typeof(ICalculadora), elbinding, "");

            ServiceMetadataBehavior metadatos = new ServiceMetadataBehavior();
            metadatos.HttpGetEnabled = true;
            elhost.Description.Behaviors.Add(metadatos); 

            elhost.Open();

            try
            {
                Console.WriteLine("Servicio alojado en " + miurl.AbsoluteUri);
                Console.ReadLine();
            }
            finally
            {
                elhost.Close();
            }
        }
    }
}
[/sourcecode]

El Cliente

El cliente que vamos a utilizar será una aplicación Winforms, muy sencilla en la incluiremos dos cajas de texto para los número x e y que está esperando nuestro servicio, además de unos botones para realizar las operaciones.

Para poder utilizar el cliente necesitamos el proxy, el cual generaremos mediante la utilidad svcutil como hemos indicado anteriormente. De momento añadiremos la clase proxy y más adelante en otros capítulos explicaremos en detalle el por qué del proxy.

Para ello arrancamos el símbolo del sistema que viene con el propio Visual Studio y ejecutamos el comando atacando a la URL en la que tenemos arrancado nuestro servicio mediante la consola.

image

Por su puesto la llamada la haremos desde la carpeta del cliente, así se nos generará en esta carpeta el proxy .cs que agregaremos al proyecto posteriormente.

image

image

Podemos ver que se ha generado el .cs con el proxy el cual incorporamos a nuestro proyecto cliente.

image

El proyecto Cliente es muy sencillo, se codificará sólo el método sumar y se dejará el resto en el código fuente para que quién se lo descargue pueda finalizar el ejemplo.

Para poder utilizar el servidor es necesario agregar las referencias a ServiceModel y utilizar la clase que nos ha generado la herramienta svcutil.

En el código del cliente cabe destacar:

La creación del Endpoint de Cliente

[sourcecode language='csharp' ]
 EndpointAddress ladireccion = 
                new EndpointAddress("http://localhost:8080/Calculadora");
            BasicHttpBinding elbinding = new BasicHttpBinding();
[/sourcecode]

La utilización del proxy

[sourcecode language='csharp' ]
 CalculadoraClient lacalculadora = 
                new CalculadoraClient(elbinding, ladireccion);
[/sourcecode]

La llamada al método sumar

[sourcecode language='csharp' ]
  MessageBox.Show("Resultado " +
                lacalculadora.sumar(int.Parse(txtX.Text), 
                int.Parse(txtY.Text)).ToString());
[/sourcecode]

El código completo del cliente sería:

[sourcecode language='csharp' ]
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ServiceModel;

namespace Cliente
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void cmd_add_Click(object sender, EventArgs e)
        {
            EndpointAddress ladireccion = 
                new EndpointAddress("http://localhost:8080/Calculadora");
            BasicHttpBinding elbinding = new BasicHttpBinding();

            CalculadoraClient lacalculadora = 
                new CalculadoraClient(elbinding, ladireccion);

            MessageBox.Show("Resultado " +
                lacalculadora.sumar(int.Parse(txtX.Text), 
                int.Parse(txtY.Text)).ToString());

            
        }
    }
}
[/sourcecode]

Como podemos ver si ejecutamos nuestro servidor y nuestra consola, la llamada FUNCIONA!!!

 

image

Este es el primer paso, a partir de ahora en los siguientes capítulos se irá avanzando en configuración, contratos, seguridad, etc.

 

Código Completo.

El código completo de este primer capítulo lo podéis descargar desde aquí:

Capitulo1.zip

Posted: 16/11/2010 2:09 por Javier Valero González | con 12 comment(s) |
Archivado en:
WCF Guía Completa de Programación. CAP. I. Introducción

 

Prólogo

Después de un tiempo trabajando con WCF me estaba empezando a plantear escribir un libro, un libro útil desde el punto de vista práctico que da el trabajar en proyectos.

Como tras evaluarlo detenidamente, supuse que esto me iba a suponer bastante tiempo del que no dispongo (ya sabemos todos lo que es este trabajo de la informática…) pues he decidido en reducirlo al máximo y publicarlo en mis blogs para que además esté al alcance de todos. Hasta a mi alcance, que a veces uno no se acuerda de cosas y tiene que echar mano “del internet”.

El principal problema cuando coges un libro es que depende mucho de quién lo haya escrito, para que podamos decir que este libro que habla de tal tecnología es un buen libro, no sólo tiene que tratar los temas a fondo, sino que tienen que ser prácticos.  Esto a lamentablemente no pasa a menudo en los libros técnicos, aunque últimamente  y en concreto los libros de Microsoft han mejorado bastante.

En resumen lo que voy a intentar en sucesivos capítulos es tratar una de las tecnologías que más me han atraído en los últimos tiempos, quizá por la añoranza de aquellos años en que los que comenzamos a trabajar con DCOM nos sentíamos como aventureros, en un mundo como poca información en el que el prueba error era una constante.

Introducción.

Una vez situados en antecedentes, creo que lo mejor es ver lo que sucesivamente voy a ir publicando, espero que con cierta constancia, en sucesivos Post, los cuales harán el libro, mini-libro, o guía como le queráis llamar de desarrollo con WCF.

Intentaré poner ejemplos completos para lo que utilizaremos una base de datos que publicaré como enlace descargable, cuando lleguemos al punto en el que accedamos a datos.

Si estuviera escribiendo un libro de verdad, pues habría capítulos dedicados a una introducción real de que es WCF y demás paja, que creo que lo mejor es que nos los saltemos y pasemos directamente a la “chicha”. Mi intención es publicar 1 o 2 capítulos a la semana, los cuales estaré encantado de completar con vuestros comentarios y sugerencias.

Con esto dicho, los distintos temas que trataré serán:

  1. Mi Primer servicio WCF
  2. Contratos
  3. Nociones Básicas de Configuración WCF.
  4. Hosting de Servicios WCF
  5. Configuración Avanzada
  6. Instrumentación en WCF
  7. Seguridad
  8. Transacciones
  9. El mundo Real

En los próximos días subiré el primer post. Espero que os guste la idea y el que quiera aportar con algún otro tema que no vaya a contemplar que me lo comente.

Herramienta de Desinstalación de Visual Studio 2008

  Bueno, me imagino que en alguna ocasión habréis tenido que desinstalar Visual Studio 2008, lo cual supone una tarea nada fácil, por no decir que algunas veces se hace imposible.

   En unas de estas ocasiones un compañero me recomendó la herramienta que pongo aquí para los que la necesitéis.

  Eso sí en hacer la limpieza tarda un buen rato…

  Pulsa en el enlace para descargártela   UninstallTool.zip

Foro de Arquitectos Microsoft

El otro día tuve el gusto de asistir al foro de arquitectos de Microsoft, en el que mi viejo amigo Cesar de la Torre (http://blogs.msdn.com/cesardelatorre/), junto con Unai Zorrilla presentaron una iniciativa de la construcción de una arquitectura base (n-capas) para el desarrollo de proyectos grandes sin acoplamiento.

Ya era hora que desde Microsoft alguien nos diera un punto de partida para no reinventar la rueda en cada proyecto.

Enhorabuena por ellos!!

La información general la podéis encontrar aquí: http://msdn.microsoft.com/es-es/architecture/default.aspx

Y la solución para descargar, en codeplex:

http://microsoftnlayerapp.codeplex.com/

Esperemos que ambos continuen con la iniciativa e incorporen las nuevas tecnologías que puedan aparecer.

Evitar el error sql parameter contained by another SqlParameterCollection / Otro SqlParameterCollection ya contiene SqlParameter.

Este error se suele producir cuando trabajamos por ejemplo con Stored Procedures con parámetros.

 

El error nos aparece así en el Visual Studio:

Error

Esto sucede porque cuando asignamos un SqlParameter a dos SqlParameterCollection, por ejemplo de dos SqlCommand diferentes, se produce una asociación con esta SqlParameterCollection, impidiendo que podamos asignar el mismo SqlParameter a otra SqlParameterCollection.

 

Este error no lo solucionaremos con la clausula Using, sino con un Clear() de la SqlparameterCollection, justo después de haberlo utilizado.

Lo voy a ilustrar con un ejemplo que nos permitirá reproducir el error.

Supongamos que tenemos un programa que llama dos veces a un procedimiento almacenado en una clase que tenemos desarrollada y que escribe el resultado en la consola.

El programa Main, que llama dos veces al procedimiento almacenado para que se produzca el error sería el siguiente:

 

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

namespace UsingCommand
{
    class Program
    {
        static void Main(string[] args)
        {
            CoreCommands comandos = new CoreCommands();

            List<SqlParameter> parametros = new List<SqlParameter>();

            parametros.Add(new SqlParameter("EmployeeID","1"));

            comandos.ExecuteDataReader_SP_Reader("GetEmployeebyID", parametros);

            comandos.ExecuteDataReader_SP_Reader("GetEmployeebyID", parametros);

            Console.ReadLine();
        }
    }
}

 

En la segunda llamada de    comandos.ExecuteDataReader_SP_Reader("GetEmployeebyID", parametros);  se produciría el error

 

Ahora os pongo la clase a la que se invoca desde MAIN con un método ExecuteDataReader_SP_Reader que provocaría el error:

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

namespace UsingCommand
{
    class CoreCommands
    {

        public void VisualizarDataReader(SqlDataReader reader)
        {
                while (reader.Read())
            {
                 for (int x=0; x<reader.FieldCount;x++){
                     Console.WriteLine(reader.GetValue(x).ToString());
                }
                }
        }

public void  ExecuteDataReader_SP_Reader(string SPname, List<SqlParameter> parametros)
       {
           using (SqlConnection conexion = new SqlConnection())
           {
               conexion.ConnectionString = "Data Source=.\\MSSQL2008STD;Initial Catalog=AdventureWorks;Integrated Security=SSPI;";
               conexion.Open();

               using (SqlCommand comando = new SqlCommand())
               {
                   comando.Connection = conexion;
                   comando.CommandType = System.Data.CommandType.StoredProcedure;
                   comando.CommandText = SPname;
                   comando.Parameters.Clear();

                   foreach (SqlParameter parametro in parametros)
                   {
                       comando.Parameters.Add(parametro);
                   }

                   SqlDataReader lector = comando.ExecuteReader();
                   VisualizarDataReader(lector);

               }
           }

       }

}

}

Esta clase, con su función DataTable ExecuteTABLE_SP_Reader(string SPname, List<SqlParameter> parametros) provocaría un error, pese a estar tanto el objeto conexion como el command bajo la claúsula “using”.

También hemos puesto el Clear(), pero para que funcione debemos ponerlo después de utilizar el comando.

Para que no se produzca el error, la función ExecuteTABLE_SP_Reader debería hacer el Clear() después de haber utilizado el SqlCommand:

public void  ExecuteDataReader_SP_Reader(string SPname, List<SqlParameter> parametros)
       {
           using (SqlConnection conexion = new SqlConnection())
           {
               conexion.ConnectionString = "Data Source=.\\MSSQL2008STD;Initial Catalog=AdventureWorks;Integrated Security=SSPI;";
               conexion.Open();

               using (SqlCommand comando = new SqlCommand())
               {
                   comando.Connection = conexion;
                   comando.CommandType = System.Data.CommandType.StoredProcedure;
                   comando.CommandText = SPname;

                   foreach (SqlParameter parametro in parametros)
                   {
                       comando.Parameters.Add(parametro);
                   }

                   SqlDataReader lector = comando.ExecuteReader();
                   VisualizarDataReader(lector);
                   comando.Parameters.Clear();
               }
           }

       }

Utilización de Data Providers (DbProviderFactories)

Alguna vez se nos ha planteado el caso de hacer un programa que soporte varias bases de datos. Para ello podemos utilizar los Dataproviders de ADO.NET tal y como explico en este POST. 

 

  La jerarquía de clases provista por .net permite la utilización de proveedores de datos específicos como SqlClient para la conexión a SQL Server.

    Por encima de este provider específico existe toda una jeraquía de interfaces y clases abstractas que permiten a los fabricantes o incluso a nosotros mismos la extensión del modelo ADO.

    Es conveniente la utilización del provider específico de cada fabricante, así podemos utilizar por ejemplo:

                             System.Data.SqlClient

                             System.Data.OleDb

                             System.Data.Odbc

                             System.Data.OracleClient.

            

           Podemos ver por ejemplo el interface IDbConnection y la implementación en SqlConnection de System.Data.SqlClient

IDBConnection

sqlconnection

Esta libertad bien construida en ADO.NET nos permite por ejemplo realizar una aplicación que sea multi base de datos, es decir, que podamos preparar una capa de conexión, que en función de las necesidades por ejemplo pueda desplegarse para utilizar Oracle o Sql Server, Access. Teniendo en cuenta claro los temas específicos de cada base de datos o proveedor.

Los proveedores disponibles los encontramos en el fichero machine.config, el cual se ubica en la carpeta del framework, en mi caso C:\Windows\Microsoft.NET\Framework\v2.0.50727\CONFIG

<system.data>
    <DbProviderFactories>


        <add name="Odbc Data Provider" invariant="System.Data.Odbc" description=".Net Framework Data Provider for Odbc" type="System.Data.Odbc.OdbcFactory, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>


        <add name="OleDb Data Provider" invariant="System.Data.OleDb" description=".Net Framework Data Provider for OleDb" type="System.Data.OleDb.OleDbFactory, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>


        <add name="OracleClient Data Provider" invariant="System.Data.OracleClient" description=".Net Framework Data Provider for Oracle" type="System.Data.OracleClient.OracleClientFactory, System.Data.OracleClient, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>


        <add name="SqlClient Data Provider" invariant="System.Data.SqlClient" description=".Net Framework Data Provider for SqlServer" type="System.Data.SqlClient.SqlClientFactory, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>


        <add name="Microsoft SQL Server Compact Data Provider" invariant="System.Data.SqlServerCe.3.5" description=".NET Framework Data Provider for Microsoft SQL Server Compact" type="System.Data.SqlServerCe.SqlCeProviderFactory, System.Data.SqlServerCe, Version=3.5.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91"/>

</DbProviderFactories>


</system.data>

En el siguiente Ejemplo podemos ver la diferencia en utilizar un proveedor de forma específica (SqlClient para SQL Server) y de utilizar las clases DbProviderFactories del espacio de nombres System.Data.Common para recuperar el mismo proveedor.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Common;
using System.Data.SqlClient;

namespace DataProviders
{
    class Program
    {
        static void Main(string[] args)
        {
            DbProviderFactory factoria = DbProviderFactories.GetFactory("System.Data.SqlClient");

            DbConnection conexion = factoria.CreateConnection();
            conexion.ConnectionString ="Data Source=.\\MSSQL2008STD;Initial Catalog=AdventureWorks;Integrated Security = SSPI";
            conexion.Open();
            Console.WriteLine("conexión creada");
            conexion.Close();

            SqlConnection conexionSql = new SqlConnection();
            conexionSql.ConnectionString="Data Source=.\\MSSQL2008STD;Initial Catalog=AdventureWorks;Integrated Security = SSPI";
            conexion.Open();
            Console.WriteLine("conexión creada");
            conexion.Close();

        }
    }
}

 

Podríamos por ejemplo tener un escenario en que hacemos una aplicación pueda distribuirse con dos modalidades, una básica con Access o una Enterprise con Sql Server. Y además dejar preparado nuestro código para la futura incorporación de un nuevo tipo de base de datos.

Vamos a ver por ejemplo como haríamos si quisiéramos que nuestro código funcionara para Access o Sql Server (Lo siento no tengo Oracle instalado, jejeje)

Podemos utilizar la conocida AdventureWorks, en mi caso la importo desde desde Sql a Access para poder hacer el programa.

Para simplificar el ejemplo, usaremos una propiedad en la clase de Datos que servirá para indicar con un valor entero si queremos conectarnos a SQL Server o Access.

En la misma clase construiremos el método conectar, que en función del tipo de conexión establecerá la cadena de conexión necesaria y el Dataprovider.

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

namespace DataProviders
{

    class DataproviderEjemplo
    {
         int conecctionType;

public int ConecctionType
{
  get { return conecctionType; }
  set { conecctionType = value; }
}

public DbConnection Conectar()
{
    String cadenaConexion = "";
    String proveedor = "";
   switch (conecctionType){
       case 0:
           cadenaConexion = "Data Source=.\\MSSQL2008STD;Initial Catalog=AdventureWorks;Integrated Security = SSPI";
           proveedor = "System.Data.SqlClient";
           break;
       case 1:
           cadenaConexion = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\\Users\\javier\\Documents\\AdventureWorksAccess.accdb;Persist Security Info=False;";
           proveedor = "System.Data.OleDb";
           break;
   }

   DbProviderFactory factoria = DbProviderFactories.GetFactory(proveedor);
   DbConnection conexion = factoria.CreateConnection();
   conexion.ConnectionString = cadenaConexion;
   conexion.Open();
   return conexion;

}
    }
}

Un programa que utilice esta clase podría ser el siguiente, simplemente se intenta ilustrar cómo conecto a access o sql en función del valor establecido en la propiedad connectiontype comentada arriba.

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

namespace DataProviders
{
    class Program
    {
        static void Main(string[] args)
        {
            DataproviderEjemplo _obj = new DataproviderEjemplo();
             DbConnection conexion;
            //conectamos con sql server
            _obj.ConecctionType=0;
            conexion = _obj.Conectar();

            //conectamos con Access
            _obj.ConecctionType = 1;
            conexion = _obj.Conectar();

        }
    }
}

 

Para finalizar vamos a hacer un método que devuelva un datatable y que haga una consulta a una tabla (Employee). Se ejecutará sobre Access o SqlServer en función del valor del ConnectionType que hayamos asignado.

Aprovechando que el nombre de las tablas en access no pueden contener un “.”, pues en la función que devuelve el datatable evaluaremos si es sql o access para ejecutar una consulta sql u otra. Esto nos puede ser util para el caso de que la sentencia sql cambie de una base de datos a otra.

La clase incluyendo la función que consulta la tabla sería la siguiente:

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

namespace DataProviders
{

    class DataproviderEjemplo
    {
         int conecctionType;

public int ConecctionType
{
  get { return conecctionType; }
  set { conecctionType = value; }
}

public DbConnection Conectar()
{
    String cadenaConexion = "";
    String proveedor = "";
   switch (conecctionType){
       case 0:
           cadenaConexion = "Data Source=.\\MSSQL2008STD;Initial Catalog=AdventureWorks;Integrated Security = SSPI";
           proveedor = "System.Data.SqlClient";
           break;
       case 1:
           cadenaConexion = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\\Users\\javier\\Documents\\AdventureWorksAccess.accdb;Persist Security Info=False;";
           proveedor = "System.Data.OleDb";
           break;
   }

   DbProviderFactory factoria = DbProviderFactories.GetFactory(proveedor);
   DbConnection conexion = factoria.CreateConnection();
   conexion.ConnectionString = cadenaConexion;
   conexion.Open();
   return conexion;

}
public DataTable GetReader(DbConnection conexion)
{

    DataTable tabla = new DataTable();
    DbCommand micommand = conexion.CreateCommand();
    switch (conecctionType)
    {
        case 0:
            micommand.CommandText = "select * from HumanResources.Employee";
            break;
        case 1:
            micommand.CommandText = "select * from HumanResources_Employee";
            break;
    }
    micommand.CommandType = System.Data.CommandType.Text;
    DbDataReader reader = micommand.ExecuteReader();
    tabla.Load(reader);
    return tabla;
}

    }
}

Y por último el programa que la utiliza puede ser el siguiente:

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

namespace DataProviders
{
    class Program
    {
        static void Main(string[] args)
        {
            DataproviderEjemplo _obj = new DataproviderEjemplo();
             DbConnection conexion;
             DataTable tabla;

            //conectamos con sql server o access cambiando el vaor 0 a 1
            _obj.ConecctionType=1;
            conexion = _obj.Conectar();
            tabla=_obj.GetReader(conexion);
            foreach(DataRow row in tabla.Rows) {
                foreach (DataColumn col in tabla.Columns)
                {
                    Console.WriteLine(row[col].ToString());
                }
            }

            conexion.Close();

        }
    }
}

Proteger una cadena de conexión con RSA por código

Si no nos interesa que nuestra cadena de conexión a una base de datos esté visible en nuestro fichero config, bien sea un app.config o un fichero config de Web, podemos escribir un código que lo encripte mediante protección RSA.

Para ello y partiendo de un fichero de configuración como el siguiente:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add name="MainConnection" connectionString="Data Source=.\MSSQL2008STD
         ;Initial Catalog=AdventureWorks; Integrated Security=SSPI"/>
  </connectionStrings>
</configuration>

Podemos escribir el código necesario para encriptarlo.

El código sería el siguiente:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Data.SqlClient;

namespace ProtectConnection
{
    class Program
    {
        static void Main(string[] args)
        {
            //Primero abrimos el fichero de configuración
            System.Configuration.Configuration config =
            ConfigurationManager.OpenExeConfiguration(
             ConfigurationUserLevel.None);

            // No es necesario pero probamos a Conectarnos para probar la cadena de conexión que hemos creado...
            String cadenaconexion = ConfigurationManager.ConnectionStrings["MainConnection"].ConnectionString;
            SqlConnection miconexion = new SqlConnection();
            miconexion.ConnectionString = cadenaconexion;
            miconexion.Open();
            Console.WriteLine("Conexion Creada");
            miconexion.Close();

            //A continuación cargamos la sección de cadenas de conexión.
            ConfigurationSection cadenasdeconexion =
                config.GetSection("connectionStrings");

            //si no está encriptada la encriptamos.
            if (!cadenasdeconexion.SectionInformation.IsProtected)
            {
                Console.WriteLine("Encriptando fichero");
                cadenasdeconexion.SectionInformation.ProtectSection
                    ("RsaProtectedConfigurationProvider");
                cadenasdeconexion.SectionInformation.ForceSave = true;
                config.Save(ConfigurationSaveMode.Full);
            }
            Console.ReadLine();
        }
    }
}

 

Si observamos en el directorio una vez generado el .exe, veremos que si abrimos nuestro fichero de configuración, está sin encriptar:

conexion0

Si ejecutamos una vez el programa veremos como se conecta a la base de datos y luego encripta la conexión:

conexion1

Si lo volvemos a ejecutar, veremos que ya no lo encripta y que vuelve a desencriptarlo automáticamente sin escribir código adicional.

conexion2

Una vez ejecutado el programa, el fichero pasaría a tener el siguiente contenido:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings configProtectionProvider="RsaProtectedConfigurationProvider">
    <EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element"
      xmlns="http://www.w3.org/2001/04/xmlenc#">
      <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" />
      <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
        <EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#">
          <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
          <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
            <KeyName>Rsa Key</KeyName>
          </KeyInfo>
          <CipherData>
            <CipherValue>q3+U3GzGciObvpgVZgjWRGMpeNYXsgpQEKFHP6q+SG3EfG54ZhcK69ocFpnqnhZZE3eZaLE0KUZBecRWeZLbtz6zI+zqg2x1er7t4DeZn5LODslURYlmNelTrR4AmXwj+ZlMMiwTb0kFjTrzlrgCe0kpo346QtxV/4shW2c4Zw0=</CipherValue>
          </CipherData>
        </EncryptedKey>
      </KeyInfo>
      <CipherData>
        <CipherValue>I9uyNHeclHjakBYoa2Y53MGFYHKJAka+GI3Lr8lYREDh7wEDyGd54ijwOLuO7BEckpIB92bHEulvP0HsKFfYUEvuP3WTBwgsbKrwNR7fJ3iHKmYf/n770Ms0f5WByFlBKki9EXO+/bmBNCBKxy8ZKMqC/mtYU5M3QtX3vjJqzRzf+gL8/Ix33neAob3qFIn554Ho+wLKbsLpSBuYD2FVLUmCfN6NdXtO6ElfB6CEIrw9ayIhvd9qzRhxHIGhfIotpuD+gu0/YtdHKLSD6/wH4vwNsM8YmSF+17AX8+oRIYl3aZbQHk0olIyq0BqRsx1vTgAPZW9z5RZC1RD0lc2zeII/NcLFup09Z5k8jbVRFYcOnUKnC0FnWJXa6TlikpUYxkuRQIR+wsQDyBbu7oHUKBRe/249DNUP/GXKwxiwYO/T132w/rjGEiESVMx+/QkrEG7KsQZpuiPgDNyvtxKdlQOzsQFKJtGlR7D6l3Az+0pfB9mUprLeJP0JIYH8pN4UtY0cWUGeR6hc+PmljhSQ6U3njC8eASJL0HdNGTcB7+0JphDchDn8VmNM5846dY18qNE30n2ZAHGW96CCTv+UhA==</CipherValue>
      </CipherData>
    </EncryptedData>
  </connectionStrings>
</configuration>

Crear una Conexión con SqlConnectionStringBuilder

 

SqlConnectionStringBuilder es una clase que nos permite crear conexiones ADO.net sin correr riesgos al introducir los valores.

Esta clase nos permite a través de sus propiedades configurar una conexión ADO.

Algunas de sus propiedades más importantes son:

ApplicaitonName: Establece u obtiene el nombre de la aplicación asociada con el connection String

AsynchronousProcessing: Establece u obtiene el valor que indica si la conexión permite procesamiento asíncrono.

AttachDBFilename: Establece u obtiene el valor del datafile primario (.mdf).

BrowsableConnectionString: Establece u obtiene un valor que indica si la conexión es visible en el diseñador de Visual Studio.

ConnectionString: Establece u obtiene el valor completo de la cadena de conexión, asociada al DBConnectionStringBuilder.

ConnectTimeout: Establece u obtiene el timeout para la conexión.

DataSource: Establece u obtiene el nombre o la dirección IP  de la instancia SQL Server a la que se conecta.

Encrypt: Establece u obtiene un valor boolean que indica cuando SQL Server utiliza SSL encryption para todos los datos que se envían entre el cliente y el servidor.

InitalGatalog: Establece u obtiene el nombre de la base de datos asociada con la conexión.

Integrated Security: Establece u obtiene el valor que indica el tipo de conexión. True-> para la seguridad integrada de windows, y False-> para indicar usuario y contraseña.

MaxPoolSize: Obtiene o Establece el máximo número de conexiones en el connection pool.

MinPoolSize: Obtiene o establece el mínimo número de conexiones para el connection pool.

MultipleActiveResultsets: Obtiene o establece mediante un valor boolean el uso de MARS.

PacketSize: Obtiene o establece el número en bytes del paquete de red de intercambio de datos con SQL Server.

Password: Establece u obtiene la contraseña para la cuenta de SQL Server asociada a la conexión.

Pooling: Establece u obtiene un valor boolano que indica si se utiilza pooling.

UserId: El usuario de SQL Server asociado a la conexión.

 

Un sencillo ejemplo del uso de esta clase es el siguiente:

 

public void Conectar()
{
    SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
    builder.DataSource = "Server\\MSSQL2008STD";
    builder.InitialCatalog="VideoGames";
    builder.IntegratedSecurity = true;
}

Configurando Assemblies con Configuration y ConfigurationManager

 

En este post voy a intentar hacer unos ejemplos que resuman la funcionalidad que nos aportan las clases configuration y ConfigurationManager.

 

Tras comprobar que es difícil encontrar un ejemplo sencillo de la utilización de estas clases en la msdn, voy a realizar un ejemplo completo de cómo añadir, eliminar, modificar y listar las claves de un archivo de configuración mediante la utilización de estas clases.

 

Lo primero que tenemos que tener en cuenta es que cuando ejecutemos nuestra aplicación, si lo hacemos desde el visual studio, el archivo de configuración que se crea es temporal (del tipo nombre_assemblie.vshost.exe.config), por lo que para que nuestro config quede persistente e ir comprobando los cambios, lo mejor es generar la aplicación y ejecutarla de manera independiente para ver el fichero de configuración que se genera.

 

Para una mejor compresión el ejemplo va a estar realizado sobre una aplicación de consola en lenguaje c#.

 

Lo primero es crear una aplicación de consola y en el main, agregar el siguiente código. Esto no lo explico ya que como se dice el siguiente código es autoexplicativo ;-)

 

 

 

static void Main(string[] args)
       {
              string opcion = "";

              while (opcion.ToLower() != "0")
              {
                  Console.Clear();
                  Console.WriteLine("Opciones");
                  Console.WriteLine("0.- Salir");
                  Console.WriteLine("1.- Leer Todo el AppSettings");
                  Console.WriteLine("2.- Crear una Clave en el  AppSettings");
                  Console.WriteLine("3.- Modificar una Clave en el  AppSettings");
                  Console.WriteLine("4.- Borrar una Clave en el  AppSettings");
                  switch (opcion)
                  {
                      case "1":
                          ConfigTest.ReadAppSettingsAllKeys();
                          break;
                      case "2":
                          ConfigTest.WriteAppSettingsKey();
                          break;
                      case "3":
                          ConfigTest.ModifyAppSettingsKey();
                          break;
                      case "4":
                          ConfigTest.DeleteAppSettingsKey();
                          break;
                  }
                  opcion = Console.ReadLine();
              }        
       }

 

A continuación agregaremos una clase al proyecto, en mi caso la he llamado ConfigTest.cs, sobre esta clase agregaremos la referencia a System.Configuration, ya que es necesaria para poder utilizar los using que necesitamos.

 

Dibujo1

Una vez añadida la referencia incluiremos dos using que vamos a utilizar:

Para recuperar la lista de entradas en el appSetting necesitamos using using System.Collections.Specialized; ya que la lista de entradas se devuelve como una NamedValue Collection.

Para poder acceder a las clases Configuration y ConfigurationManager, debemos incorporar el using System.Configuration;

Una vez que hemos hecho esto crearemos una función para crear, borrar, modificar, listar una entrada y listar todas las entradas.

Los comentarios están en el propio código así que copio toda la clase y luego pongo unas capturas de su utilización.

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

namespace TestConfigurationSettings
{
    class ConfigTest
    {
        // Para  lee todas las entradas del appSettings 

        public static void ReadAppSettingsAllKeys()
        {
            try
            {
                Console.Clear();
                Console.WriteLine("Opción 1. Ver las entradas en el AppSetting");

                // Obtenemos la colección de valores de AppSettings
                NameValueCollection appSettings = ConfigurationManager.AppSettings;

                if (appSettings.Count == 0)
                {
                    Console.WriteLine("[ReadAppSettings: {0}]",
                    "AppSettings no tiene Entradas.");
                }
                for (int i = 0; i < appSettings.Count; i++)
                {
                    Console.WriteLine("#{0} Key: {1} Value: {2}",
                      i, appSettings.GetKey(i), appSettings[i]);
                }
            }
            catch (ConfigurationErrorsException e)
            {
                Console.WriteLine("[ReadAppSettingsAllKeys: {0}]",
                    e.ToString());
            }
        }

        //Esta función se utiliza para modificar una clave en el AppSettings
        public static void ModifyAppSettingsKey()
        {
            try
            {
                   //Mostramos las claves actuales
                Console.Clear();
                Console.WriteLine("Opción 3. Modificar una Clave en el AppSetting");
                Console.WriteLine("Estas son las claves actuales:");
                ReadAppSettingsAllKeys(); //llámamos a la función de lectura que hemos creado previamente

                Console.WriteLine("Introduce la clave a modificar:");
                String clave = Console.ReadLine();
                Console.WriteLine("Introduce la clave a modificar:");
                String valor = Console.ReadLine();

              //Ahora modificamos la clave
                //Primero obtenemos el fichero de configuración
                Configuration Config = ConfigurationManager.OpenExeConfiguration
                        (ConfigurationUserLevel.None);

                //Creamos un objeto de tipo AppSettingsSection y le asignamos la sección
                //appSettings de nuestro fichero de configuración
                AppSettingsSection appSection = Config.AppSettings;

                //Por último asignamos el valor y refrescargamos la sección entera para
                //tenerla disponible en el programa
                appSection.Settings[clave].Value = valor;

                Config.Save(ConfigurationSaveMode.Modified);

                ConfigurationManager.RefreshSection("appSettings");

            }
            catch (ConfigurationErrorsException e)
            {
                Console.WriteLine("[ModifyAppSettingsAllKey: {0}]",
                    e.ToString());
            }
        }

       //Esta función se utiliza para crear una clave en el AppSettings
        public static void WriteAppSettingsKey()
        {
            try
            {
                //Primero abrimos el fichero de configuración
                System.Configuration.Configuration config =
                ConfigurationManager.OpenExeConfiguration(
                 ConfigurationUserLevel.None);

                //Pedimos la clave por consola
                Console.Clear();
                Console.WriteLine("Opción 2. Crear una Clave en el AppSetting");

                Console.WriteLine("Introduce la clave para la entrada en el appSetting");
                String Clave = Console.ReadLine();

                //Pedimos el valor por la consola
                Console.WriteLine("Introduce el valor para la entrada en el appSetting");
                String Valor = Console.ReadLine();
                //Ahora agregamos la nueva entrada al fichero de configuración
                config.AppSettings.Settings.Add(Clave, Valor);

                //Después guardamos el fichero
                config.Save(ConfigurationSaveMode.Modified );

                //Una vez que hemos guardado el fichero es necesario
                //Volver a recargar al menos la sección que hemos modificado.
                //De esta forma podremos utilizar los valores desde nuestra aplicación.
                ConfigurationManager.RefreshSection("appSettings");

                AppSettingsSection appSettingSection =
                  (AppSettingsSection)config.GetSection("appSettings");
            }
            catch (ConfigurationErrorsException e)
            {
                Console.WriteLine("[WriteAppSettingsKey: {0}]",
                    e.ToString());
            }

              }
        //Esta función borra una clave del appSettings
        public static void DeleteAppSettingsKey()
        {
            try{
                //Mostramos las claves actuales
                Console.Clear();
                Console.WriteLine("Opción 4. Borrar una Clave en el AppSetting");
                Console.WriteLine("Estas son las claves actuales:");
                ReadAppSettingsAllKeys(); //llámamos a la función de lectura que hemos creado previamente

                Console.WriteLine("Introduce la clave a borrar:");
                String clave = Console.ReadLine();

                //Ahora procedemos a borrar la clave
                Configuration Config = ConfigurationManager.OpenExeConfiguration
                       (ConfigurationUserLevel.None);

                //Creamos un objeto de tipo AppSettingsSection y le asignamos la sección
                //appSettings de nuestro fichero de configuración
                AppSettingsSection appSection = Config.AppSettings;

                //Por último asignamos el valor y refrescargamos la sección entera para
                //tenerla disponible en el programa
                appSection.Settings.Remove(clave);

                //Después guardamos el fichero
                Config.Save(ConfigurationSaveMode.Modified);

                //Una vez que hemos guardado el fichero es necesario
                //Volver a recargar al menos la sección que hemos modificado.
                //De esta forma podremos utilizar los valores desde nuestra aplicación.
                ConfigurationManager.RefreshSection("appSettings");

            }
            catch (ConfigurationErrorsException e)
            {
                Console.WriteLine("[DeleteAppSettingskey: {0}]",
                    e.ToString());
            }

        }
    }
}

 

Para finalizar vamos a ver un ejemplo de su utilización:

Primero tras ver el menú principal, metemos algunas entradas, yo sólo muestro el pantallazo de la primera por legibilidad.

Dibujo2

Con la Opción 2 vamos introduciendo entradas

Dibujo3

Tras haber introducido la primera entrada se habrá generado un fichero, que podemos ver aquí con el mi caso las tres entradas que he creado:

Dibujo4

Así podéis probar todas las opciones, por ejemplo la de listar todas las claves

Dibujo5

Espero que os sirva!!!