2012-01-04 20 views
6

Antecedentes

de error intermitente extraño Tenemos una aplicación asp.net 4.0 web escrito en C# .NET que llama a un servicio Web 3.5 escrito en C#. El servicio web recibe una identificación de usuario y devuelve una lista de datos en función de los grupos de directorios activos a los que pertenece el usuario.manejo de errores al llamar UserPrinciapl.GetGroups en System.DirectoryServices.AccountManagement

El servicio web .NET utiliza la versión 3.5 de System.DirectoryServices.AccountManagement para obtener los SID de los grupos pertenece el usuario.

La llamada a UserPrincipal.GetGroups falla intermitentemente con el error abajo. Hubo periodos de tiempo muy largos entre las ocurrencias, pero cuando ocurrió, ocurrió repetidamente durante varios minutos. El problema ocurrió para diferentes usuarios de AD.

El seguimiento de pila de esta excepción no tenía sentido para nosotros. Pasamos mucho tiempo mirando el código Microsoft AD en Reflector/ILSpy pero no pudimos ir más allá de la llamada a IADsPathName.Retrieve.

Excepción

System.NotSupportedException: Specified method is not supported. 
at System.Web.HttpResponseStream.get_Position() 
at System.Drawing.UnsafeNativeMethods.ComStreamFromDataStream.Seek(Int64 offset, Int32 origin) 
at System.DirectoryServices.AccountManagement.UnsafeNativeMethods.IADsPathname.Retrieve(Int32 lnFormatType) 
at System.DirectoryServices.AccountManagement.ADStoreCtx.LoadDomainInfo() 
at System.DirectoryServices.AccountManagement.ADStoreCtx.get_DnsForestName() 
at System.DirectoryServices.AccountManagement.ADStoreCtx.GetGroupsMemberOf(Principal p) 
at System.DirectoryServices.AccountManagement.Principal.GetGroupsHelper() 
at System.DirectoryServices.AccountManagement.Principal.GetGroups() 
at Data.SoftwarePublishingItemData.GetSids(String requestedForUserId) 
at Data.SoftwarePublishingItemData.GetSoftwarePublishingItems(IDatabaseContext dbContext, GetSoftwarePublishingItemsSettings settings, XBXmlDocument parameters) 
at Web.GetSoftwarePublishingItems.GetFlexiFieldData(String xml) 

Código para reproducir

Tenga en cuenta, el método CauseNotSupportedException está imitando código que no se está ejecutando en nuestra aplicación, pero en el código en otro lugar en el ambiente que no estamos en control de.

class Program 
{ 
    static void Main(string[] args) 
    { 
     CauseNotSupportedException(); 

     string samAccountName = "domain.user"; 

     using (var principalContext = new PrincipalContext(ContextType.Domain)) 
     { 
      using (var userPrincipal = UserPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, samAccountName)) 
      { 
       if (userPrincipal == null) 
        throw new ActiveDirectoryObjectNotFoundException(); 

       using (var groups = userPrincipal.GetGroups()) 
       { 
        foreach (GroupPrincipal group in groups) 
        { 
         Console.WriteLine(group.Sid); 
        } 
       } 
      } 
     } 
    } 

    public static void CauseNotSupportedException() 
    { 
     using (var b = new Bitmap(500, 500, PixelFormat.Format32bppArgb)) 
     { 
      b.Save(new FakeStream(), ImageFormat.Png); 
     } 
    } 
} 

Implementación de corriente para imitar HttpResponseStream comportamiento

public class FakeStream : Stream 
{ 
    public override bool CanRead { get { return false; } } 
    public override bool CanSeek { get { return false; } } 
    public override bool CanWrite { get { return true; } } 

    public override void Flush() { } 

    public override long Length { get { throw new NotSupportedException("No Seek"); } } 

    public override long Position 
    { 
     get { throw new NotSupportedException("No Seek"); } 
     set { throw new NotSupportedException("No Seek"); } 
    } 

    public override int Read(byte[] buffer, int offset, int count) 
    { 
     throw new InvalidOperationException("Write only stream"); 
    } 

    public override long Seek(long offset, SeekOrigin origin) 
    { 
     throw new NotSupportedException("net_noseek"); 
    } 

    public override void SetLength(long value) { } 

    public override void Write(byte[] buffer, int offset, int count) { } 
} 

Preguntas

  1. Si ejecuta el ejemplo anterior, el error que se produce en el método CauseNotSupportedException se lanza en el llame a GetGroups. ¿Como puede ser? Se agradecerá cualquier teoría o visión adicional.
  2. ¿Alguna sugerencia sobre cómo investigar más?
  3. mejor sugerencias que captura la excepción y volver a intentar? Ese es nuestro trabajo actual alrededor.

Gracias.

Aclaración

No estoy seguro de cómo está claro que he estado en mi explicación así que aquí tiene algunas aclaraciones. En primer lugar, estoy contento con el código de directorio activo que obtiene el Sids. Eso hace lo que quiero que haga y no creo que el problema sea con eso como tal. El verdadero problema es que cuando se produce un error en otro código no relacionado (que no está en nuestra aplicación) el error se manifiesta en las GetGroups llaman, por lo tanto, el seguimiento de pila extraña con el error que se produce inicialmente en System.Web.HttpResponseStream.get_Position(). En la aplicación de muestra NotSupportedException ocurre en CauseNotSupportedException pero el código no se rompe allí, se rompe en la llamada a GetGroups. Si comenta CauseNotSupportedException() en la aplicación de muestra, el error nunca ocurre.

