在System.DirectoryServices.AccountManagement中调用UserPrinciapl.GetGroups时出现奇怪的间歇性错误处理错误

时间:2012-01-04 14:07:33

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

背景

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

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

对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方法模仿的是我们的应用程序中没有运行的代码,但代码是我们无法控制的环境中的其他代码。 < / p>

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. 除了捕获异常并重试之外,还有哪些更好的建议?这是我们目前的工作。
  4. 感谢。

    澄清

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

    我不清楚这是怎么发生的。

2 个答案:

答案 0 :(得分:4)

在发起支持电话后,微软已针对此问题发布了热门修复程序。见下面的链接。

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

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

感谢那些提出建议的人。

答案 1 :(得分:0)

如果您使用的是.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;
}