DNM+ Online
dotnetmania 2.0
El modelo de proveedores de ASP.NET 2.0
Este artículo investiga el modelo de proveedores de .NET, uno de los conceptos fundamentales sobre los que se sustenta ASP.NET 2.0. Estudiaremos cómo funciona la arquitectura de proveedores y su importancia, y conoceremos la forma de crear nuestros propios proveedores personalizados.

ASP.NET 2.0 es un entorno de desarrollo de aplicaciones Web extenso y complejo en sus entresijos. A pesar de que dispone de multitud de características que nos ahorran muchas horas de trabajo, éstas no siempre se adaptarán a nuestras necesidades. Por ejemplo, desde la versión 1.0 del producto disponemos de tres formas de almacenar el estado de sesión de nuestras aplicaciones. Por defecto, éste se almacena en el mismo proceso de ejecución de ASP.NET, pero con un simple cambio en el archivo de configuración Web.config podemos conseguir que las variables de sesión se persistan en un servidor central de estado o en una base de datos de SQL Server. Con estas tres opciones tenemos para el 95% de los casos habituales. Pero, ¿qué pasa si necesitamos almacenar la información en otro lugar, como por ejemplo una base de datos Oracle? ¿Y si aún usando uno de los tres almacenamientos anteriores queremos cambiar su comportamiento en ciertas circunstancias? En ASP.NET 1.x tendríamos muy difícil cambiar cualquiera de estas cosas. No nos quedaría más remedio que "trucar" la plataforma y escribir partes enteras de ésta de nuevo para poder sustituirlas de modo no-nativo. En ASP.NET 2.0 han aparecido muchas nuevas API que nos facilitan las tareas más comunes, como la gestión de la seguridad a través de Membership y Roles, el almacenamiento de preferencias con Profiles, el registro de eventos de aplicaciones con WebEvents, y multitud de cuestiones más. Cuando en alguna charla o curso enseño los fundamentos de Membership y Roles, algo de uso muy habitual, los oyentes suelen quedar encantados con las posibilidades que éstas ofrecen. Sin embargo, siempre hay alguien que de repente cae en la cuenta de lo mucho que tuvo que trabajar para conseguir algo similar en ASP.NET 1.x, y la pregunta lógica no tarda en aparecer: "Si uso Membership y Roles, ¿qué pasa con la inversión que he hecho para desarrollar mi propio método? ¿Cómo puedo usar mi propia base de datos de usuarios que ya había creado y aprovechar las facilidades de la nueva API?". Se trata de una pregunta lógica y relacionada directamente con el modelo de proveedores. Al final de este artículo estaremos en disposición de contestarla.

El patrón de diseño de proveedores Un proveedor es un componente de software que proporciona la implementación de una interfaz estándar y que aísla a un servicio del medio de almacenamiento u obtención de datos que utiliza para su propósito. En lenguaje llano, se trata de un componente intermedio que separa una funcionalidad del lugar donde almacenamos su información. El funcionamiento de un servicio que sigue el modelo de proveedores se puede modificar simplemente cambiando de manera externa (por configuración) el proveedor que éste utiliza por debajo. Es decir, el proveedor se sitúa entre la API final que usa el programador y el medio de almacenamiento empleado. De este modo, aunque se cambie el proveedor no hay que tocar el código y el programa pasa a funcionar de una forma diferente sin que sea perceptible para el programador. Por ejemplo, en la API de seguridad Roles podemos hacer que los perfiles a los que pertenecen nuestros usuarios se lean desde una base de datos SQL Server en donde previamente los habremos almacenado, o bien que se comprueben contra el Directorio Activo de nuestra organización. Para ello no tendremos que tocar el código de la aplicación ni recompilarla. Lo único que se debe hacer es cambiar un ajuste en Web.config para que esta API haga uso de un proveedor diferente en cada caso. Dicho proveedor se cargará de forma transparente para nosotros por el motor de tiempo de ejecución de .NET, y funcionará "de tapadillo", dando el soporte a las funciones de la API.
El esquema de la figura 1 muestra de manera clara el concepto. Cada una de las API representadas en la parte superior de la figura dispone de uno o más proveedores nativos (capa del medio) que utilizan su correspondiente medio de almacenamiento (capa inferior). Los servicios representados en la figura son solo unos pocos de los disponibles realmente. Casi toda funcionalidad de ASP.NET 2.0 que tenga que ver con almacenar un estado o mover un dato está implementada usando este modelo. Enseguida veremos qué servicios tenemos disponibles de esta manera. En definitiva, los objetivos que se persiguen con el modelo de proveedores son: •  Aislar tanto nuestro código como el de ASP.NET de la implementación y funcionamiento concreto de la mayor parte de las API de alto nivel disponibles. •  Hacer que el almacenamiento sea flexible para adaptarlo a nuestras necesidades, y extensible para poder usar otros medios de persistencia alternativos. Tiene a todo el mundo contento: si nos sirve la implementación por defecto de una API, simplemente la usamos. Si no se nos adapta, podemos cambiarla o crear una propia. •  Que sea muy fácil crear nuestros propios proveedores, proporcionando un sistema de clases base bien documentado que nos den parte del trabajo ya hecho. •  Permite separar de manera sencilla las labores de desarrollo para trabajo en equipo: mientras unos trabajan en la aplicación en sí con proveedores por defecto, otros pueden trabajar en paralelo en la implementación concreta que se necesite de una característica.