Es claro para mí cómo esto puede suceder.

+0

Pregunta, ¿está tratando de averiguar si los usuarios pertenecen a un grupo en particular o no? Estoy tratando de obtener una mejor comprensión de lo que realmente está buscando. Es posible que tenga una sugerencia sobre un mejor enfoque con respecto a codificación ... aquí hay un enlace que también ayudará a pegar 2 fragmentos de código a continuación que deberían ayudarlo a obtener lo que necesita con mayor facilidad. http://msdn.microsoft.com/en-us/magazine/cc135979.aspx – MethodMan

+0

Muchas gracias por la respuesta. Lo que necesito es una lista de Sids de los grupos de los que un usuario es miembro. Actualmente estoy usando el espacio de nombres System.DirectoryServices.AccountManagement (UserPrincipal.GetGroups está en ese espacio de nombres). Entonces, mi muestra de código es muy similar a la que publicaste a continuación. El código que obtengo es lo que quiero, es solo el error intermitente que se produce por un código no relacionado que intento entender. Si tiene tiempo, valdría la pena ejecutar el código de muestra y recorrerlo. –

+0

ok no es un problema ... fue difícil determinar al principio qué era lo que deseabas ... mis disculpas aún necesitarías verificar esa propiedad o las (cadenas) Propiedades ["samAccountName"] [0] .ToString() porque esto Sería un Objeto en este momento ... Espero que esto tenga sentido. – MethodMan

Respuesta

4

Después de generar una llamada de soporte, Microsoft ha emitido una solución para este problema. Ver el enlace a continuación.

La causa declarada es:. "Este problema se produce debido a que el espacio de nombres System.DirectoryServices.AccountManagement es una envoltura delgada para la API nativa de interfaces de servicio de Active Directory (ADSI) La interfaz IErrorInfo que se implementa mediante la interfaz responde a IADsPathName excepciones que ADSI no arroja. Cuando no hay una excepción ADSI en la pila, la interfaz IErrorInfo arroja la excepción que está en la parte superior de la pila, incluso si la excepción es manejada por otro controlador en la aplicación ".

http://support.microsoft.com/kb/2683913

Gracias a los que ofreció sugerencias.

0

Si utiliza .NET 3.5 o superior, puede usar el nuevo espacio de nombres System.DirectoryServices.AccountManagement (S.DS.AM), que hace que esto sea mucho más fácil de lo que solía ser.

leer todos los detalles aquí: [Los directores gerente de seguridad para directorio en el .NET Framework 3.5] [1]

Básicamente, es necesario tener un "contexto principal" (por lo general su dominio), un principal de usuario, y luego se obtiene muy fácilmente sus grupos:

public List<GroupPrincipal> GetGroups(string userName) 
{ 
    List<GroupPrincipal> result = new List<GroupPrincipal>(); 

    // establish domain context 
    PrincipalContext yourDomain = new PrincipalContext(ContextType.Domain); 

    // find your user 
    UserPrincipal user = UserPrincipal.FindByIdentity(yourDomain, username); 

    // if found - grab its groups 
    if(user != null) 
    { 
     PrincipalSearchResult<Principal> groups = user.GetAuthorizationGroups(); 

     // iterate over all groups 
     foreach(Principal p in groups) 
     { 
     // make sure to add only group principals or change this to add to a list or varible if needed. 
     if(p is GroupPrincipal) 
     { 
      result.Add(p); 
     } 
     } 
    } 

    return result; 
} 

con el fin de acceder a ciertas propiedades, que no se salieron a la superficie en el objeto UserPrincipal, es necesario profundizar en la DirectoryEntry subyacente:

public string GetDepartment(Principal principal) 
{ 
    string result = string.Empty; 

    DirectoryEntry de = (principal.GetUnderlyingObject() as DirectoryEntry); 

    if (de != null) 
    { 
     if (de.Properties.Contains("samAccountName")) 
     { 
      result = de.Properties["samAccountName"][0].ToString(); 
     } 
    } 

    return result; 
} 

//Change this Method to fit what ever your needs desire.. 
public string GetDepartment(string username) 
{ 
    string result = string.Empty; 

    // if you do repeated domain access, you might want to do this *once* outside this method, 
    // and pass it in as a second parameter! 
    PrincipalContext yourDomain = new PrincipalContext(ContextType.Domain); 

    // find the user 
    UserPrincipal user = UserPrincipal.FindByIdentity(yourDomain, username); 

    // if user is found 
    if(user != null) 
    { 
     // get DirectoryEntry underlying it 
     DirectoryEntry de = (user.GetUnderlyingObject() as DirectoryEntry); 

     if (de != null) 
     { 
      if (de.Properties.Contains("department")) 
      { 
      result = de.Properties["department"][0].ToString(); 
      } 
     } 
    } 

    return result; 
}