Search code examples
c#active-directoryldap.net-6.0

DirectorySearcher code in application sporadically returns incorrect results, but in isolated test app is consistently accurate


I have a C# .NET 6 console application running under a Windows service that uses DirectorySearcher to determine if contacts (read from a CRM's SQL database) currently also exist in Active Directory. The results returned from DirectorySearcher are inconsistent, even when the exact same search is repeated multiple times one after another (essentially the search will sometimes find the contact and sometimes not, even though it is stored in the searched OU).

I then created a C# .NET 6 console test application that runs as a scheduled task. It isolates the exact same search code used in the malfunctioning application and repeats the same search three times for search criteria that is input. This application consistently returns accurate search results. (The test application is a scheduled task due to credentials provided by a gMSA and the need to input email IDs for testing.)

This is the code used in the malfunctioning application and the correctly functioning test application:

public (bool ContactExists, DirectoryEntry DirectoryEntryObjectNullIfFalse) ADContactExists(int srcId, long emailId, bool shortEmailIdNeedsConversion, bool searchExceptionsPath = false)
{
    #if STAGING || DEBUG
        string searchPath = configuration.GetValue<string>("ActiveDirectory:StagingAdsSearchPath");
    #else
        string searchPath = App_Configuration.GetAdsClientsPath();
    #endif

    if (searchExceptionsPath)
        searchPath = App_Configuration.GetAdsExceptionsPath();

    using (DirectoryEntry rootDirectoryEntry = App_Configuration.GetCredentialedDirectoryEntry(false, searchPath))
    {
        using (DirectorySearcher ds = new DirectorySearcher(rootDirectoryEntry))
        {
            ds.SearchScope = SearchScope.OneLevel;
            long longEmailId = 0;

            if (shortEmailIdNeedsConversion)
                longEmailId = InterActionShortIdConversions.ConvertToLongInterActionID(srcId, emailId);
            else
                longEmailId = emailId;

            ds.Filter = GetFilter("extensionAttribute8", "=", longEmailId.ToString());
            ds.PropertiesToLoad.Add("extensionAttribute8"); //InterAction Email ID (Converted Long Form)

            SearchResult sr = ds.FindOne();

            if (sr != null)
            {
                //Update existing Active Directory contact
                DirectoryEntry directoryEntryResult = App_Configuration.GetCredentialedDirectoryEntry(false, sr.Path);
                return (true, directoryEntryResult);
            }
            else
            {
                //Create new Active Directory contact
                DirectoryEntry directoryEntryResult = new DirectoryEntry();
                return (false, directoryEntryResult);
            }
        }
    }
}

public string GetFilter(string property, string comparisonOperator, string propertyValue)
{
    string filter = "(&(objectCategory=person)(objectClass=contact)(" + property + comparisonOperator + propertyValue + "))";
    return filter;
}
  • I am using version 7.01 of System.DirectoryServices
  • This does not appear to be an issue caused by LDAP calls to domain controllers not having the requested information, as this issue is occurring on contacts that have been in Active Directory for years.
  • The malfunctioning solution is compiled using a Release solution configuration in Visual Studio 2022. I experimented with a non-release compilation and observed that some contacts that always yield incorrect search results yielded correct search results when a Release compilation is not used (although other search results were still incorrect). The compilation seems to affect this issue, but I do not know why or how.
  • TDD and Wireshark traces were performed, but nothing, as of yet, has come of these (the data is still being reviewed)
  • The malfunctioning application has some asynchronous (async await) code due to using NServiceBus and Quartz.NET, however most is synchronous. That being said, the asynchronous code does call some synchronous methods that then call the DirectorySearcher code. I mention this should someone think that a possibility exists that the asynchronous code might be affecting the Active Directory searches in some way.

All assistance with getting DirectorySearcher to consistently return accurate results is greatly appreciated.


Solution

  • The issue was caused by a C# Dictionary being populated and read asynchronously. Because some threads would access theDictionary before it was fully populated, incorrect information was sometimes submitted to Active Directory searches (for example, null values), which would then result in incorrect results being retrieved.

    I attempted to correct this using a ConcurrentDictionary, as detailed in the SO post Populate C# dictionary in async via conditional arguments before access from threads reading dictionary contents, however this was not possible with my use case so I ended up rewriting the logic for the affected methods and removing the Dictionary entirely. Once I did this, DirectorySearcher consistently returned accurate results.