从MVC查询Active Directory导致:尝试访问已卸载的appdomain。 (HRESULT异常:0x80131014)

时间:2021-11-16 03:01:45

I have an issue using c# on .Net 4 in a MVC web application, where when I query Active Directory, I frequently get an error: Attempted to access an unloaded appdomain. (Exception from HRESULT: 0x80131014).

我在MVC Web应用程序中使用c#on .Net 4时遇到问题,当我查询Active Directory时,我经常收到错误:尝试访问已卸载的appdomain。 (来自HRESULT的异常:0x80131014)。

The strange thing is, that it will work flawlessly for a time, and then it will just start happening, and then just disappear again.

奇怪的是,它会在一段时间内完美无缺地工作,然后它才会开始发生,然后再次消失。

I have made a few modifications to the function to get it to work , but they all seem to fail. I am wondering if I am doing something wrong, or if there is a better way to do it.

我对函数进行了一些修改以使其工作,但它们似乎都失败了。我想知道我做错了什么,或者是否有更好的方法来做。

Here is my current function, that will accept a loginId, and a PrincipalContext. The loginId can either be the user DisplayName i.e "John Smith", or DOMAINNAME\josmi. The default is to use the first 2 letters of their firstname, and then the first 3 letters of their surname. There is a check in there if this is not the case. This part if fine.

这是我当前的函数,它将接受loginId和PrincipalContext。 loginId可以是用户DisplayName,即“John Smith”,也可以是DOMAINNAME \ josmi。默认设置是使用其名字的前2个字母,然后使用其姓氏的前3个字母。如果不是这样,那里有一张支票。这部分如果罚款。

