Blog dedicado a la programación .NET y la informática en general

Archivo para la Categoría "Programación .NET"

Ficheros de Configuración de Proyecto Adaptables

Seguro que muchos ya conocéis las ventajas que tienen los ficheros de configuración, en los proyectos web, donde los valores se adaptan a la configuración de compilación seleccionada.

Para el que no los conozca recomiendo que visiten la web de MSDN donde lo explican o también el vídeo tutorial en Channel9.

Pero ¿qué ocurre si queremos usarlo en proyectos que no sean web?. De principio, y sin hacer absolutamente nada, no podemos. Pero tenemos tres posibilidades para poder agregar dicha funcionalidad a nuestros proyectos.

Instalación de la Extensión de VS  Configuration Transform

Esta extensión os permitirá agregar ficheros de configuración adaptables al entorno para cualquier proyecto. Yo, desde luego, me decanto por esta opción.

image

Su uso es muy fácil e intuitivo, botón derecho del ratón sobre el fichero de configuración y hacer click en el menú correspondiente.

image

Y si, por ejemplo, añadís nuevos entornos de configuración bastará con volver a hacer “click” en el menú contextual para generar el fichero de configuración y asociarlo al proyecto.

Podéis descargarlo desde aquí.

Instalación del Paquete Nuget ConfigTransform

Este paquete Nuget permite extender el fichero de configuración en el proyecto donde esté instalado. Esta es una buena opción cuando no va a ser muy utilizado en vuestros proyectos y queréis dejar a Visual Studio lo más limpio posible de extensiones. Aunque tiene un PERO muy grande, no es absolutamente nada intuitivo de usar, y yo con el poco tiempo del que dispongo no lo he conseguido. Así que si alguien puede aportar más información será de agradecer.

image

Podéis acceder a su información en este enlace.

Añadirlo de forma manual

Esta es la parte menos sencilla, pero tampoco conlleva mucha complejidad. Se trata de editar el fichero de proyecto (.csproj o .vbproj) y agregarle algunos parámetros para que termine usando la misma librería que se usa en los proyectos web para el fichero “web.config” llamada “Microsoft.Web.Publishing.Tasks.dll”

Primero se debe buscar el nodo XML del fichero de proyecto llamado “ItemGroup” donde esté la entrada al fichero “App.config” y agregar los ficheros de configuración extensibles. Segundo des-comentar el nodo XML “Target” con el nombre “BeforeBuild” y dejarlo como lo pongo a continuación:

<ItemGroup>
    <None Include="App.config">
        <SubType>Designer</SubType>
    </None>
    <None Include="App.Debug.config">
        <DependentUpon>App.config</DependentUpon>
        <SubType>Designer</SubType>
    </None>
    <None Include="App.Release.config">
        <DependentUpon>App.config</DependentUpon>
        <SubType>Designer</SubType>
    </None>
</ItemGroup>
<!--...-->
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="BeforeBuild">
    <TransformXml Source="App.config" Destination="$(OutputPath)$(TargetFileName).config" Transform="App.$(Configuration).config" ContinueOnError="False" StackTrace="True" />
</Target>

Agregar los fichero de configuración extensibles en el mismo directorio donde está el fichero de configuración del proyecto, acordándose de incluir en el tag XML de “configuration” el elemento “xmlns:xdt” como pongo en el siguiente ejemplo:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
    <appSettings>
        <add key="Prueba01" value="TextoARelease" xdt:Transform="Replace" xdt:Locator="Match(key)"/>
    </appSettings>
</configuration>

Volver a cargar el proyecto en Visual Studio y estará listo.


image

Espero que os resulte útil.

Autenticación con Membership ASP.NET Identity

La autenticación Membership tradicional de ASP.NET tiene algunas limitaciones que ahora gracias a WIF (Windows Identity Foundation) se mejora. No voy a entrar en detalles técnicos y en definiciones ya que estoy introduciéndome en esta tecnología y todavía no estoy capacitado para ello.

Por tanto la intención de estos apuntes son los de mostrar un ejemplo de cómo integrar este tipo de autenticación en una aplicación web vacía, ya que las plantilla que VS2013 incluye traer otro módulos que no tienen por qué servirnos y por tanto es muy útil saber qué necesitamos para implementarlo desde cero sin depender de esta plantilla. Se da por supuesto que se tiene una base de datos SQL Server donde poder realizar las pruebas.

Lo primero es instalar mediante NuGet los siquitentes paquetes:

  • Microsoft ASP.NET Identity EntityFramework
  • Microsoft ASP.NET Identity Owin
  • Microsoft.Owin.Host.SystemWeb

Captura

El segundo paso es modificar el web.config. Para ello previamente debemos tener la cadena de conexión con la BBDD sobre la que se van a hacer las pruebas. Dicha base de datos no tiene porqué tener previamente las tablas de Membership generadas, sino que el mismo aplicativo cuando intenta acceder a éste las crea de forma automática.