Tipos de proveedores
y proveedores disponibles ASP.NET 2.0 ofrece soporte para el modelo de proveedores en todos los servicios ilustrados en la figura 2. Cada uno de estos servicios dispone de al menos un proveedor nativo integrado en la plataforma, si bien todos ellos permiten la creación de proveedores adicionales que nos permiten adaptar su funcionamiento a nuestras necesidades. También se puede escribir código que en lugar de crear un nuevo proveedor se limite a modificar el comportamiento existente de cualquiera de los nativos. En la tabla 1 se pueden ver los proveedores nativos disponibles para cada uno de los servicios. Los que tienen un asterisco al final son los proveedores por defecto que se usan en el servicio, si no los modificamos mediante configuración. Tanto la API para notificación de eventos Web como la de protección y cifrado de secciones de configuración carecen de un proveedor por defecto (ninguno tiene un asterisco en la tabla), ya que en ambos casos es necesario establecerlos manualmente tocando la configuración si queremos usarlos. El funcionamiento
de los proveedores Para ver mejor los entresijos del funcionamiento de los proveedores, y antes de meternos de verdad en harina construyendo uno propio, fijémonos en cómo están implementados los proveedores de Membership de la plataforma (figura 3). Como podemos observar, tenemos una clase llamada MembershipProvider que hereda de la clase base abstracta BaseProvider. Esta clase a su vez es también abstracta, de modo que no es ésta la que define el funcionamiento del proveedor, sino que se usa como plantilla de lo que debe implementar un proveedor de este subsistema Membership. Es entonces cuando aparecen los proveedores concretos que se van a utilizar. Así, el proveedor SqlMembershipProvider hereda de MembershipProvider y sobrescribe la mayor parte de los métodos de éste para crear una implementación concreta de la funcionalidad requerida. Todo ello forma una jerarquía que va de lo más general a lo concreto. La clase BaseProvider forma los cimientos de todos los proveedores, y cada subsistema o API concreto define su clase abstracta adaptada a sus necesidades, de la cual heredan los proveedores concretos que se van a utilizar. La definición en C# de la clase BaseProvider es la siguiente: Solo define el nombre del proveedor, su descripción y un método de inicialización que es lo verdaderamente importante. Al estar declarados los métodos como virtuales, las clases que heredan de ésta, como MembershipProvider o los proveedores finales, tienen la opción de implementarlos para recoger desde la configuración los parámetros de personalización que sean necesarios. Por ejemplo, si escribimos en Web.config la definición siguiente para el proveedor SqlMembershipProvider: Durante la llamada que la API realiza automáticamente al método Initialize de la clase SqlMembershipProvider, el código recupera todos los atributos especificados dentro de la colección attributes que se le pasa como segundo parámetro. Así se ajustan diversas propiedades y se personaliza el comportamiento del proveedor desde la configuración. Luego lo veremos. Dado que BaseProvider proporciona su propia implementación también del método de inicialización, suele ser conveniente llamarlo desde el nuestro. Además, la clase intermedia (en este caso MembershipProvider), aunque es abstracta, generalmente proporciona algunos servicios comunes ya construidos. Por ejemplo, en el caso que nos ocupa el cifrado de las claves para almacenarlas de forma segura no hace falta que lo implementemos, pues disponemos de un método EncodePassword que se encarga de ello por nosotros. Aunque, claro está, no tenemos porqué usarlo. Solo crearemos clases propias derivadas de ProviderBase si necesitamos definir un modelo de proveedores para un servicio nuevo creado desde cero por nosotros, pero no para un servicio existente. En este caso, para definir un proveedor propio heredaremos de la clase derivada de ProviderBase que se haya definido para el servicio que nos interesa personalizar. En el caso de Membership será de MembershipProvider, pero en el caso de la API de protección de la configuración sería ProtectedConfigurationProvider, y de forma análoga en los demás casos. En la figura 4 se muestra la jerarquía de clases de proveedores que vienen con ASP.NET 2.0. No vamos a analizar el código de ninguna de estas implementaciones. No nos llegaría una docena de artículos como éste. Aprenderemos creando un nuevo proveedor propio. No obstante el lector interesado puede encontrar el código fuente completo de todos los proveedores nativos de ASP.NET en la siguiente URL:

http://download.microsoft.com/ download/a/b/3/ab3c284b-dc9a-473d-b7e3-33bacfcc8e98/ProviderToolkitSamples.msi

Microsoft los liberó hace unos meses para facilitar su estudio y fomentar el uso del modelo de proveedores en aplicaciones propias. Son dignos de pararse a estudiarlos. Se aprende mucho. Proveedores de mapas
de sitios Veamos ahora un ejemplo de cómo crear un proveedor propio para personalizar el comportamiento de un subsistema de ASP.NET 2.0. El caso típico que se suele encontrar por Internet es el que crea un proveedor propio de Membership y/o Roles. El lector interesado en ver un ejemplo sencillo de éstos (mucho menos complejo que el de los proveedores nativos antes mencionados) puede descargar desde CodePlex los "proveedores simples de Altairis":

http://www.codeplex.com/Wiki/View.aspx ?ProjectName=AltairisWebProviders Dado que hay muchos ejemplos de este estilo por ahí, intentaremos ser un poco más originales y hacer algo diferente a lo habitual y que ilustre otro tipo de proveedor menos típico. Una cuestión interesante nueva en ASP.NET 2.0 son los controles de navegación. Éstos (menús, árboles y rutas) están enlazados a datos y beben información de un control fuente de datos llamado SiteMapDataSource. Basta con arrastrar sobre un formulario Web un control SiteMapDataSource y un control de navegación como un <asp:Menu> para tener implementada en segundos la navegación de un sitio o aplicación. Estos controles SiteMapDataSource obtienen la información jerárquica de navegación a partir de un determinado proveedor de estructura de sitio. Si nos fijamos en la figura 4, veremos que el único proveedor nativo disponible para la navegación por un sitio es el XmlSiteMapProvider. Éste es un proveedor especializado en leer archivos XML que contienen información sobre la estructura de páginas que queremos utilizar para la navegación. El archivo que se utiliza por defecto se llama Web.sitemap, si bien es posible definir diversos archivos XML diferentes para navegaciones parciales con sólo añadir nuevos proveedores de este tipo. La estructura de uno de estos archivos de navegación es análoga a ésta: Como podemos imaginar es un tanto tedioso de construir, sobre todo porque Visual Studio no nos proporciona ningún tipo de editor para facilitarnos la tarea.