public List<ADGroup> GetMemberGroups(string loginId, PrincipalContext principalContext, int tries = 0)
{
    var result = new List<ADGroup>();

    try
    {
        var samAccountName = "";
        if (loginId.Contains(" "))
        {
            var fName = loginId.Split(Char.Parse(" "))[0].ToLower();
            var sName = loginId.Split(Char.Parse(" "))[1].ToLower();

            if (sName.Trim().Length == 2)
                samAccountName = string.Format("{0}{1}", fName.StartsWith(".") ? fName.Substring(0, 4) : fName.Substring(0, 3), sName.Substring(0, 2));
            else
                samAccountName = string.Format("{0}{1}", fName.StartsWith(".") ? fName.Substring(0, 3) : fName.Substring(0, 2), sName.Substring(0, 3));
        }
        else
            samAccountName = loginId.Substring(loginId.IndexOf(@"\") + 1);

        var authPrincipal = UserPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, samAccountName);

        if (authPrincipal == null)
            throw new Exception(string.Format("authPrincipal is null for loginId - {0}", loginId));

        var firstLevelGroups = authPrincipal.GetGroups();

        AddGroups(firstLevelGroups, ref result);
    }
    catch
    {
        if (tries > 5)
            throw;

        tries += 1;

        System.Threading.Thread.Sleep(1000);
        GetMemberGroups(loginId, principalContext, tries);
    }

    return result;
}

    private void AddGroups(PrincipalSearchResult<Principal> principal, ref List<ADGroup> returnList)
    {
        foreach (var item in principal)
        {
            if (item.GetGroups().Count() > 0)
                AddGroups(item.GetGroups(), ref returnList);

            returnList.Add(new ADGroup(item.SamAccountName, item.Sid.Value));
        }
    }

This function is called like this:

这个函数被调用如下:

MembershipGroups = ad.GetMemberGroups(user.SamAccountName, new PrincipalContext(ContextType.Domain));

The the error that I SOMETIMES get is:

我有时得到的错误是:

System.AppDomainUnloadedException: Attempted to access an unloaded appdomain. (Exception from HRESULT: 0x80131014) at System.StubHelpers.StubHelpers.InternalGetCOMHRExceptionObject(Int32 hr, IntPtr pCPCMD, Object pThis) at System.StubHelpers.StubHelpers.GetCOMHRExceptionObject(Int32 hr, IntPtr pCPCMD, Object pThis) at System.DirectoryServices.AccountManagement.UnsafeNativeMethods.IADsPathname.Retrieve(Int32 lnFormatType) at System.DirectoryServices.AccountManagement.ADStoreCtx.LoadDomainInfo() at System.DirectoryServices.AccountManagement.ADStoreCtx.get_UserSuppliedServerName() at System.DirectoryServices.AccountManagement.ADDNLinkedAttrSet.BuildPathFromDN(String dn) at System.DirectoryServices.AccountManagement.ADDNLinkedAttrSet.MoveNextPrimaryGroupDN() at System.DirectoryServices.AccountManagement.ADDNLinkedAttrSet.MoveNext() at System.DirectoryServices.AccountManagement.FindResultEnumerator1.MoveNext() at System.DirectoryServices.AccountManagement.FindResultEnumerator1.System.Collections.IEnumerator.MoveNext()

System.AppDomainUnloadedException:尝试访问已卸载的appdomain。 (来自HRESULT的异常:0x80131014)System.StubHelpers.StubHelpers.InternalGetCOMHRExceptionObject(Int32hr,IntPtr pCPCMD,Object pThis)位于System.DuboryServices.AccountManagement的System.StubHelpers.StubHelpers.GetCOMHRExceptionObject(Int32hr,IntPtr pCPCMD,Object pThis)。位于系统的System.DirectoryServices.AccountManagement.ADStoreCtx.get_UserSuppliedServerName()处于System.DirectoryServices.AccountManagement.ADDNLinkedAttrSet.BuildPathFromDN(String dn)的System.DirectoryServices.AccountManagement.ADStoreCtx.LoadDomainInfo()中的UnsafeNativeMethods.IADsPathname.Retrieve(Int32 lnFormatType)。位于System.DirectoryServices.AccountManagement.FindResultEnumerator1.MoveNext()的System.DirectoryServices.AccountManagement.ADDNLinkedAttrSet.MoveNext()的SystemServices.AccountManagement.ADDNLinkedAttrSet.MoveNextPrimaryGroupDN(),System.DirectoryServices.AccountManagement.FindResultEnumerator1.System.Collections.IEnumerator.MoveNext( )

5 个解决方案

#1


9  

looking though reflector at System.DirectoryServices.AccountManagement the internal class "UnsafeNativeMethods" is implemented in native code, so UserSuppliedServerName one level up is all I can go on without looking at the CLR VM, (frankly im not sure even how to do that) Seems that a node is failing to return its primary group, so perhaps consider other implementations, after a bit of googling ive come across these that may help

通过System.DirectoryServices.AccountManagement中的反射器查看内部类“UnsafeNativeMethods”是在本机代码中实现的,因此UserSuppliedServerName只需一级就可以继续而不用查看CLR VM,(坦率地说我甚至不确定怎么做)似乎一个节点无法返回其主要组,所以也许考虑其他实现,在谷歌搜索后看到这些可能会有所帮助

  • Active Directory and nested groups this one may be promising heres the code sample..

    Active Directory和嵌套组这个可能很有希望继承代码示例..

        public IList<string> FindUserGroupsLdap(string username)
    { 
        // setup credentials and connection  
        var credentials = new NetworkCredential("username", "password", "domain");  
        var ldapidentifier = new LdapDirectoryIdentifier("server", 389, true, false); 
        var ldapConn = new LdapConnection(ldapidentifier, credentials); 
        // retrieving the rootDomainNamingContext, this will make sure we query the absolute root   
        var getRootRequest = new SearchRequest(string.Empty, "objectClass=*", SearchScope.Base, "rootDomainNamingContext");  
        var rootResponse = (SearchResponse)ldapConn.SendRequest(getRootRequest);   
        var rootContext = rootResponse.Entries[0].Attributes["rootDomainNamingContext"][0].ToString();  
        // retrieve the user 
        string ldapFilter = string.Format("(&(objectCategory=person)(sAMAccountName={0}))", username); 
        var getUserRequest = new SearchRequest(rootContext, ldapFilter, SearchScope.Subtree, null);  
        var userResponse = (SearchResponse)ldapConn.SendRequest(getUserRequest);     
        // send a new request to retrieve the tokenGroups attribute, we can not do this with our previous request since 
        // tokenGroups needs SearchScope.Base (dont know why...)  
        var tokenRequest = new SearchRequest(userResponse.Entries[0].DistinguishedName, "(&(objectCategory=person))", SearchScope.Base, "tokenGroups");  
        var tokenResponse = (SearchResponse)ldapConn.SendRequest(tokenRequest);  
        var tokengroups = tokenResponse.Entries[0].Attributes["tokenGroups"].GetValues(typeof(byte[])); 
        // build query string this query will then look like (|(objectSid=sid)(objectSid=sid2)(objectSid=sid3))  
        // we need to convert the given bytes to a hexadecimal representation because thats the way they   
        // sit in ActiveDirectory  
        var sb = new StringBuilder();  
        sb.Append("(|");   
        for (int i = 0; i < tokengroups.Length; i++)  
        {  
            var arr = (byte[])tokengroups[i];    
            sb.AppendFormat("(objectSid={0})", BuildHexString(arr));   
        }   
        sb.Append(")");    
        // send the request with our build query. This will retrieve all groups with the given objectSid
        var groupsRequest = new SearchRequest(rootContext, sb.ToString(), SearchScope.Subtree, "sAMAccountName"); 
        var groupsResponse = (SearchResponse)ldapConn.SendRequest(groupsRequest); 
        // loop trough and get the sAMAccountName (normal, readable name)   
        var userMemberOfGroups = new List<string>();    
        foreach (SearchResultEntry entry in groupsResponse.Entries)    
        userMemberOfGroups.Add(entry.Attributes["sAMAccountName"][0].ToString());  
        return userMemberOfGroups;
    } 
    
    private string BuildHexString(byte[] bytes)
    {  
        var sb = new StringBuilder(); 
        for (int i = 0; i < bytes.Length; i++) 
        sb.AppendFormat("\\{0}", bytes[i].ToString("X2")); 
        return sb.ToString();
    }
    

These are more for info purposes

这些更多用于信息目的

#2


4  

This issue is the same as Determine if user is in AD group for .NET 4.0 application

此问题与确定用户是否在.NET 4.0应用程序的AD组中相同

It appears to be a bug in ADSI that was resolved with a hotfix. Windows 7 SP1 and Windows Server 2008 R2 SP1 don't include the fix, so it will need to be manually deployed on your development machines and server environments.

它似乎是ADSI中的一个错误,通过修补程序解决。 Windows 7 SP1和Windows Server 2008 R2 SP1不包含此修复程序,因此需要在开发计算机和服务器环境中手动部署它。

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

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

#3


2  

I don't know how PrincipalContext is being passed in, here, but one thing I noticed in my own code and research when I had this error, I had:

我不知道PrincipalContext是如何传入的,在这里,但是当我遇到这个错误时,我在自己的代码和研究中注意到了一件事,我有:

PrincipalContext oPrincipalContext = new PrincipalContext(ContextType.Domain);
UserPrincipal oUserPrincipal = UserPrincipal.FindByIdentity(oPrincipalContext , strUserName);

Where strUserName was some user, i.e. DOMAIN\johndoe

strUserName是某个用户,即DOMAIN \ johndoe

I was calling that code (which was in a separate function) and returning the UserPrincipal object as up and passing it to:

我正在调用该代码(它在一个单独的函数中)并将UserPrincipal对象作为up返回并将其传递给:

using (PrincipalSearchResult<Principal> result = up.GetGroups())
{
    // do something with result, here
}

result wouldn't be null, but after I checked for that condition, I checked if result.Count() > 0, and that's when it would fail (sometimes - though I could re-create the conditions when it would happen by clicking on a particular tab in my app that called this code - even though the same code was called onload of my app and had no issues). The Message property in result was Attempted to access an unloaded appdomain. (Exception from HRESULT: 0x80131014).

结果不会为null,但是在我检查了那个条件之后,我检查了result.Count()> 0,那就是它失败的时候(有时 - 虽然我可以通过点击它来重新创建条件在我的应用程序中调用此代码的特定选项卡 - 即使相同的代码被称为我的应用程序的onload并且没有问题)。结果中的Message属性已尝试访问已卸载的appdomain。 (来自HRESULT的异常:0x80131014)。

I found in a similar post to this one that all I had to do was specify the domain in my PrincipalContext. Since I could not hard code mine in, as we move our code between Dev, Test, and Production environments where they have different domains for each of these, I was able to specify it as Environment.UserDomainName:

我在一篇类似的帖子中发现,我所要做的就是在PrincipalContext中指定域名。由于我无法硬编码,因为我们在开发,测试和生产环境之间移动我们的代码,在这些环境中,每个域都有不同的域,我可以将其指定为Environment.UserDomainName:

PrincipalContext oPrincipalContext = new PrincipalContext(ContextType.Domain, Environment.UserDomainName);

This got rid of the error, for me.

对我来说,这摆脱了错误。

#4


0  

You could put in some logging to narrow down the problem. That Thread.Sleep does not look like something one would want in a web application :)

