Accediendo a Active Directory con System.DirectoryServices.AccountManagement

En el Microsoft Framework .NET 3.5 se ha incluido el namespace System.DirectoryServices.AccountManagement que nos permite acceder de una manera más transparente a grupos y usuarios de un Active Directory que con las clases DirectoryEntry y DirectorySearcher. Para ello contamos con las clases UserPrincipal y GroupPrincipal, que hereda de la clase abstracta Principal y nos permitirán crear nuestras plantillas de búsqueda. Después ejecutaremos nuestras peticiones contra el Active Directory a través de la clase PrincipalSearcher, con sus métodos FindOne() si queremos obtener solo un resultado o FindAll() si queremos obtener una collección de resultados en un objeto de tipo PrincipalSeachResult.

El primer paso será inicializar un PrincipalContext que será nuestra conexión contra el Active Directory:

PrincipalContext context = new PrincipalContext(ContextType.Domain, «plainconcepts», «DC=plainconcepts,DC=com», «User», «Password»);

Y ahora vamos a hacer una búsqueda sobre un usuario del Active Directory cuyo correo electrónico sea vicente.garcia@plainconcepts.com y vamos a cambiarle su propiedad Description. Lo primero, deberemos inicializar un objecto de la clase UserPrincipal con su propiedad EmailAddress a “vicente.garcia@plainconcepts.com” y, después, ejecutar esa búsqueda a través del método FindOne() del objeto PrincipalSearcher:

UserPrincipal userQuery = new UserPrincipal(context);

userQuery.EmailAddress = «vicente.garcia@plainconcepts.com»;

PrincipalSearcher principalSearcher = new PrincipalSearcher(userQuery);

UserPrincipal result = principalSearcher.FindOne() as UserPrincipal;

Ahora solo queda modificar la propiedad Description y llamar al método Save() del objeto de tipo UserPrincipal:

result.Description = «Development Advisor»;

result.Save();

 

Para utilizar el método FindAll() haremos una búsqueda por todos los elementos cuya propiedad EmailAddress pertenezca al dominio plainconcepts.com e imprimiremos su nombre por salida estándar:

PrincipalContext context = new PrincipalContext(ContextType.Domain, «plainconcepts», «DC=plainconcepts,DC=com», «User», «Password»);

UserPrincipal userQuery = new UserPrincipal(context);

userQuery.EmailAddress = «*@plainconcepts.com»;

PrincipalSearcher principalSearcher = new PrincipalSearcher(userQuery);

PrincipalSearchResult<Principal> result = principalSearcher.FindAll();

foreach (Principal item in result)

{

      Console.WriteLine(((UserPrincipal)item).Name);

}

 

La única consideración que hay que tener es que los métodos FindAll() y FindOne() devuelven siempre o colección de objetos de clase Principal o un objeto de tipo Principal, por lo que será necesario castearlo.

Para recuperar un grupo o grupos de un Active Directory con la funcionalidad que este nuevo namespace nos provee debemos operar de una manera muy similar. Por ejemplo, vamos a recuperar todos los grupos cuya propiedad Name comience por la letra P e imprimiermos su propiedad Description:

PrincipalContext context = new PrincipalContext(ContextType.Domain, «plainconcepts», «DC=plainconcepts,DC=com», «User», «Password»);

GroupPrincipal groupQuery = new GroupPrincipal(context);

groupQuery.Name = «P*»;

PrincipalSearcher principalSearcher = new PrincipalSearcher(groupQuery);

PrincipalSearchResult<Principal> result = principalSearcher.FindAll();

foreach (Principal item in result)

{

     Console.WriteLine(((GroupPrincipal)item).Description);

}

Manejador global de excepciones en ADO.NET Data Services

Si tenemos la necesidad de incorporar a nuestro servicio WCF un manejador global de excepciones para capturar y tratar las excepciones que no manejamos en nuestros bloques try-catch, podemos seguir los siguientes pasos que se indican en este post.

Pero si nuestra necesidad es el incluir un manejador global de excepciones en un servicio WCF ADO.NET Data Services, no nos servirá esta solución, ya que solo se capturarán las excepciones que se generen en el método de inicialización del servicio InitializeService, ni las excepciones no manejadas en nuestos ServiceOperation ni en los ChangeInterceptor serán capturadas.

Para tal propósito, tenemos a nuestra disposición el método virtual HandleException, que recibe un solo parámetro de tipo HandleExceptionArgs, en el que tenemos toda la información disponible sobre la excepción que se acaba de producir y, además, nuestra ya conocida propiedad booleana UseVerboseErrors. Hay que recordar que el valor de esta propiedad solo influye en la información que va a ser mostrada en el error de la request, la información que va a ser escrita a log será siempre la misma.