Creación de un proveedor personalizado En muchas ocasiones, sobre todo en aplicaciones sencillas, nos resultaría de mucha más utilidad poder generar de forma automática la estructura del sitio directamente a partir de los archivos y carpetas que éste contiene. Así, bastaría con arrastrar un control y ver cómo obtenemos la navegación "de la nada", con enlaces para ir a las diferentes páginas y carpetas del sitio.
Eso es precisamente lo que vamos a construir. Nuestro proveedor personalizado se llamará FSSiteMapProvider (de File System SiteMap Provider), y no necesitará archivo alguno para trabajar, ya que recorrerá la estructura de contenidos de la carpeta que le indiquemos dentro de nuestro proyecto, y generará automáticamente una jerarquía de navegación gemela. Nos resultará de mucha utilidad a la hora de crear la navegación de aplicaciones de tamaño pequeño o mediano. Es extraño que Microsoft no haya incluido algo así de serie en ASP.NET. Lo primero que debemos hacer es añadir una nueva clase a nuestra carpeta AppCode. Le llamaremos igual que el proveedor, FSSiteMapProvider: Al heredar de StaticSiteMapProvider deberemos redefinir dos métodos abstractos que es indispensable crear y que ya nos genera Visual Studio por nosotros. El primero de ellos se llama BuildSiteMap y sirve, como su propio nombre indica, para construir el mapa del sitio a partir del medio de persistencia elegido, en este caso una carpeta del disco duro. El motor de tiempo de ejecución de ASP.NET llama automáticamente a este método cuando necesita generar un mapa del sitio. El listado 1 muestra el código para este método en nuestro ejemplo (el código completo está disponible para descarga en la Web de dotNetManía). Este código es bastante sencillo, ya que se ha dividido por claridad en métodos auxiliares que hacen el trabajo "de fondo". Esto nos permitirá además entender mejor lo que está pasando. He definido para la clase una serie de variables privadas que se usan para almacenar estados internos necesarios para trabajar. El nombre de todas ellas comienza con un guión bajo, que es la notación que suelo usar en mi código para este tipo de miembros. Los mapas de sitio son una jerarquía de nodos de tipo SiteMapNode. Cada uno de ellos contiene una referencia tanto en sentido ascendente como descendente, es decir, a su nodo padre y a sus hijos. Por este motivo, para obtener un mapa completo sólo es necesaria la referencia al nodo raíz de la jerarquía, ya que ASP.NET puede recorrerla en el sentido que prefiera para generar los mapas. Es por ello que este primer método devuelve simplemente una clase de tipo SiteMapNode. Lo primero que se hace es comprobar si ya existe un mapa del sitio previamente generado en la variable _nodoRaiz. Para no tener que estar recorriendo el disco siempre que alguien haga una petición y generar de nuevo el mapa en cada ocasión, lo que hacemos es generarlo una primera vez y a partir de ese momento lo almacenamos en una variable privada, que será lo que se devuelva en sucesivas ocasiones. También comprobamos (con cortocircuito) si ha cambiado la estructura de carpetas de la carpeta raíz de nuestro mapa. Más tarde volveremos sobre este detalle, pero de momento vamos a olvidarnos porque es accesorio a la creación del proveedor en sí. Si no hay ninguna copia del mapa en caché o ha cambiado la estructura de disco, volvemos a generar la jerarquía de nuevo. El método Resetear simplemente borra la caché en caso de que exista, para empezar con la jerarquía desde cero. Se crea el primer nodo indicando la ruta física y la virtual de la carpeta raíz que usaremos para generar el mapa. Ésta se especifica mediante configuración, como luego veremos, y se almacena en la variable privada _carpetaRaiz. El método CrearNodo se puede ver en el listado 2. Éste aísla la lógica de creación de objetos SiteMapNode, y trata de manera distinta a los nodos que van a apuntar a carpetas y a los que apuntan a archivos. Si se trata de una carpeta hace caso omiso de las que son de sistema, esto es, las que empiezan con el nombre "App" (como App_Code) y la carpeta bin, que no deben aparecer en la navegación. Además, cuando se añade una referencia a una carpeta se le añade automáticamente el nombre de la página por defecto, Default.aspx, que deberá existir (podría optimizarse este código para comprobar su existencia, pero lo dejo para el lector). El nodo se crea indicando el proveedor (Me), la ruta física y ruta virtual a la que apunta, y como título para éste el nombre de la carpeta o del archivo sin la extensión. A este primer nodo creado directamente se le asigna el título que se haya indicado en la configuración (luego lo veremos), y que se lee a través de un propiedad personalizada llamada Inicio. Se añade el nodo al mapa del proveedor usando una versión sobrecargada del método AddNode que ya implementa la clase base. Con esto queda establecida la raíz de la jerarquía. Una vez que tenemos el primer nodo, generamos los subsiguientes recorriendo la estructura de disco a partir de la carpeta raíz elegida. He creado una función a tal efecto llamada CreaMapaAPartirDe. Se trata de una función recursiva que toma como parámetros el nodo actual y la ruta de una carpeta cuyos archivos debemos incluir como hijos de éste en el mapa. El código de este método se muestra en el listado 3. Usando la clase System.IO.DirectoryInfo, añadimos como hijos del nodo actual tantos nodos como archivos .aspx haya en la carpeta indicada. Además, por cada carpeta contenida en éste se añade también un nodo hijo, y la función se llama recursivamente a sí misma para generar las entradas para sus  archivos. Es la belleza de la recursividad. Volviendo al listado 1, lo único que resta tras haber generado la jerarquía de nodos es devolver el nodo padre como resultado de llamar a la función.