Por tanto se agrega la cadena de conexión y se incluye el tag “sessionState” indicándole la cadena de conexión a la que debe conectarse.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!-- ... -->
<connectionStrings>
<add name="DefaultConnection" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=Investigando;Integrated Security=True"
providerName="System.Data.SqlClient" />
</connectionStrings>
<system.web>
<!-- ... -->
<sessionState mode="InProc" customProvider="DefaultSessionProvider">
<providers>
<add name="DefaultSessionProvider" type="System.Web.Providers.DefaultSessionStateProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="DefaultConnection" />
</providers>
</sessionState>
</system.web>
</configuration>

El tercer paso es agregar una clase que asocie la aplicación con el tipo de autenticación. Para ello hay que incluir en la raiz de la aplicación una clase llamada “Starup.cs”.

image

El código a incluir puede ser el siguiente:

using Microsoft.Owin;
using Owin;

namespace WebApplication2
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
}
}

Para que funcione también hay que incluir otra clase dentro de la carpeta “App_Start” de la aplicación web:

image

Y que contenta el siguiente código:

using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Owin;

namespace WebApplication2
{
public partial class Startup
{

// Para obtener más información sobre la configuración de la autenticación, visite http://go.microsoft.com/fwlink/?LinkId=301883
public void ConfigureAuth(IAppBuilder app)
{
// Habilitar la aplicación para que use una cookie para almacenar la información del usuario que inició sesión
// y almacenar también información acerca de un usuario que inicie sesión con un proveedor de inicio de sesión de un tercero.
// Es obligatorio si la aplicación permite a los usuarios iniciar sesión
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

// Quitar las marcas de comentario de las líneas siguientes para habilitar el inicio de sesión con proveedores de inicio de sesión de terceros
//app.UseMicrosoftAccountAuthentication(
// clientId: "",
// clientSecret: "");

//app.UseTwitterAuthentication(
// consumerKey: "",
// consumerSecret: "");

//app.UseFacebookAuthentication(
// appId: "",
// appSecret: "");

//app.UseGoogleAuthentication();
}
}
}

Se debe tener en cuenta que se ha cambiado el espacio de nombres y se le ha quitado “App_Start”. Y OJO con las rutas que se le están indicando, se supone que luego se van a crear para la autenticación, lo digo por este código –> LoginPath = new PathString(“/Account/Login”).

El cuarto paso va a consistir en comprobar que efectivamente podemos tener acceso a la base de datos y crear usuarios y roles. En este caso se crea una nueva clase para gestionar las identidades. Como se está usando EntityFramework mediante CodeFirst se generará un contexto de acceso, una clase para manejar los usuarios y un gestor de operaciones sobre los mismos. Este código lo podéis sacar de los proyectos plantilla de ASP.NET. El código para gestionar los roles los he obtenido en el siguiente enlace.

La clase quedaría de la siguiente forma:

image

using System;
using WebApplication2.Models;
using System.Collections.Generic;

namespace WebApplication2.Models
{
// Para agregar datos del usuario, agregue más propiedades a su clase de usuario. Visite http://go.microsoft.com/fwlink/?LinkID=317594 para obtener más información.
public class ApplicationUser : IdentityUser
{
}

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection")
{
}
}

public class UserManager : UserManager<ApplicationUser>
{
public UserManager()
: base(new UserStore<ApplicationUser>(new ApplicationDbContext()))
{
}
}

public class IdentityManager
{
public bool RoleExists(string name)
{
var rm = new RoleManager<IdentityRole>(
new RoleStore<IdentityRole>(new ApplicationDbContext()));
return rm.RoleExists(name);
}

public bool CreateRole(string name)
{
var rm = new RoleManager<IdentityRole>(
new RoleStore<IdentityRole>(new ApplicationDbContext()));
var idResult = rm.Create(new IdentityRole(name));
return idResult.Succeeded;
}

public bool CreateUser(ApplicationUser user, string password)
{
var um = new UserManager<ApplicationUser>(
new UserStore<ApplicationUser>(new ApplicationDbContext()));
var idResult = um.Create(user, password);
return idResult.Succeeded;
}

public bool AddUserToRole(string userId, string roleName)
{
var um = new UserManager<ApplicationUser>(
new UserStore<ApplicationUser>(new ApplicationDbContext()));
var idResult = um.AddToRole(userId, roleName);
return idResult.Succeeded;
}

public void ClearUserRoles(string userId)
{
var um = new UserManager<ApplicationUser>(
new UserStore<ApplicationUser>(new ApplicationDbContext()));
var user = um.FindById(userId);
var currentRoles = new List<IdentityUserRole>();
currentRoles.AddRange(user.Roles);
foreach (var role in currentRoles)
{
um.RemoveFromRole(userId, role.Role.Name);
}
}
}
}

