从 MVC 查询 Active Directory 会导致:尝试访问已卸载的应用程序域。 (HRESULT 异常:0x80131014)

Posted

技术标签:

【中文标题】从 MVC 查询 Active Directory 会导致:尝试访问已卸载的应用程序域。 (HRESULT 异常:0x80131014)【英文标题】:Querying Active Directory from MVC result in: Attempted to access an unloaded appdomain. (Exception from HRESULT: 0x80131014) 【发布时间】:2011-09-30 03:28:24 【问题描述】:

我在 MVC Web 应用程序中使用 .Net 4 上的 c# 时遇到问题,当我查询 Active Directory 时,我经常收到错误:尝试访问未加载的应用程序域。 (HRESULT 的例外情况:0x80131014)。

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

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

这是我当前的函数,它将接受一个 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("01", fName.StartsWith(".") ? fName.Substring(0, 4) : fName.Substring(0, 3), sName.Substring(0, 2));
            else
                samAccountName = string.Format("01", 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));
        
    

这个函数是这样调用的:

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

有时得到的错误是:

System.AppDomainUnloadedException: 试图访问一个已卸载的 应用程序域。 (HRESULT 的例外情况: 0x80131014) 在 System.StubHelpers.StubHelpers.InternalGetCOMHRExceptionObject(Int32 hr,IntPtr pCPCMD,对象 pThis) 在 System.StubHelpers.StubHelpers.GetCOMHRExceptionObject(Int32 hr,IntPtr pCPCMD,对象 pThis) 在 System.DirectoryServices.AccountManagement.UnsafeNativeMethods.IADsPathname.Retrieve(Int32 lnFormatType) 在 System.DirectoryServices.AccountManagement.ADStoreCtx.LoadDomainInfo() 在 System.DirectoryServices.AccountManagement.ADStoreCtx.get_UserSuppliedServerName() 在 System.DirectoryServices.AccountManagement.ADDNLinkedAttrSet.BuildPathFromDN(字符串 dn) 在 System.DirectoryServices.AccountManagement.ADDNLinkedAttrSet.MoveNextPrimaryGroupDN() 在 System.DirectoryServices.AccountManagement.ADDNLinkedAttrSet.MoveNext() 在 System.DirectoryServices.AccountManagement.FindResultEnumerator1.MoveNext() at System.DirectoryServices.AccountManagement.FindResultEnumerator1.System.Collections.IEnumerator.MoveNext()

【问题讨论】:

我在拨打S.DS.AM 时遇到了与随机AppDomainUnloadedExceptions 相同的问题,但找不到解决方案。希望附加赏金会引起一些关注... 至少对我有用的一个技巧是当您初始化 PrincipalContext 时,在构造函数中传递域名。 PrincipalContext domain = new PrincipalContext(ContextType.Domain,"AD"); @Reza 的评论似乎对我有用。我遇到了同样的错误,并且在域中传递似乎已经成功了。 【参考方案1】:

虽然在 System.DirectoryServices.AccountManagement 中查看反射器,但内部类“UnsafeNativeMethods”是在本机代码中实现的,所以 UserSuppliedServerName 上一层是我可以继续进行的所有操作,而无需查看 CLR VM,(坦率地说,我什至不知道如何这样做)似乎一个节点未能返回其主要组,所以也许考虑其他实现,经过一番谷歌搜索后,我发现这些可能有帮助

Active Directory and nested groups 这个可能很有希望这里是代码示例..

    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();

这些更多用于信息目的

How to use the PrimaryGroupID attribute to find the primary group for a user Determining User Group Membership in Active Directory and ADAM

【讨论】:

我今天将尝试这种方法,并希望在几个小时内发布我的发现。不过非常感谢。 @Ryk - 您是否曾确认(在您在上一条评论中提到的测试之后)此代码被剪断为可靠的修复? @wakurth - 是的,今天仍在可靠地使用它。但是,从那以后我没有检查是否有更好的解决方案/修复。它有效,所以我有点不理会它 @almog.ori Windows Server 2012 R2 是否存在此问题? 可能没有看到微软为早期版本发布了操作系统补丁,因为其他答案表明,但这是一个未经检验的理论【参考方案2】:

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

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

strUserName 是某个用户,即DOMAIN\johndoe

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

using (PrincipalSearchResult<Principal> result = up.GetGroups())

    // do something with result, here

result 不会为空,但是在我检查了那个条件之后,我检查了 result.Count() &gt; 0 是否会失败(有时 - 虽然我可以通过单击重新创建条件发生时)在我的应用程序中调用此代码的特定选项卡上 - 即使我的应用程序调用了相同的代码并且没有问题)。 result 中的 Message 属性为 Attempted to access an unloaded appdomain. (Exception from HRESULT: 0x80131014)

我在a similar post 中发现,我所要做的就是在我的PrincipalContext 中指定域。由于我无法在其中进行硬编码,当我们在开发、测试和生产环境之间移动代码时,它们对每个环境都有不同的域,因此我能够将其指定为 Environment.UserDomainName

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

对我来说,这消除了错误。

【讨论】:

也为我工作过,Server 2012 我有这个:_context = new PrincipalContext(ContextType.Domain); 更改为这个:_context = new PrincipalContext(ContextType.Domain, Environment.UserDomainName); 它有效。 @Sworgkh 是的,你的说法更快捷,就像我在冗长的解释中所说的一样! :)【参考方案3】:

这个问题和Determine if user is in AD group for .NET 4.0 application一样

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

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

【讨论】:

这个修补程序对我有用。从描述来看,我想说接受的答案可能只是不使用错误的 AccountManagement 命名空间的一种解决方法。但是,API 更加简洁,我认为这应该是首选解决方案。【参考方案4】:

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

如果你遇到异常,也许你可以用不同的方式处理它们。

我认为您的 AppDomain 正在被回收,而 AD 正在做它的巫术。将日志记录添加到Application_End 也可以提供一些线索。

【讨论】:

嗯,好的,我会试一试。我 100% 同意你对 Thread.Sleep 的看法,但我只是在几个论坛建议它有效后才补充说。好吧,它没有! :( 在日志记录中找不到任何可以帮助我的信息,抱歉【参考方案5】:

试试

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("01", fName.StartsWith(".") ? fName.Substring(0, 4) : fName.Substring(0, 3), sName.Substring(0, 2));
        else
            samAccountName = string.Format("01", 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));
    

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

在第二个函数中,您在开始 foreach 之前从未检查过主要参数是否为 null...我也更改了它...

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

【讨论】:

感谢您在这里所做的努力 Yahia,您提供的代码是 100% 正确的,但是,它仍然会不时失败。 almog.ori 提供的解决方案 100% 有效。我认为他的解释是正确的。再次感谢您。

以上是关于从 MVC 查询 Active Directory 会导致:尝试访问已卸载的应用程序域。 (HRESULT 异常:0x80131014)的主要内容,如果未能解决你的问题,请参考以下文章

具有自定义角色和 Active Directory 的 ASP MVC 5 Windows 身份验证

LDAP:如何从 Active Directory 中获取所有用户和组

链接到 SQL Server 的 Active Directory

如何通过Powershell在域计算机上查询Active Directory而不在客户端计算机上安装RSAT工具

powershell PowerShell:按收件人类型交换Active Directory查询

从 ASP.NET 应用程序使用 Active Directory 时出现 DirectoryServicesCOMException (0x80072020)