Runtime: LdapConnection BeginSendRequest Throws PlatformNotSupportedException in .NET Core 2.2

Created on 21 Jan 2019  路  5Comments  路  Source: dotnet/runtime

LdapConnection.BeginSendRequest uses BeginInvoke which is not supported in .NET Core.

<TargetFramework>netcoreapp2.2</TargetFramework>

<PackageReference Include="System.DirectoryServices.Protocols" Version="4.5.0" />

System.PlatformNotSupportedException: Operation is not supported on this platform.
at System.DirectoryServices.Protocols.GetLdapResponseCallback.BeginInvoke(Int32 messageId, LdapOperation operation, ResultAll resultType, TimeSpan requestTimeout, Boolean exceptionOnTimeOut, AsyncCallback callback, Object object)
at System.DirectoryServices.Protocols.LdapConnection.BeginSendRequest(DirectoryRequest request, TimeSpan requestTimeout, PartialResultProcessing partialMode, AsyncCallback callback, Object state)
at System.DirectoryServices.Protocols.LdapConnection.BeginSendRequest(DirectoryRequest request, PartialResultProcessing partialMode, AsyncCallback callback, Object state)

area-System.DirectoryServices

Most helpful comment

If this library is a complete rewrite it would make sense to expose a method that looks something like this:

Task<DirectoryResponse> LdapConnection.SendAsync(DirectoryRequest request, ...)

All 5 comments

I've digged a bit into this issue. As you point the issue, usage of BeginInvoke is not supported in .NET Core.
I found very lately this issue: https://devblogs.microsoft.com/dotnet/migrating-delegate-begininvoke-calls-for-net-core/ . Especially I didn't think the CoreFx would use an illegal call :)

The System.DirectoryServices.Protocols library is a complete rewrite compared to the .NET Framework v4.0 and above.

LdapConnection.BeginSendRequest(...) has 2 distinguished parts:

  • PartialResultProcessing.NoPartialResultSupport:
        _fd.BeginInvoke(messageID, operation, ResultAll.LDAP_MSG_ALL, requestTimeout, true, new AsyncCallback(ResponseCallback), requestState);
        return asyncResult;

This code use an illegal call to the Delegate.BeginInvoke() method.

  • PartialResultProcessing.ReturnPartialResults and PartialResultProcessing.ReturnPartialResultsAndNotifyCallback:
var asyncResult = new LdapPartialAsyncResult(messageID, callback, state, true, this, partialCallback, requestTimeout); 
s_partialResultsProcessor.Add(asyncResult);
return asyncResult; 

Personally I don't exactly understand exactly why the processing is different. Probably when using NoPartialResultSupport to ensure LDAP results are returned before the callback is invoked. While when working with PartialResults, this is left to the caller.

I've (with lot of difficulties) built the v2.0.3 of CoreFX to build myself the System.DirectoryServices.Protocols.dll with the symbols (.pdb) and access to the souce code so I can insert breakoint into the dll. Once done, I've referenced my build of the dll instead the Nuget package.

Sample Program that works:

using System;
using System.Threading;
using System.Threading.Tasks;
using System.DirectoryServices.Protocols;

    class Program
    {
        static void Main(string[] args)
        {
            LdapConnection connection = new LdapConnection("");
            TryTask(connection);
        }

        private static void TryTask(LdapConnection connection)
        {
            Console.WriteLine("main on thread:" + Thread.CurrentThread.ManagedThreadId);
            var r = RootDSE(connection);
            r.Wait();
            Console.WriteLine(r.Result.Attributes.Count);
            Console.ReadLine();
        }

        private static async Task<SearchResultEntry> RootDSE(LdapConnection connection)
        {
            return await Task.Factory.FromAsync(
                (callback, state) =>
                {
                    Console.WriteLine("request on thread:" + Thread.CurrentThread.ManagedThreadId);
                    SearchRequest req = new SearchRequest() { DistinguishedName = "", Filter = "objectclass=*", Scope = SearchScope.Base };
                    return connection.BeginSendRequest(req, PartialResultProcessing.ReturnPartialResultsAndNotifyCallback, callback, state);
                },
                (asyncresult) =>
                {
                    Console.WriteLine("callback on thread:" + Thread.CurrentThread.ManagedThreadId);
                    SearchResponse res = (SearchResponse)connection.EndSendRequest(asyncresult);
                    if (res.ResultCode == ResultCode.Success)
                    {
                        return res.Entries[0];
                    }
                    return null;
                },
                null
            );
        }

Just replace the PartialResultProcessing.ReturnPartialResultsAndNotifyCallback with PartialResultProcessing.NoPartialResultSupport and the program fails on .NET Core 2.2.

The program run fine for both values with the .NET Framework 4.7.2.

I've attempt to update the CoreFX source and it seems working. I've performed a Pull Request referencing this issue. Probably more investigation should be done and run the tests (I don't know if i build correctly neither I know how to run tests, especially for this library: a temporary LDAP Server must be setup).

Helper for building CoreFX from sources:

git tag
dotnet --info

.NET Core SDK (reflecting any global.json):
Version: 2.2.202
Commit: 8a7ff6789d

Runtime Environment:
OS Name: Windows
OS Version: 10.0.16299
OS Platform: Windows
RID: win10-x64
Base Path: C:Program Filesdotnetsdk2.2.202

Host (useful for support):
Version: 2.2.3
Commit: 6b8ad509b6

Match the tag with the version of .NET Core you use on your system. Probably the commit value may be used too.

git reset --hard v2.2.3
build-managed.cmd

Using the above test case this error still persists in .NET core 3.1. Is there any plans on working on this for Windows OS compatibility?

If this library is a complete rewrite it would make sense to expose a method that looks something like this:

Task<DirectoryResponse> LdapConnection.SendAsync(DirectoryRequest request, ...)

BindAsync() would be nice too.

This is a dup of https://github.com/dotnet/runtime/issues/36690 which was fixed by https://github.com/dotnet/runtime/pull/37774 for .NET 5.
cc: @joperezr

Was this page helpful?
0 / 5 - 0 ratings