namespace WebApplication1
{
public static class IdentityHelper
{
// Se utilizan para XSRF al vincular inicios de sesión externos
public const string XsrfKey = "XsrfId";

public static void SignIn(UserManager manager, ApplicationUser user, bool isPersistent)
{
IAuthenticationManager authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = manager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}

public const string ProviderNameKey = "providerName";
public static string GetProviderNameFromRequest(HttpRequest request)
{
return request[ProviderNameKey];
}

public static string GetExternalLoginRedirectUrl(string accountProvider)
{
return "/Account/RegisterExternalLogin?" + ProviderNameKey + "=" + accountProvider;
}

private static bool IsLocalUrl(string url)
{
return !string.IsNullOrEmpty(url) && ((url[0] == '/' && (url.Length == 1 || (url[1] != '/' && url[1] != '\\'))) || (url.Length > 1 && url[0] == '~' && url[1] == '/'));
}

public static void RedirectToReturnUrl(string returnUrl, HttpResponse response)
{
if (!String.IsNullOrEmpty(returnUrl) && IsLocalUrl(returnUrl))
{
response.Redirect(returnUrl);
}
else
{
response.Redirect("~/");
}
}
}
}

Aquí tengo que hacer hincapié en que dentro del código hay referencias a rutas de autenticación que van a ser desarrolladas supuestamente a posteriori y que no se incluye en este ejemplo.

Por último queda consumir esta última clase que se ha construido y comprobar que se han creado correctamente el usuario administrador y su rol correspondiente.

Se agrega en Global.asax.cs el siguiente código:

protected void Session_Start(object sender, EventArgs e)
{
// Comprueba que hay un rol de administrador y un usuario asociado
IdentityManager identMgr = new IdentityManager();
if (!identMgr.RoleExists(ROLADM))
{
identMgr.CreateRole(ROLADM);
var user = new ApplicationUser() { UserName = "Administrador" };
if (identMgr.CreateUser(user, "c1@v3~"))
{
identMgr.AddUserToRole(user.Id, ROLADM);
}
}
}

Para terminar comprobando que una vez que se lanza la aplicación se han creado las tablas de forma automática y se ha generado el usuario y rol correspondientes:

image

He realizado un video con todo el proceso completo, pero la calidad del mismo deja mucho que desear debido al escaso tiempo que le he podido dedicar. Pero como una imagen vale más que mil palabras os lo dejo a vuestra disposición, ya que no disponemos de mucha información en este sentido en español.

Ejemplo de implementación de Membership con ASP.NET Identity

 

Un saludo.

Código C# para comprobar permisos de un fichero

Si eres uno de los que está más que harto de no encontrar un código en condiciones para comprobar si un fichero tiene permisos de lectura y escritura entonces has llegado al sitio indicado.