Destrucción de la caché Dado que nuestro proveedor hace caché del mapa, si se producen cambios en la estructura de disco una vez que éste haya sido generado, quedaría obsoleto. La única forma de volver a refrescarlo sería llamando por código al método Resetear que hemos definido. Esto podría ser una buena solución, pues bastaría con colocar un botón en alguna página de administración y pulsarlo cuando fuera necesario (las actualizaciones no serían frecuentes seguramente). De todos modos, he decidido mejorar el proveedor para que detecte al menos los cambios que se produzcan en la carpeta raíz de nuestra jerarquía. Para ello he aprovechado la clase CacheDependency de ASP.NET que es la que usa el sistema de caché para determinar cuándo debe hacer expirar los elementos que dependen de algún archivo. El constructor de esta clase permite indicar un archivo o carpeta a monitorizar y el momento en que queremos empezar a controlarlo (si no lo indicamos, empieza inmediatamente). Posteriormente se puede comprobar su propiedad HasChanged, que devolverá true en caso de que haya cambiado algo. De hecho, también se puede capturar un evento en el preciso instante en que se produzca el cambio para destruir la caché, pero en nuestro caso sólo necesitamos saber si ha cambiado cuando se nos solicite el mapa de nuevo, no en tiempo real. El único problema de este sistema elegido es que sólo detectará cambios en la carpeta raíz, pero en la mayor parte de los casos será suficiente. Podríamos construir un notificador más potente que controlase las subcarpetas usando la clase FileSystemMonitor, pero eso se sale por completo del ámbito de nuestro artículo. A estas alturas, algún lector avispado se estará preguntando por qué no hemos usado directamente el objeto Cache de ASP.NET para almacenar el mapa que generamos, en lugar de guardarlo en una variable privada. Enseguida veremos el motivo.

Otro detalle más Si a estas alturas aún sigue con nosotros, recordará que dije que era necesario otro método más para este tipo de proveedor. Este otro método abstracto que debemos implementar para acabar de definir nuestro flamante proveedor se llama GetRootNodeCore. Se encuentra definido en la clase SiteMapProvider, de la que hereda StaticSiteMapProvider,  como se observa en la figura 4. Éste es llamado automáticamente por los controles apropiados de ASP.NET cuando se necesita obtener una referencia al nodo raíz del mapa actual. Es funcionalmente idéntico al método anterior, por lo que su código es bien fácil, ya que sólo hay que llamar al método BuildSiteMap:

