Search code examples
c#asp.net-web-apidirectoryservices

Why is principalsearcher code significantly slower


I have a web service that assigns usernames to people. inside I am calling a bunch of functions that verify if a username exist in AD across the fields UserPrincipal, samAccountName, proxyaddresses, and email.

In unit test this code takes 10 seconds to run. Deployed to the web server locally the code takes over 9 minutes to run. I don't think this is a first run issue because it takes just as long the second time around. Our domain has over 30k users and I have to search the whole domain to make sure the name doesn't exist. Per requirements caching can't be used because we need an up to the minute check.

I have searched for how to improve the speed, but haven't turned up anything.

Anyone have suggestions on how to improve performance?

    public bool DoesEmailExist(string email)
    {
        using (var context = GetPrincipalContext())
        {
            var userQuery = new UserPrincipal(context) { EmailAddress = email + "@*" };

            var searcher = new PrincipalSearcher(userQuery);

            var result = searcher.FindOne();

            if (result == null)
            {
                var aliasQuery = new ExtendedUserPrincipal(context) { ProxyAddress = "*smtp:" + email + "@*" };

                searcher = new PrincipalSearcher(aliasQuery);

                var aliasResult = searcher.FindOne();

                if (result == null)
                {
                    return false;
                }
            }

            return true;
        }
    }

    private PrincipalContext GetPrincipalContext(string ou)
    {
            return new PrincipalContext(ContextType.Domain, dc, ou, ContextOptions.Negotiate);
    }

Edit - I switched to using DirectorySearcher and the speed seems to have improved some. It now takes 5 minutes on a running web api. I still would like to know why my unit test are significantly faster than the web api. Using logging code the call for DoesUserNameExist in a unit test takes 7 seconds. Through the running web api it takes 5 minutes.

    public bool DoesUserNameExist(string userName)
    {
        string filter = "(|(samAccountName={NAME})(UserPrincipalName={NAME}@*)(mail={NAME}@*)(proxyAddresses=*smtp:{NAME}@*))";

        filter = filter.Replace("{NAME}", userName);

        using (var de = new DirectoryEntry("LDAP://" + domainController + "/" + rootOU))
        {
            var searcher = new DirectorySearcher();
            searcher.Filter = filter;
            searcher.PageSize = 1;
            var result = searcher.FindOne();

            if (result != null)
            {
                return true;
            }

            return false;
        }
    }

Solution

  • Although proxyAddresses attribute is indexed, it only helps for equal and start-with filter but not end-with and contains ones.

    • Fast: proxyAddresses=sth, proxyAddresses=sth*
    • Slow: proxyAddresses=*sth*, proxyAddresses=*sth

    I don't think there are proxyAddresses that looks like "abcdsmtp:[email protected]".
    You can simply use proxyAddresses=smtp:{NAME}@*