private static bool TienePermisosUsuarioImpersonalizado(string rutaFisica)
{
bool resultado = false;

// Esta variable pretende evitar que salte la excepción de no tener permniso cuando se lanza File.Exists
bool existeRuta = false;

// Obtiene el usuario que lanza la aplicación en la máquina
WindowsIdentity identity = WindowsIdentity.GetCurrent();

try
{
// NOTA : RublenX - Se podría comprobar por FileIOPermission para comprobar File.Exists pero como da una excepción igualmente no se hace así
// Más info en : http://msdn.microsoft.com/es-es/library/vstudio/system.security.permissions.fileiopermission(v=vs.100).aspx
existeRuta = File.Exists(rutaFisica);
}
catch (SecurityException)
{
// Si no hay permisos ni siquiera para acceder al fichero entonces se da por excluido
existeRuta = false;
}

if (!string.IsNullOrEmpty(rutaFisica) && identity != null && existeRuta)
{
// Primero comprueba que el fichero no esté en modo sólo lectura
FileAttributes atributos = File.GetAttributes(rutaFisica);
if ((atributos & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
{
resultado = false;
}
else
{
// Ahora comprueba que el usuario logado tiene permisos de escritura y lectura
FileSecurity fSecurity = File.GetAccessControl(rutaFisica);
if (fSecurity != null)
{
AuthorizationRuleCollection rules = fSecurity.GetAccessRules(true, true, typeof(SecurityIdentifier));

// Recorre las reglas para todos los usuarios incluidos
foreach (AuthorizationRule rule in rules)
{
FileSystemAccessRule ruleConvertida = rule as FileSystemAccessRule;

if (ruleConvertida != null)
{
// NOTA : RublenX Tal vez se podía haber hecho con Environment.UserName, pero esta forma me gusta mucho más ya que es el usuario que
// ha lanzado la aplicación
if (ruleConvertida.IdentityReference.Value == identity.Owner.Value && ruleConvertida.AccessControlType == AccessControlType.Allow && (ruleConvertida.FileSystemRights & (FileSystemRights.Read | FileSystemRights.Write)) == (FileSystemRights.Read | FileSystemRights.Write))
{
// Una de sus reglas es del usuario que ha lanzado la aplicación con permisos de lectura y escritura
resultado = true;
break;
}
}
}
}
}
}
return resultado;
}

El código es capaz de comprobar si el usuario que lanza la aplicación es capaz de llegar primero al fichero, luego comprueba que no está en modo de sólo lectura, para terminar recorriendo los permisos de ese usuario y ver si son de lectura y escritura.

También puede pasar que necesitéis suplantar el usuario original que ha lanzado la aplicación por otro que tenga permisos de lectura y escritura. En ese caso la siguiente clase os puede ser muy útil:

using System;
using System.Runtime.InteropServices;
using System.Security.Principal;

namespace RublenX.PermisosDeAcceso
{
/// <summary>
/// Clase que permite suplantar el usuario identificado que ejecuta la aplicación en la máquina servidora
/// </summary>
public class SuplantarUsuarioIISBL : IDisposable
{
#region Librerías Externas
public const int LOGON32_LOGON_INTERACTIVE = 2;
public const int LOGON32_PROVIDER_DEFAULT = 0;

WindowsImpersonationContext impersonationContext;

[DllImport("advapi32.dll")]
public static extern int LogonUserA(String lpszUserName,
String lpszDomain,
String lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int DuplicateToken(IntPtr hToken,
int impersonationLevel,
ref IntPtr hNewToken);

[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool RevertToSelf();

[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern bool CloseHandle(IntPtr handle);
#endregion

#region Métodos Públicos
/// <summary>
/// Inicia la suplantación de usuario por el indicado en los parámetros
/// </summary>
/// <param name="userName">Usuario</param>
/// <param name="domain">Dominio de autenticación</param>
/// <param name="password">Contraseña</param>
/// <returns>True si consigue validar al usuario e impersonalizarlo</returns>
public bool ImpersonateValidUser(String userName, String domain, String password)
{
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(domain))
{
WindowsIdentity tempWindowsIdentity;
IntPtr token = IntPtr.Zero;
IntPtr tokenDuplicate = IntPtr.Zero;

if (RevertToSelf())
{
if (LogonUserA(userName, domain, password, LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT, ref token) != 0)
{
if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
{
tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
impersonationContext = tempWindowsIdentity.Impersonate();
if (impersonationContext != null)
{
CloseHandle(token);
CloseHandle(tokenDuplicate);
return true;
}
}
}
}
if (token != IntPtr.Zero)
CloseHandle(token);
if (tokenDuplicate != IntPtr.Zero)
CloseHandle(tokenDuplicate);
}
return false;
}

/// <summary>
/// Devuelve la suplantación del usuario al original
/// </summary>
public void UndoImpersonation()
{
if (impersonationContext != null)
{
impersonationContext.Undo();
impersonationContext.Dispose();
impersonationContext = null;
}
}
#endregion

#region Implementación de la interfaz
/// <summary>
/// Revierte la suplantación de identidad si es que se ha producido y libera todos los recursos utilizados
/// </summary>
public void Dispose()
{
UndoImpersonation();
}
#endregion
}
}

Y por tanto la forma correcta de invocarlo uniendo los dos sería de esta forma:

/// <summary>
/// Comprueba si el fichero tiene permisos de lectura y escritura para el usuario indicado
/// </summary>
/// <param name="rutaFisica">Ruta del fichero</param>
/// <param name="identity">Usuario del que se obtendrán los permiosos (por ejemplo el actual WindowsIdentity.GetCurrent())</param>
/// <returns>Verdadero si tiene los permisos de lectura y escritura</returns>
public static bool TienePermisosLecturaEscritura(string rutaFisica)
{
// Resultado de la validación
bool resultado = false;

// Datos del usuario a autenticar
string u = ConfigurationManager.AppSettings["UsuariosAppLocalAutorizados.LecturaEscrituraDiscoUsuario"];
string d = ConfigurationManager.AppSettings["UsuariosAppLocalAutorizados.LecturaEscrituraDiscoDominio"];
string p = ConfigurationManager.AppSettings["UsuariosAppLocalAutorizados.LecturaEscrituraDiscoPassword"];

using (SuplantarUsuarioIISBL suplantarUsuarioBL = new SuplantarUsuarioIISBL())
{
if (string.IsNullOrEmpty(u))
{
resultado = TienePermisosUsuarioImpersonalizado(rutaFisica);
}
else if (suplantarUsuarioBL.ImpersonateValidUser(u, d, p))
{
resultado = TienePermisosUsuarioImpersonalizado(rutaFisica);
}
else
{
// Personalizar la excepción como os venga en gana
throw new Exception("Error en la aplicación");
}
}

// Devuelve el resultaod de la validación de permisos
return resultado;
}

Espero que os guste.

Como hacer persistente la autenticación en ASP.NET mediante Cookies en Membership

Nota : Me he encontrado entre mis borradores este artículo que no publiqué en su momento, ya está desfasado pero quién sabe si le sirve a alguien por lo que lo publico ahora.

Puede daros el caso como el que yo tengo en el que vuestro proveedor de hosting limite el tiempo máximo de timeout de la sessión a un valor escaso para vuestro fin. Lo cual obliga a vuestros usuarios a tener que autenticarse cada vez que acceden a vuestra web. Para evitar esto y no depender del timeout de sesión podemos hacer persistente la autenticación mediante el uso de Cookies.

Las cookies son ficheros de texto de intercambio entre el explorador y el servidor que almacenan información relevante a la página web y de forma individual para cada usuario. Estos ficheros son almacenados en el equipo que usa el cliente y en algunos casos son objeto de fines malintencionados por lo que evitaremos guardar información confidencial y en nuestro caso almacenaremos un token o símbolo que identifique al usuario.

Cuando el usuario ha sido autenticado correctamente se crea la cookie a guardar en el cliente:

   1: // Crea el contenedor de autenticación
   2: FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
   3:                                                                     this.LoginUser.UserName,
   4:                                                                     DateTime.Now,
   5:                                                                     DateTime.Now.AddDays(Constantes.VariablesConfiguracion.AutenticationCookieDaysExpired),
   6:                                                                     true,
   7:                                                                     string.Empty);
   8:
   9: // Encripta the ticket de autenticación
  10: string encTicket = FormsAuthentication.Encrypt(ticket);
  11:
  12: // Crea la cookie
  13: HttpCookie galleta = new HttpCookie(Constantes.AutenticacionCookieName, encTicket);
  14: galleta.Expires = ticket.Expiration;
  15: Response.Cookies.Add(galleta);

Para recuperar la cookie almacenada seguiremos este otro código:

   1: MembershipUser usuarioAutenticado = Membership.GetUser();
   2:
   3: // Comprueba si tiene una cookie almacenada para restaurar la autenticación
   4: if (usuarioAutenticado == null)
   5: {
   6:     // Se comprueba si hay una cookie almacenada
   7:     HttpCookie myCookie = Request.Cookies[Constantes.AutenticacionCookieName];
   8:
   9:     // Lee la cookie y comprueba el usuario y si es válido lo da como autenticado
  10:     if (myCookie != null)
  11:     {
  12:         FormsAuthenticationTicket ticketRecupeado = FormsAuthentication.Decrypt(myCookie.Value);
  13:         string strUsuario = ticketRecupeado.Name;
  14:         MembershipUser usuarioCookie = Membership.GetUser(strUsuario);
  15:         if (usuarioCookie != null && usuarioCookie.IsApproved && !usuarioCookie.IsLockedOut)
  16:         {
  17:             FormsAuthentication.SetAuthCookie(usuarioCookie.UserName, true);
  18:             Response.Redirect(Request.Url.AbsoluteUri);
  19:         }
  20:     }
  21: }

Y eliminamos la cookie cuando el usuario cierra la sesión manualmente.

   1: // Desconecta al usuario activo y elimina sus cookies
   2: HttpCookie myCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
   3: if (myCookie != null)
   4: {
   5:     myCookie.Expires = DateTime.Now;
   6:     Response.Cookies.Set(myCookie);
   7: }

Espero que os funcione.

Como activar la página de errores de ASP.NET

Primero indicar que esto es una MALA PRÁCTICA, pero a veces se hace necesaria cuando no tienes acceso al servidor de hosting como te gustaría, en mi caso no puedo guardar un fichero log de errores, tampoco en base de datos y de hecho no llega ni a pasar por la página inicial, y de depurar en remoto ni hablamos.

La forma más fácil cuando no sabes el error que te da la aplicación es cambiar el web.config y activar la página web de errores que viene por defecto en ASP.NET.

Para ello poner lo siguiente:

<system.web>
<compilation debug="true" targetFramework="4.5">
<customErrors mode="Off" />
</system.web>

Una vez que tengáis el error volver a ponerlo como estuviera, como repito, es una MALA PRÁCTICA.

En mi caso ha sido porque el hosting ha incluido el Framework 4.5 y ahora las configuraciones del web.config son visibles también en los subdominios. Por lo cual la sección correspondiente a las EnterpriseLibrary se estaban pisando y al ser un error del fichero de configuración no pasaba ni tan siquiera por el Global.asax, así que no tenía forma de capturar el error y gracias a esta modificación que comento he podido comprobar el error y arreglarlo.

Detalles del error:

Tipo del Error: Configuration Error 

Descripción: An error occurred during the processing of a configuration file required to service this request. Please review the specific error details below and modify your configuration file appropriately.

Mensaje del Error: Section or group name ‘loggingConfiguration’ is already defined. Updates to this may only occur at the configuration level where it is defined.

Espero que no lo tengáis que utilizar.

Pasando de Visual SourceSafe a Team Foundation Service (antes TfsPreview)

Introducción

Prácticamente desde que empecé a programar en .NET (allá por el 2003) he usado como controlador de código para mis propios proyectos el Visual SourceSafe (VSS) desde su versión 6 hasta la última del 2005 (v.8). Principalmente porque podía llevarlo en un pendrive y tenerlo disponible allí donde iba. Eso no quita que profesionalmente haya usado también SVN o ClearCase, pero desarrollando con Microsoft uno tira por lo que le da de comer.

Desde la salida de VS2010 Microsoft ha puesto mucho interés en que usemos Team Foundation Server (TFS). El caso es que esto no me era viable debido a que no disponía de la portabilidad que me ofrece el “pendrive”, y me es imposible subirlo a un servidor donde ponerlo “online” para poder acceder desde cualquier sitio con internet.

Esto ahora ha cambiado gracias a la conferencia que hizo MadridDotNet el pasado día 23 de Octubre de 2012 y a una consulta que hice en Twitter a MSDN España y a la cual me respondieron. En la conferencia, entre otras muchas cosas, hablaron de TfsPreview.com (ahora Team Foundation Service, que no Server, no se si han elegido una buena nomenclatura seguro que alguno se hace el lio), un portal para poder disponer de un servidor TFS en la nube para tus proyectos. La versión “Free” por supuesto tiene sus limitaciones, no está disponible SharePoint ni Reporting, y tiene las mismas limitaciones que un TFS Express donde se pueden conectar hasta 5 desarrolladores a un proyecto. Sí dispone de número ilimitado de proyectos, control de versiones, trazado de tareas, herramientas de planificación para las metodologías ágiles, gestión de retroalimentación o retroinformación (llamémoslo mejor “Feedback”) y generación de ensamblados (nombrados como “Builds”). Ruego que me perdonéis mi mala traducción.

Tenéis más detalles de las características en su página web.

En este artículo veremos:

 

Creación de Cuenta

¿Por dónde empezar? Abriendo una nueva cuenta en la página web “tfs.visualstudio.com”. Para ello deberás tener una cuenta Live con la que autenticarte y administrarla.

Img. Creación de Cuenta

pantallazo

Al crear la cuenta te genera una colección por defecto llamada DefaultCollection de donde van a colgar el resto de tus proyectos. Por defecto te pedirá que generes una, ten cuidado con el nombre que le pones porque una vez puesto o lo dejas tal cual o lo borras, y esta segunda opción no es moco de pavo.

Img. Portal de Bienvenida

pantallazo2

Llegados a este punto he de decir que la gestión de los “proyectos de equipo” es bastante limitada en cuanto a renombrar o a eliminar. Se puede generar nuevos “proyectos de equipo” o gestionar los que ya tengas hechos, los grupos de usuarios y los roles de los mismos. Todo de forma fácil y básica y bajo la rama principal llamada “DefaultCollection”.

Img. Configuración de Cuenta

pantallazo3

Conexión con Visual Studio

La conexión de Visual Studio (VS) con el TFS Online es muy sencilla siempre y cuando no tengáis un proxy de por medio.

Hay que destacar que soporta conexión con las versiones 2008, 2010 y 2012. No lo he comprobado con versiones anteriores. Se que con la versión 2012 no hay ningún tipo de problema conocido, pero recomiendo la actualización del mismo. Con la versión 2010 debe tener instalado el SP1 y el (Hotfix KB2581206 este ya no vale) Hotfix KB2662296. Para VS2008 también se debe tener instalado una actualización de distribución general.

Img. Conexión VS con TFS

pantallazo4

Para VS2012 la ruta de menús es la siguiente:

EQUIPO –> Conectar con Team Foundation Server –> Servidores… –> Agregar

No voy a profundizar en el manejo de TFS, pero prácticamente se puede hacer todo desde las herramientas de “Team Explorer”, la ventana de “Explorador de control de código fuente” y desde el menú “Equipo”.

Corrección de problemas de conexión

Entramos en terreno pantanoso, los problemas de conexión en red que usan proxy y las credenciales de conexión. En este caso lo he solucionado para VS2010, pero probablemente pase lo mismo para VS2012.

Primer Paso : Adaptar Visual Studio para que tome las credenciales del proxy

La solución la he obtenido de este enlace. El cual explica como generar una librería que hay que poner en la carpeta de Visual Studio y cómo hay que cambiar su fichero de configuración para que tome dicha librería, la cual va a proporcionar los identificadores necesarios para la conexión proxy.

Yo además le he incluido el código necesario para que dichas credenciales las pueda tomar de su propio fichero de configuración:

Img. Configuración Librería

pantallazo5

Código Fuente Clase Credenciales Proxy Configurables
public class ModuloAutenticacion : IWebProxy
{
    ICredentials crendential = new NetworkCredential(Configuracion.AppSettings.Settings["ProxyUser"].Value, Configuracion.AppSettings.Settings["ProxyPassword"].Value);
    public ICredentials Credentials
    {
        get { return crendential; }
        set { crendential = value; }
    }
    public Uri GetProxy(Uri destination)
    {
        return new Uri(Configuracion.AppSettings.Settings["ProxyUrl"].Value, UriKind.Absolute);
    }
    public bool IsBypassed(Uri host)
    {
        return host.IsLoopback;
    }
    /// <summary>       
    /// Obtiene la configuración de la aplicación        
    /// </summary>       
    public static Configuration Configuracion
    {
        get
        {
            return ConfigurationManager.OpenExeConfiguration(Assembly.GetExecutingAssembly().GetName().CodeBase.Replace("file:///", string.Empty));
        }
    }
}

La librería resultante y su fichero de configuración hay que ponerlos en el directorio de Visual Studio. Hecho esto se cambia el fichero de configuración de VS incrustando un código parecido a este:

Código XML Configuración VS
<system.net>
  <defaultProxy>
    <module type="RublenX.AutenticacionProxy.ModuloAutenticacion, RublenX.AutenticacionProxy"/>
  </defaultProxy>
</system.net>

Ojo, aquí no se resuelven todos los problemas, al menos en mi caso.

Segundo Paso : Configuración de las Credenciales de la Web

Llegados a este punto el servidor no me daba errores de conexión por las credenciales proxy, este vez era por la autenticación contra “tfs.visualstudio.com”. ¿Qué hacer entonces?. Pues cambiar las credenciales desde el “Panel de Control”.

Img. Acceso al Administrador de Credenciales


pantallazo6

Incluir una nueva entrada con la dirección web de “tfs.visualstudio.com” y vuestras credenciales de “Window Live”. No hace falta que lo explique ¿verdad?. Bueno dicen que una imagen vale más que mil palabras.

Img. Configuración Credenciales (Recordar que ya no es “tfspreview” sino “visualstudio”)


Credenciales

Traspaso de los ficheros de VSS a TFS

En este caso el equipo de foro de MSDN en español fueron tan atentos, como suelen tenernos acostumbrados, y contestaron a este planteamiento: ¿Cómo traspaso mis proyectos de VSS a TFService?.

La solución la tenemos descargando la herramienta “Visual Source Safe Upgrade Tool for Team Foundation Server” o con el nombre con el que se instala en el equipo “VSS Upgrade Wizard”.

Recomiendo tener el “SQLServer Express” instalado en el equipo ya que se va a necesitar como almacenamiento temporal durante la exportación a TFS. Y también que ejecutéis la herramienta de “SourceSafe” llamada “Analyze” para evitar que los problemas que pueda tener la BBDD de VSS pueda dar problemas en la migración de los datos. Por ejemplo mediante la siguiente línea de comando (cambiando claro está la ruta que he puesto por la de vuestro directorio):

analyze -C -D -F -V4 J:\PROGRAMACION\VSSBBDD\DATA

Mecanismo de un chupete… arrancamos el programa y seguimos estos pasos:

Img. Selección del Directorio Fuente de VSS


pantallazo7

Img. Selección del Servicio TFS


pantallazo8

Img. Opciones de Exportación y SQL Server


pantallazo9

Img. Resumen de Configuración


pantallazo10

Img. Validación de Configuración


pantallazo11

Img. Proceso de Análisis y Subida


pantallazo12

Si todo ha ido bien os aparecerá las siguiente pantallas.

Img. Migración Finalizada


pantallazo13

Img. Resultado de Migración


pantallazo16

Si has llegado hasta aquí tendrás tu copia de VSS subida a TFS, pero también te puede pasar como a mí que la migración de algún tipo de problema:

Img. Migración con Errores


pantallazo14

Img. Detalle de Errores


pantallazo15

Podéis comprobar los errores en el fichero XML que genera el programa en su carpeta Logs:

C:\ProgramData\Microsoft\Team Foundation\Server Configuration\Logs

Abriéndolo con el “XML Editor” que instala Office lo podréis leer sin problemas, pero sino ir a la sección “Issues” y podéis hacer una búsqueda por Type=”Error” o por Type=”Warning”.

En mi caso los 4 errores se han producido por un fichero con una nomenclatura antigua de DOS (si, si, ese sistema operativo que su interfaz gráfica era todo texto) y que he podido agregar manualmente al TFS posteriormente.

Los avisos o “warnings” me dan cuando el fichero fue creado en otra rama diferente de la que esté ubicado en ese momento y su fecha de creación se queda por tanto en el momento en el que se pasó a esa rama. Y el resto de los avisos se suceden prácticamente por mover ficheros en SourceSafe de una carpeta a otra.

Sea como sea se guarda el registro de cambios y se puede hacer el seguimiento de los mismos.

Img. Historia Fichero Importado


pantallazo17

Eliminación de un Proyecto de Equipo

Si os ha pasado como a mí, el primer proyecto de equipo le ponéis cualquier nombre pensando ingenuamente que luego se lo podréis cambiar…. pues NO.

Para borrar un Equipo de proyecto, ejecutar en modo administrador el icono de Visual Studio “Símbolo del sistema de las herramientas nativas de VS2012 x86”, y una vez abierta la ventana de comandos se ejecutaría la siguiente orden, cambiando por supuesto “tuproyecto” y “Nombre Proyecto” por los correspondientes:

TFSDeleteProject /collection:”https://tuproyecto.visualstudio.com/DefaultCollection” “Nombre Proyecto”

Img. Resultado de la Ejecución de Eliminación


image

Probé a ejecutarlo en otro acceso director de “Símbolo de Comandos” que tiene Visual Studio y no funcionó, así que si no os va en uno probar en otro, yo os he puesto donde sí me ha funcionado.

Conclusión

Estoy mucho más que satisfecho con la decisión de pasarme a TFS, mis datos ahora no sólo están accesibles desde cualquier sitio con conexión a internet sino que también están seguros, ya no los llevo encima en un dispositivo que podía perderse o estropearse. Y si nos ponemos hablar de las ventajas de TFS frente a VSS entonces este artículo sería ya mucho más largo de lo que ya lo es de por sí.

Cualquier duda o aclaración estoy a vuestra entera disposición.

Saludos.

Usar compresión SevenZipSharp en .NET

Tengo un programa para hacer las publicaciones de mis proyectos, este tiene la opción de hacer una copia de seguridad de los ficheros modificados, pero no los comprime.

Hoy voy a explicar cómo usar en .NET el que para mí es el mejor compresor de ficheros, 7zip, el cual además de tener mejor resultados en compresión también es el más rápido.

Introducción a la compresión en .NET

Inicialmente desde la versión 2.0 de Framework, Microsoft nos ofrecía en la librería System.IO.Compression las clases para comprimir DeflateStream y GZipStream.

Las diferencia fundamental entre estas clases radica en que GZipStream originalmente usa el algoritmo de DeflateStream pero se puede extender para que utilice otros formatos de compresión.

Hay un montón de ejemplos en internet, el problema fundamental es a la hora de comprimir una carpeta entera, así que os dejo el ejemplo más completo que he encontrado aquí.

La mayoría de la gente usa otras librerías de terceros como DotNetZip o también SharpZipLib, que son mucho más fácil de manejar y de la que hay también mucha información en Internet.

Pero como bien he dicho al principio de este artículo yo me quedo con el algoritmo LMZA que usa 7zip.

Paquete de Desarrollo de Software LMZA

El creador de 7zi,p Igor Pavlov, muy amablemente nos ha dejado un SDK de LMZA donde tiene un ejemplo para comprimir 1 sólo archivo. Con lo cual no hacemos mucho.

Buscando por Internet nos encontramos con nuestros amigos de CodePlex, que comparten con todos nosotros un paquete de código abierto llamado SevenZipSharp. Esta va a ser la librería que usemos en este artículo. Y la cual nos facilitará la vida para la compresión y descompresión. Por tanto nos descargamos su librería.

Ejemplo

El código que se va a mostrar consta de una solución hecha con VS2010 y un proyecto Windows Forms, tal y como se muestra en la siguiente imagen:

ExploradorSolucion

Consta de un formulario Windows y la librería 7z.dll de la cual hablaremos más adelante. Además se agregará la referencia a la librería de SevenZipSharp.

El formulario tiene la siguiente pinta:

Programa

El formulario básicamente toma un directorio del cuadro de texto y lo comprime con el mismo nombre del directorio y con la extensión 7z.

Para ello he quiero añadirle una barra de progreso que nos indique lo que queda por comprimir, ya que la librería facilita este uso.

Por tanto nos vemos en la necesidad de usar la clase BackgroundWorker, donde no me voy a extender mucho en su explicación.

Por tanto cuando se presiona el botón “Comprimir”, iniciaremos el control de ejecución en segundo plano para controlar el avance de la compresión.

private void button2_Click(object sender, EventArgs e)
{
    if (Directory.Exists(this.textBox1.Text))
    {
        progressBar1.Value = 0;
        object[] parametros = new object[] { this.textBox1.Text };
        backgroundWorker1.RunWorkerAsync(parametros);
    }
    else
    {
        MessageBox.Show("El directorio no existe");
    }
}

El componente BackgroundWorker iniciará la compresión:

private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
    string ficheroSalida = string.Format("{0}.7z", (string)((object[])e.Argument)[0]);
    try
    {
        comp.CompressDirectory((string)((object[])e.Argument)[0], ficheroSalida, true);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

El objeto “comp” es el objeto de la clase SevenZipCompressor que se ha inicializado en el constructor de la clase:

public FormPrueba()
{
    InitializeComponent();
    // Para que funcione correctamente se debe indicar la ruta a la librería 7z.dll, 
    // aqui lo que hago es incluirla en el proyecto y al estar en el mismo directorio no hace falta decírselo
    //string rutaLib = string.Format("{0}\\7z.dll", Application.StartupPath);
    //SevenZipCompressor.SetLibraryPath(rutaLib);
    comp = new SevenZipCompressor();
    comp.CompressionFinished += new EventHandler<EventArgs>(comp_CompressionFinished);
    comp.Compressing += new EventHandler<ProgressEventArgs>(comp_Compressing);
}

Además se están controlando los eventos de finalización y compresión de la clase SevenZipCompressor. De tal forma que al finalizar el proceso mostraremos al usuario un mensaje diciendo que ha finalizado. Y a medida que se van comprimiendo los ficheros usaremos la barra de progreso del formulario con el siguiente código:

void comp_Compressing(object sender, ProgressEventArgs e)
{
    try
    {
        backgroundWorker1.ReportProgress(e.PercentDone);
    }
    catch (Exception ex)
    {
        string parada = ex.Message;
    }
}

private void backgroundWorker1_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
    this.progressBar1.Value = e.ProgressPercentage;
}

Lo único que cabe destacar es la inclusión de la librería de 7zip dentro del proyecto. Esto se debe a que de otra forma tenemos que indicar a la librería SevenZip la ruta de la misma, y de esta forma la encuentra directamente en el directorio de compilación. Para ello recordar que cuando se incluya la librería en el proyecto debes indicarle que se copie en el directorio de resultados.


PropiedadesDll

También tener en cuenta si la versión de instalación de 7zip en vuestro equipo, ya que yo tengo la de 64bit y esto al combinarlo con el proyecto da un error, para ello he tenido que usar la librería de 32bit que os podéis descargar también desde la web.

La solución, por supuesto comprimida con 7zip, la podéis descargar desde aquí el fichero con el nombre “2010WinForm_ComprimiendoFicherosSolution.7z”.

Espero que os sirva de ayuda.

A %d blogueros les gusta esto: