简体   繁体   中英

Async Active Directory querying

I'm trying to create an async'ed query to AD using Directory Services (however it doesn't have any natural async methods), but trying to go around it by doing this:

    public class Domain
    {
        public async Task<SearchResultCollection> Start()
        {
            DirectoryEntry de = new DirectoryEntry("LDAP://DC.com");
            DirectorySearcher de_searcher = new DirectorySearcher(de);
            de_searcher.Filter = "(&(objectClass=person)(sAMAccountName=USERNAME))";
            de_searcher.SearchScope = SearchScope.Subtree;
            de_searcher.PropertiesToLoad.Add("memberOf");
            de_searcher.PropertiesToLoad.Add("DisplayName");
            SearchResultCollection sResult = de_searcher.FindAll();
            return sResult;
        }
    }

    public async void Button1_Click(object sender, EventArgs e)
    {
        Domain domain_object = new Domain();

        SearchResultCollection searchResult_from_domain = await Task.Run(() => domain_object.Start());
    }

I'm getting errors from VS at runtime when clicking the button:

在此处输入图像描述

at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail)
at System.DirectoryServices.DirectoryEntry.Bind()
at System.DirectoryServices.DirectoryEntry.get_AdsObject()
at System.DirectoryServices.DirectorySearcher.FindAll(Boolean findMoreThanOne) > at System.DirectoryServices.DirectorySearcher.FindAll()
at AD_Tool.AD_tool.Domain.d__0.MoveNext() in C:\Users\318306735\source\repos\AD_Tool\Form1.cs:line 33
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()

Is there a proper way to query AD and not block the UI thread...? I have searched for this, can't find anything useful.

The Active Directory classes wrap COM (Component Object Model) objects. COM objects often have thread affinity; they can only exist one thread, and copying them from one thread to another can raise an error. I'm guessing that this is the case for SearchResultCollection ; since it has a Handle property, it probably wraps an unmanaged resource, which is a COM object.

To avoid the error, copy the data on the original thread where you did the search, and copy it into your own data structure that doesn't wrap any unmanaged resource.

class MyClass
{
    public string Name { get; set; }
    public string MemberOf { get; set; } 
}

public class Domain
{
    public async Task<List<MyClass>> Start()
    {
        DirectoryEntry de = new DirectoryEntry("DC.com");
        DirectorySearcher de_searcher = new DirectorySearcher(de);
        de_searcher.Filter = "(&(objectClass=person)(sAMAccountName=USERNAME))";
        de_searcher.SearchScope = SearchScope.Subtree;
        de_searcher.PropertiesToLoad.Add("memberOf");
        de_searcher.PropertiesToLoad.Add("DisplayName");
        SearchResultCollection sResult = de_searcher.FindAll();

        var results = sResult.Cast<SearchResult>().Select( r => new MyClass { Name = r.Name, MemberOf = r.MemberOf).ToList();
        return results;
    }
}

public async void Button1_Click(object sender, EventArgs e)
{
    var results = await Task.Run(() => domain_object.Start());
}

The above is an approximate example-- you may need slightly different code to copy the properties. But the overall idea is to not return that SearchResultCollection in a manner where it may cross threads.

Also, be sure to Dispose your SeachResultCollection when you are done.

Your code works just fine for me when I test it (after changing DC.com to my domain name). The search is performed successfully, and I'm able to read the results back on the main thread.

Is your LDAP path just LDAP:// followed by your domain name? "Unspecified error" can happen when the LDAP path is malformed. For example, if I remove the LDAP:// and just put:

DirectoryEntry de = new DirectoryEntry("DC.com");

Then FindAll() throws an "Unspecified error". So I suspect your problem might be in your LDAP path.

On another note, your code is running in parallel (multi-threaded) because Task.Run runs your code on a new thread. That's fine, and that's the correct way in your case to move code off of the UI thread so that you don't lock the UI thread.

But Start() is not running asynchronously . The async keyword does not make anything asynchronous by itself. It only enables the use of await , which you aren't using anywhere (and DirectorySearcher doesn't support it). You will see a compiler warning telling you that Start() will run synchronously.

So you can remove the async keyword from your method definition:

public SearchResultCollection Start()

You might benefit from reading Microsoft's articles on Asynchronous programming with async and await . They are quite well written, and it should help you understand the difference between asynchronous and parallel.

Thank you, John Wu, for your advise with code it worked. (pasting code below for other people)

If its not too much trouble, could you explain this line of code please (its originally yours, i modified it a little):

List<Maple_results> results = sResult.Cast<SearchResult>().Select(r => new M_results { Displayname = r.Properties["DisplayName"][0].ToString(), Memberof =  r.Properties["memberOf"] }).ToList();

    public class M_results
    {
        public string Displayname { get; set; }
        public ResultPropertyValueCollection Memberof { get; set; }
    }

        public async Task<List<_results>> Start()
    {
        DirectoryEntry de = new DirectoryEntry("LDAP://dc.com");
        DirectorySearcher de_searcher = new DirectorySearcher(de);
        de_searcher.Filter = "(&(objectClass=person)(sAMAccountName=USERNAME))";
        de_searcher.SearchScope = SearchScope.Subtree;
        de_searcher.PropertiesToLoad.Add("memberOf");
        de_searcher.PropertiesToLoad.Add("DisplayName");
        SearchResultCollection sResult = de_searcher.FindAll();

        List<Maple_results> results = sResult.Cast<SearchResult>().Select(r => new M_results { Displayname = r.Properties["DisplayName"][0].ToString(), Memberof =  r.Properties["memberOf"] }).ToList();
        return results;
    }

    private async void Button1_Click(object sender, EventArgs e)
    {
        var results = await Task.Run(() => Start());

        resultTextbox.AppendText(results[0].Memberof[2].ToString() ) ;

    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM