【问题标题】:Strange intermittent error handling bug when calling UserPrinciapl.GetGroups in System.DirectoryServices.AccountManagement在 System.DirectoryServices.AccountManagement 中调用 UserPrinciapl.GetGroups 时出现奇怪的间歇性错误处理错误
【发布时间】:2012-02-02 10:24:13
【问题描述】:

背景

我们有一个用 C# 编写的 asp.net 4.0 Web 应用程序,它调用一个用 C# 编写的 .net 3.5 Web 服务。 Web 服务被传递一个用户 ID,并根据用户所属的活动目录组返回一个数据列表。

Web 服务使用 .net 3.5 版本的 System.DirectoryServices.AccountManagement 来获取用户所属组的 Sid。

对 UserPrincipal.GetGroups 的调用间歇性地失败,并出现以下错误。两次发生之间有很长的时间,但当它确实发生时,它会反复发生几分钟。不同的 AD 用户出现此问题。

这个异常的堆栈跟踪对我们来说毫无意义。我们花了很多时间查看 Reflector/ILSpy 中的 Microsoft AD 代码,但无法超越对 IADsPathName.Retrieve 的调用。

异常

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)

重现代码

请注意,CauseNotSupportedException 方法正在模仿不在我们的应用程序中运行的代码,而是在我们无法控制的环境中其他地方的代码中运行的代码。

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

实现 Stream 以模仿 HttpResponseStream 行为

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) { }
}

问题

  1. 如果您运行上面的示例,则在调用 GetGroups 时会引发 CauseNotSupportedException 方法中发生的错误。这个怎么可能?任何理论或进一步的见解将不胜感激。
  2. 关于如何进一步调查的任何建议?
  3. 有比捕获异常并重试更好的建议吗?这是我们目前的工作。

谢谢。

澄清

我不确定我的解释有多清楚,所以这里有一些澄清。首先,我对获取 Sids 的活动目录代码感到满意。这就是我想要它做的事情,我认为问题不在于这样。真正的问题是,当其他不相关的代码(不在我们的应用程序中)发生错误时,错误会显示在 GetGroups 调用中,因此会出现奇怪的堆栈跟踪,错误最初发生在 System.Web.HttpResponseStream.get_Position() 中。在示例应用程序中 NotSupportedException 发生在 CauseNotSupportedException 但代码不会在那里中断,它会在调用 GetGroups 时中断。如果您在示例应用中注释掉 CauseNotSupportedException(),则永远不会发生错误。

我不清楚这怎么可能发生。

【问题讨论】:

  • 问题,您是否要查找用户是否在特定组中。我试图更好地了解您真正在寻找什么。我可能有一个建议在编码方面更好的方法..这里有一个链接也会有所帮助,我将在下面粘贴 2 个代码 sn-ps,它应该可以帮助您更轻松地获得所需的内容。 msdn.microsoft.com/en-us/magazine/cc135979.aspx
  • 非常感谢您的回复。我需要的是用户所属组的 Sid 列表。我目前正在使用 System.DirectoryServices.AccountManagement 命名空间(UserPrincipal.GetGroups 在该命名空间中)。所以我的代码示例与您在下面发布的非常相似。我的代码得到了我想要的,这只是我试图理解的不相关代码引起的间歇性错误。如果您有时间,值得运行示例代码并单步执行。
  • 好吧没问题..一开始很难确定你想要什么..我很抱歉你仍然需要检查那个属性或 (string)Properties["samAccountName"][0 ].ToString() 因为此时这将是一个对象..希望这是有道理的..
  • 抱歉,我发现很难写出这些提供足够信息但仍然清晰的东西。奇怪的是,当 CauseNotSupportedException 中发生错误时,它会跳转到 GetGroups 并在那里抛出异常。
  • 如果不查看您的所有代码,我无法判断有很多方法可以获取您正在寻找的内容,但我正在努力为您提供答案,以帮助您了解您的代码,以防您需要修复或重构某些东西

标签: c# .net error-handling active-directory


【解决方案1】:

在拨打支持电话后,Microsoft 已针对此问题发布了热修复程序。请参阅下面的链接。

陈述的原因是: “出现此问题是因为 System.DirectoryServices.AccountManagement 命名空间是本机 API Active Directory 服务接口 (ADSI) 的精简包装。由 IADsPathName 接口实现的 IErrorInfo 接口响应 ADSI 不抛出的异常。当有堆栈上没有 ADSI 异常,IErrorInfo 接口会抛出堆栈顶部的异常,即使该异常由应用程序中的另一个处理程序处理。”

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

感谢提供建议的人。

【讨论】:

    【解决方案2】:

    如果您使用的是 .NET 3.5 或更高版本,则可以使用新的 System.DirectoryServices.AccountManagement (S.DS.AM) 命名空间,这比以前更容易。

    在此处阅读所有相关信息:[在 .NET Framework 3.5 中管理目录安全主体][1]

    基本上,您需要有一个“主体上下文”(通常是您的域)、一个用户主体,然​​后您就可以很容易地获得它的组:

    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;
    }
    

    要访问UserPrincipal 对象上未显示的某些属性,您需要深入了解底层DirectoryEntry

    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;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-29
      • 2012-05-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-05
      相关资源
      最近更新 更多