Protected Overrides Function _ GetRootNodeCore() As _ System.Web.SiteMapNode Return BuildSiteMap() End Function Consideraciones sobre seguridad para múltiples
subprocesos Antes de continuar con los últimos detalles de nuestro proveedor para a continuación poder probarlo, es necesario hacer unos comentarios muy importantes sobre el modo en que trabajan éstos. En el listado 1 se observa que el código está contenido dentro de una sentencia SyncLock. El motivo es que es necesario controlar el acceso simultáneo de varios hilos a las variables privadas de nuestro proveedor. En ASP.NET, generalmente todas las clases como módulos o clases que representan páginas se instancian individualmente por cada petición recibida (por cada hilo de ejecución). De este modo, cada petición tiene sus propias clases, y no es necesario tener en cuenta que puedan ser accedidas por varios hilos al mismo tiempo. Los proveedores son una excepción a esta norma, ya que una misma instancia de un proveedor es compartida por todos los hilos de ejecución de la misma aplicación. Por este motivo, es muy probable que haya varios subprocesos accediendo simultáneamente a las mismas variables privadas de nuestro proveedor, y es necesario por lo tanto regular el acceso a éstas estableciendo bloqueos de sincronización con SyncLock. Si no hubiésemos tenido en cuenta este detalle, seguramente nuestro proveedor funcionaría perfectamente en los entornos de pruebas y durante el desarrollo. Al ponerlo en producción y tener muchos accesos es cuando empezarían los problemas y probablemente nos costaría mucho averiguar qué está pasando. Así que no debemos olvidar que todos los métodos y propiedades de un proveedor deben estar preparados para trabajar bajo múltiples subprocesos.
Para ilustrar este concepto es por lo que hemos implementado la caché en una variable privada de la clase. Su valor se mantiene además entre las diferentes peticiones de un mismo o de diferentes usuarios, por lo que tenemos una forma fácil y eficiente de almacenar nuestros datos comunes sin recurrir al objeto Cache. Hay multitud de formas de sincronizar el acceso multi-hilo, pero el uso de SyncLock es el más sencillo para nuestro propósito. Inicialización Con lo visto hasta ahora tenemos casi todo lo necesario para dar por terminado nuestro proveedor. Nos falta todavía una cuestión: la inicialización del mismo. Para poner en funcionamiento un proveedor, debemos agregarlo al archivo de configuración de nuestra aplicación. En éste se especifica la clase que define el proveedor y es posible indicar los parámetros que sean necesarios para personalizar su funcionamiento. En nuestro ejemplo podremos indicar cuál es la carpeta raíz que usaremos para generar el mapa (por defecto "~/") y también el nombre que queremos darle al nodo raíz ("Inicio" de forma predeterminada). Estos parámetros se especifican en Web.config, como veremos enseguida. Cuando ASP.NET necesita usar el proveedor por primera vez llama a su método Initialize, pasándole como parámetros el nombre del proveedor y una colección de parejas nombre/valor con los atributos especificados en la configuración. De este modo, resulta muy fácil asignar configuraciones específicas como se ilustra en el listado 4. Lo único que hacemos es verificar si se ha especificado o no el parámetro, y en tal caso lo asignamos usando la propiedad correspondiente que habremos definido en nuestra clase. Nótese que los nombres de los parámetros distinguen entre mayúsculas y minúsculas. Al fin y al cabo se trata de XML, y este tipo de cosas importan. Otra observación importante que debo hacer es que este método de inicialización solo se llama una vez en el tiempo de duración del dominio de aplicación de nuestro programa. Por lo tanto, este es el único método del proveedor que no es necesario que sea seguro para múltiples subprocesos. También debemos asegurarnos de que llamamos al método de inicialización de la clase base, la cual también debe hacer sus ajustes. Y si debemos lanzar alguna excepción (algún parámetro erróneo o ausente), lo mejor es siempre que tratemos de usar excepciones específicas del espacio de nombres System.Configuration.Provider, o heredadas de éstas. Como en toda gestión de excepciones, cuanto más específico seamos, mejor. ¡Vamos a probarlo! Por fin tenemos nuestro proveedor completamente definido. Ahora es el momento de probarlo. Para ello debemos definirlo en Web. config, en concreto dentro de la sección específica para proveedores de mapas de sitios (listado 5). En este ejemplo hemos añadido dos instancias de nuestro proveedor. Una de ellas (ProveedorSistemaArchivos) deja los valores por defecto para los parámetros de funcionamiento y se convierte en el nuevo proveedor por defecto. La otra ("Admon") está parametrizada para generar un mapa sólo con los contenidos de la carpeta Administración de nuestro proyecto y para usar la frase "Home sweet home" para mostrar el nodo raíz. Una vez hecho esto, para probarlos vamos a agregar en una página dos controles de tipo SiteMapDataSource. Uno de ellos lo dejaremos tal cual se ha arrastrado y en el otro ajustaremos su propiedad SiteMapProvider con el valor "Admon", para que use el segundo proveedor que hemos definido. Añadiremos un control TreeView enlazado al primero y un control Menu al segundo, dándoles un aspecto bonito con las plantillas que incluyen. Si ahora ejecutamos la aplicación veremos como no hay problema alguno para utilizar ambas instancias del proveedor al mismo tiempo, e incluso podríamos utilizar también alguna del proveedor XmlSiteMapProvider que viene con ASP.NET 2.0.

Conclusión En este artículo hemos estudiado el modelo de proveedores de ASP.NET 2.0 y todas las ventajas que nos ofrece. Hemos visto los distintos subsistemas que están basados en proveedores, con sus correspondientes clases nativas. Finalmente, hemos desarrollado un ejemplo completo de un proveedor para mapas de sitios que trabaja generando directamente enlaces para los archivos del disco duro, evitándonos el tener que definir engorrosos archivos XML de navegación. Con todo esto, hemos sentado la base suficiente para poder crear cualquier proveedor propio o modificar alguno de los existentes heredando de las clases definidas en el sistema. Así podremos personalizar fácilmente el comportamiento de las API más importantes de ASP.NET 2.0.

blog comments powered by Disqus
autor
referencias