2

I have a very simple WCF service that I need to write a custom client for, that has to override CreateChannel, but when I call EndInvoke inside my ChannelBase implementation I get

System.NullReferenceException occurred
  HResult=0x80004003
  Message=Object reference not set to an instance of an object.
  Source=System.ServiceModel
  StackTrace:
   at System.ServiceModel.Dispatcher.ProxyOperationRuntime.MapAsyncEndInputs(IMethodCallMessage methodCall, IAsyncResult& result, Object[]& outs)
   at System.ServiceModel.ClientBase`1.ChannelBase`1.EndInvoke(String methodName, Object[] args, IAsyncResult result)
   at Client.TestServiceClient.TestServiceChannel.<SayHello>b__1_0(IAsyncResult result) 
   at System.Runtime.AsyncResult.Complete(Boolean completedSynchronously)

I'm not sure what I did wrong and the StackTrace isn't helping me either, google didn't turn up anything useful. My solution is using .net 4.6.2 and the call to the service succeeds (it prints to console), but EndInvoke throws from within framework code. Any assistance would be greatly appreciated.

Minimal repro:

namespace Service {
    using System;
    using System.ServiceModel;

    [ServiceContract]
    public interface ITestService {
        [OperationContract]
        void SayHello();
    }

    public class TestService : ITestService {
        public void SayHello() => Console.WriteLine("Hello");
    }
}


namespace Host {
    using System;
    using System.ServiceModel;
    using Service;

    internal static class Program {
        private static void Main() {
            var host = new ServiceHost(typeof(TestService));
            host.AddServiceEndpoint(typeof(ITestService), new BasicHttpBinding(BasicHttpSecurityMode.None), "http://localhost:13377/");
            host.Open();
            Console.ReadLine();
        }
    }
}

namespace Client {
    using System;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using Service;

    public class TestServiceClient : ClientBase<ITestService>, ITestService {
        public TestServiceClient(Binding binding, EndpointAddress remoteAddress) : base(binding, remoteAddress) {}

        public void SayHello() => Channel.SayHello();

        protected override ITestService CreateChannel() => new TestServiceChannel(this);

        private class TestServiceChannel: ChannelBase<ITestService>, ITestService {
            public TestServiceChannel(ClientBase<ITestService> client) : base(client) {}

            public void SayHello() => base.BeginInvoke("SayHello", new object[0], result => base.EndInvoke("SayHello", new object[0], result), null);
        }
    }

    internal static class Program {
        private static void Main() {
            var client = new TestServiceClient(new BasicHttpBinding(BasicHttpSecurityMode.None), new EndpointAddress("http://localhost:13377/"));
            client.SayHello();
            Console.ReadLine();
        }
    }
}
RoXX
  • 1,664
  • 1
  • 24
  • 28

1 Answers1

0

The reason is that calling BeginInvoke/EndInvoke pair is only allowed if the service contract has Begin.../End... pair of methods. WCF internally analyses contract interface and if it sees the method which looks like async one (starts from "Begin", has 3 or more parameters, etc), then it initializes some internal structures which are later used by EndInvoke (in the MapAsyncEndInputs).

If you want to use WCF infrastructure for async calls, then client-side service contract interface should be async-style. Alternatively, you need to wrap the whole channel object to provide async operations on top.

You can check this answer (option #3) to see how it was done for Task-based async pattern.

Igor Labutin
  • 1,406
  • 10
  • 10
  • Do you mean that the service must implement the Begin/End pair, or just that the client's version of the service contract must implement them? – StingyJack Sep 09 '18 at 23:34
  • WCF has the benefit of having independent async implementation of client and/or server. Therefore if you need async behavior on the client side, then you can implement async WCF client and keep service synchronous. – Igor Labutin Sep 10 '18 at 08:20