您可以进行一些日志记录以缩小问题范围。 Thread.Sleep看起来不像Web应用程序中想要的东西:)

If you are getting exceptions maybe you could handle them differently.

如果你得到例外,也许你可以用不同的方式处理它们。

I reckon your AppDomain is being recycled while AD is doing its voodoo. Adding logging to the Application_End could also provide some clues.

我估计你的AppDomain正在回收,而AD正在做巫术。向Application_End添加日志记录也可以提供一些线索。

#5


0  

try

尝试

public List<ADGroup> GetMemberGroups(string loginId, PrincipalContext principalContext, int tries = 0)
{

var result = new List<ADGroup>();
bool Done = false;

try
{
    var samAccountName = "";
    if (loginId.Contains(" "))
    {
        var fName = loginId.Split(Char.Parse(" "))[0].ToLower();
        var sName = loginId.Split(Char.Parse(" "))[1].ToLower();

        if (sName.Trim().Length == 2)
            samAccountName = string.Format("{0}{1}", fName.StartsWith(".") ? fName.Substring(0, 4) : fName.Substring(0, 3), sName.Substring(0, 2));
        else
            samAccountName = string.Format("{0}{1}", fName.StartsWith(".") ? fName.Substring(0, 3) : fName.Substring(0, 2), sName.Substring(0, 3));
    }
    else
        samAccountName = loginId.Substring(loginId.IndexOf(@"\") + 1);

    var authPrincipal = UserPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, samAccountName);

    if (authPrincipal == null)
        throw new Exception(string.Format("authPrincipal is null for loginId - {0}", loginId));

    var firstLevelGroups = authPrincipal.GetGroups();

    AddGroups(firstLevelGroups, ref result);

    Done = true;
}
catch
{
    if (tries > 5)
        throw;

    tries += 1;
}

if ( ( !Done) && (tries < 6) )
     {
     System.Threading.Thread.Sleep(1000);
     result = GetMemberGroups(loginId, principalContext, tries);
     }

return result;
}

private void AddGroups(PrincipalSearchResult<Principal> principal, ref List<ADGroup> returnList)
{
    if ( principal == null )
         return;
    foreach (var item in principal)
    {
        if (item.GetGroups().Count() > 0)
            AddGroups(item.GetGroups(), ref returnList);

        returnList.Add(new ADGroup(item.SamAccountName, item.Sid.Value));
    }
}

When an exception happens you called the function again from the catch-block (depending on the value of tries) but discarded its return value - so even if the second/third... call worked you returned an empty result to the original caller. I changed that so the result won't be discarded anymore...

当发生异常时,你从catch块再次调用该函数(取决于尝试的值)但是丢弃了它的返回值 - 所以即使第二个/第三个...调用工作,你也会向原调用者返回一个空结果。我改变了,所以结果不会再被丢弃了......

In the second function you never checked the principal param for null before starting the foreach... I changed that too...

在第二个函数中,你从来没有在启动foreach之前检查了主要参数为null ...我也改变了...

And I removed the recursion from within the catch block catch (although I am really not sure whether this change has any real effect).

我从catch块catch中删除了递归(虽然我真的不确定这个更改是否有任何实际效果)。

#1


9  

looking though reflector at System.DirectoryServices.AccountManagement the internal class "UnsafeNativeMethods" is implemented in native code, so UserSuppliedServerName one level up is all I can go on without looking at the CLR VM, (frankly im not sure even how to do that) Seems that a node is failing to return its primary group, so perhaps consider other implementations, after a bit of googling ive come across these that may help

通过System.DirectoryServices.AccountManagement中的反射器查看内部类“UnsafeNativeMethods”是在本机代码中实现的,因此UserSuppliedServerName只需一级就可以继续而不用查看CLR VM,(坦率地说我甚至不确定怎么做)似乎一个节点无法返回其主要组,所以也许考虑其他实现,在谷歌搜索后看到这些可能会有所帮助

  • Active Directory and nested groups this one may be promising heres the code sample..

    Active Directory和嵌套组这个可能很有希望继承代码示例..

        public IList<string> FindUserGroupsLdap(string username)
    { 
        // setup credentials and connection  
        var credentials = new NetworkCredential("username", "password", "domain");  
        var ldapidentifier = new LdapDirectoryIdentifier("server", 389, true, false); 
        var ldapConn = new LdapConnection(ldapidentifier, credentials); 
        // retrieving the rootDomainNamingContext, this will make sure we query the absolute root   
        var getRootRequest = new SearchRequest(string.Empty, "objectClass=*", SearchScope.Base, "rootDomainNamingContext");  
        var rootResponse = (SearchResponse)ldapConn.SendRequest(getRootRequest);   
        var rootContext = rootResponse.Entries[0].Attributes["rootDomainNamingContext"][0].ToString();  
        // retrieve the user 
        string ldapFilter = string.Format("(&(objectCategory=person)(sAMAccountName={0}))", username); 
        var getUserRequest = new SearchRequest(rootContext, ldapFilter, SearchScope.Subtree, null);  
        var userResponse = (SearchResponse)ldapConn.SendRequest(getUserRequest);     
        // send a new request to retrieve the tokenGroups attribute, we can not do this with our previous request since 
        // tokenGroups needs SearchScope.Base (dont know why...)  
        var tokenRequest = new SearchRequest(userResponse.Entries[0].DistinguishedName, "(&(objectCategory=person))", SearchScope.Base, "tokenGroups");  
        var tokenResponse = (SearchResponse)ldapConn.SendRequest(tokenRequest);  
        var tokengroups = tokenResponse.Entries[0].Attributes["tokenGroups"].GetValues(typeof(byte[])); 
        // build query string this query will then look like (|(objectSid=sid)(objectSid=sid2)(objectSid=sid3))  
        // we need to convert the given bytes to a hexadecimal representation because thats the way they   
        // sit in ActiveDirectory  
        var sb = new StringBuilder();  
        sb.Append("(|");   
        for (int i = 0; i < tokengroups.Length; i++)  
        {  
            var arr = (byte[])tokengroups[i];    
            sb.AppendFormat("(objectSid={0})", BuildHexString(arr));   
        }   
        sb.Append(")");    
        // send the request with our build query. This will retrieve all groups with the given objectSid
        var groupsRequest = new SearchRequest(rootContext, sb.ToString(), SearchScope.Subtree, "sAMAccountName"); 
        var groupsResponse = (SearchResponse)ldapConn.SendRequest(groupsRequest); 
        // loop trough and get the sAMAccountName (normal, readable name)   
        var userMemberOfGroups = new List<string>();    
        foreach (SearchResultEntry entry in groupsResponse.Entries)    
        userMemberOfGroups.Add(entry.Attributes["sAMAccountName"][0].ToString());  
        return userMemberOfGroups;
    } 
    
    private string BuildHexString(byte[] bytes)
    {  
        var sb = new StringBuilder(); 
        for (int i = 0; i < bytes.Length; i++) 
        sb.AppendFormat("\\{0}", bytes[i].ToString("X2")); 
        return sb.ToString();
    }
    

These are more for info purposes

这些更多用于信息目的

#2


4  

This issue is the same as Determine if user is in AD group for .NET 4.0 application

此问题与确定用户是否在.NET 4.0应用程序的AD组中相同

It appears to be a bug in ADSI that was resolved with a hotfix. Windows 7 SP1 and Windows Server 2008 R2 SP1 don't include the fix, so it will need to be manually deployed on your development machines and server environments.

它似乎是ADSI中的一个错误,通过修补程序解决。 Windows 7 SP1和Windows Server 2008 R2 SP1不包含此修复程序,因此需要在开发计算机和服务器环境中手动部署它。

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

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

#3


2  

I don't know how PrincipalContext is being passed in, here, but one thing I noticed in my own code and research when I had this error, I had:

我不知道PrincipalContext是如何传入的,在这里,但是当我遇到这个错误时,我在自己的代码和研究中注意到了一件事,我有:

PrincipalContext oPrincipalContext = new PrincipalContext(ContextType.Domain);
UserPrincipal oUserPrincipal = UserPrincipal.FindByIdentity(oPrincipalContext , strUserName);

Where strUserName was some user, i.e. DOMAIN\johndoe

strUserName是某个用户,即DOMAIN \ johndoe

I was calling that code (which was in a separate function) and returning the UserPrincipal object as up and passing it to:

我正在调用该代码(它在一个单独的函数中)并将UserPrincipal对象作为up返回并将其传递给:

using (PrincipalSearchResult<Principal> result = up.GetGroups())
{
    // do something with result, here
}

result wouldn't be null, but after I checked for that condition, I checked if result.Count() > 0, and that's when it would fail (sometimes - though I could re-create the conditions when it would happen by clicking on a particular tab in my app that called this code - even though the same code was called onload of my app and had no issues). The Message property in result was Attempted to access an unloaded appdomain. (Exception from HRESULT: 0x80131014).

结果不会为null,但是在我检查了那个条件之后,我检查了result.Count()> 0,那就是它失败的时候(有时 - 虽然我可以通过点击它来重新创建条件在我的应用程序中调用此代码的特定选项卡 - 即使相同的代码被称为我的应用程序的onload并且没有问题)。结果中的Message属性已尝试访问已卸载的appdomain。 (来自HRESULT的异常:0x80131014)。

I found in a similar post to this one that all I had to do was specify the domain in my PrincipalContext. Since I could not hard code mine in, as we move our code between Dev, Test, and Production environments where they have different domains for each of these, I was able to specify it as Environment.UserDomainName:

我在一篇类似的帖子中发现,我所要做的就是在PrincipalContext中指定域名。由于我无法硬编码,因为我们在开发,测试和生产环境之间移动我们的代码,在这些环境中,每个域都有不同的域,我可以将其指定为Environment.UserDomainName:

PrincipalContext oPrincipalContext = new PrincipalContext(ContextType.Domain, Environment.UserDomainName);

This got rid of the error, for me.

对我来说,这摆脱了错误。

#4


0  

You could put in some logging to narrow down the problem. That Thread.Sleep does not look like something one would want in a web application :)