Por poner un ejemplo en código, trataremos las excepciones no capturadas por nuestros boques try – catch traceándolas al log del aplicación. Básicamente, tenemos un ServiceOperation que, al ser llamado, provoca una excepción no manejada. Si colocamos un punto de interrupción en el método HandleException, veremos cómo ese método se ejecuta y nuestra excepción no tratada se tracea:

namespace Plainconcepts.Server

{

    [ServiceBehavior(IncludeExceptionDetailInFaults = true)]

    public class PlainDataService : DataService<PlainEntities>

    {

       public static void InitializeService(DataServiceConfiguration config)

        {

            config.SetEntitySetAccessRule("*", EntitySetRights.All);

            config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);

            config.UseVerboseErrors = true;

        }

 

        [WebGet()]

        public IQueryable<Master> GetMasterById(int id)

        {

            throw new InvalidOperationException();

            return this.CurrentDataSource.Master.Where(m => m.IdMaster == id);

        }

 

       protected override void HandleException(HandleExceptionArgs args)

       {

          if (!EventLog.SourceExists("Operations"))

                 EventLog.CreateEventSource("Operations", "Application");

 

         EventLog.WriteEntry("Operations", args.Exception.ToString());

        }

    }

}

[How-to] Información de excepciones en ADO.NET Data Services

Por defecto, al crear un servicio ADO.NET Data Sevices, no tenemos información de errores y, si se lanza alguna excepción o hemos cometido algún fallo, obtendremos un mensaje con tan poca información como este:

First

Por lo que no tenemos ni idea de lo que está pasando con nuestro servicio. Realmente, nuestro error ha sido el siguiente:

Second

No hemos introducido el nombre del EntitySet al que queremos asignarle la regla de acceso correspondiente y no hemos introducido el nombre de la operación de servicio a la que queremos asignarle los derechos correspondiente.

 

Bien, podemos obtener toda la información de las anomalías que se están produciendo añadiendo a nuestro servicio dos líneas de código:

Third

El atributo ServiceBehavior lleva en el Framework .NET desde la versión 3.0 en la que se incluyó WCF y nos provee de la capacidad de especificar el comportamiento de la implementación del contrato. Con la propiedad booleana IncludeExceptionDetailInFaults podemos decidir si las excepciones no manejadas del servicio son devueltas como errores SOAP.

Este atributo puede decorar cualquier servicio WCF en general, aunque también podemos indicarlo en nuestro fichero de configuración de la siguiente manera:

<system.serviceModel>
    <services>
      <service name=»PlainConcepts.Server.PlainService» behaviorConfiguration=»Debug» />
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name=»Debug»>
          <serviceDebug includeExceptionDetailInFaults=»true»/>
        </behavior>
      </serviceBehaviors>
     </behaviors>
    <serviceHostingEnvironment aspNetCompatibilityEnabled=»true»/>
</system.serviceModel>

 

La otra línea que hemos incluido tiene que ver con el objeto de la clase DataServiceConfiguration (en versiones anteriores, de la interfaz IDataServiceConfiguration), que provee de la propiedad booleana UseVerboseErrors, a través de la cual podemos indicar a nuestro servicio que el comportamiento por defecto para todos los errores de sus respuestas sean detallados. La interfaz IDataServiceConfiguration se ha incluido desde el Framework .NET versión 3.5 SP1.

Bien, con estas modificaciones, ahora obtendremos el siguiente error para la misma configuración errónea de nuestro servicio:

The server encountered an error processing the request. The exception message is 'Exception has been thrown by the target of an invocation.'. See server logs for more details. The exception stack trace is:
   at System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
   at System.RuntimeMethodHandle.InvokeMethodFast(Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
   at System.Data.Services.DataServiceConfiguration.InvokeStaticInitialization(Type type)
   at System.Data.Services.DataServiceConfiguration.Initialize(Type type)
   at System.Data.Services.DataService`1.CreateConfiguration(Type dataServiceType, IDataServiceMetadataProvider provider)
   at System.Data.Services.DataService`1.CreateProvider()
   at System.Data.Services.DataService`1.EnsureProviderAndConfigForRequest()
   at System.Data.Services.DataService`1.HandleRequest()
   at System.Data.Services.DataService`1.ProcessRequestForMessage(Stream messageBody)
   at SyncInvokeProcessRequestForMessage(Object , Object[] , Object[] )
   at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)
   at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)

Siempre recordar que este tipo de configuraciones son con el propósito de obtener más información de las excepciones que provoca nuestro servicio, por lo que es recomendable eliminar estas ayudas en entorno de producción.

Decir también que el comportamiento es equivalente cuando se consume el servicio a través de la librería de cliente System.Data.Services.Client o, incluso, cuando se incluye la referencia al servicio en el cliente para la generación del proxy, ya que si no tenemos esta ayuda, el servicio fallará y la información adicional que obtendremos será “The server encountered an error processing the request. See server logs for more details.”