您可以进行一些日志记录以缩小问题范围。 Thread.Sleep看起来不像Web应用程序中想要的东西:)

If you are getting exceptions maybe you could handle them differently.

如果你得到例外,也许你可以用不同的方式处理它们。

I reckon your AppDomain is being recycled while AD is doing its voodoo. Adding logging to the Application_End could also provide some clues.

我估计你的AppDomain正在回收,而AD正在做巫术。向Application_End添加日志记录也可以提供一些线索。

#5


0  

try

尝试

public List<ADGroup> GetMemberGroups(string loginId, PrincipalContext principalContext, int tries = 0)
{

var result = new List<ADGroup>();
bool Done = false;

try
{
    var samAccountName = "";
    if (loginId.Contains(" "))
    {
        var fName = loginId.Split(Char.Parse(" "))[0].ToLower();
        var sName = loginId.Split(Char.Parse(" "))[1].ToLower();

        if (sName.Trim().Length == 2)
            samAccountName = string.Format("{0}{1}", fName.StartsWith(".") ? fName.Substring(0, 4) : fName.Substring(0, 3), sName.Substring(0, 2));
        else
            samAccountName = string.Format("{0}{1}", fName.StartsWith(".") ? fName.Substring(0, 3) : fName.Substring(0, 2), sName.Substring(0, 3));
    }
    else
        samAccountName = loginId.Substring(loginId.IndexOf(@"\") + 1);

    var authPrincipal = UserPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, samAccountName);

    if (authPrincipal == null)
        throw new Exception(string.Format("authPrincipal is null for loginId - {0}", loginId));

    var firstLevelGroups = authPrincipal.GetGroups();

    AddGroups(firstLevelGroups, ref result);

    Done = true;
}
catch
{
    if (tries > 5)
        throw;

    tries += 1;
}

if ( ( !Done) && (tries < 6) )
     {
     System.Threading.Thread.Sleep(1000);
     result = GetMemberGroups(loginId, principalContext, tries);
     }

return result;
}

private void AddGroups(PrincipalSearchResult<Principal> principal, ref List<ADGroup> returnList)
{
    if ( principal == null )
         return;
    foreach (var item in principal)
    {
        if (item.GetGroups().Count() > 0)
            AddGroups(item.GetGroups(), ref returnList);

        returnList.Add(new ADGroup(item.SamAccountName, item.Sid.Value));
    }
}

When an exception happens you called the function again from the catch-block (depending on the value of tries) but discarded its return value - so even if the second/third... call worked you returned an empty result to the original caller. I changed that so the result won't be discarded anymore...

当发生异常时,你从catch块再次调用该函数(取决于尝试的值)但是丢弃了它的返回值 - 所以即使第二个/第三个...调用工作,你也会向原调用者返回一个空结果。我改变了,所以结果不会再被丢弃了......

In the second function you never checked the principal param for null before starting the foreach... I changed that too...

在第二个函数中,你从来没有在启动foreach之前检查了主要参数为null ...我也改变了...

And I removed the recursion from within the catch block catch (although I am really not sure whether this change has any real effect).

我从catch块catch中删除了递归(虽然我真的不确定这个更改是否有任何实际效果)。