13

Becuase monotouch compile to native code, so it has some limitation such as dynamic invoke is not allowed.

But I have a lot class in .net, that I use the ChannelFactory dynamic to invoke the wcf service: new ChannelFactory(myBinding, myEndpoint); Now in monotouch I should use the slsvcutil to generate the wcf proxy class, but the slsvcutil generate a lot of Unnecessary extra code (huge), and Makes consumers difficult to unit test, due to high coupling with the WCF infrastructure through the ClientBase class.

Is there a better solution except the ChannelFactory? I would rather write the code manually, have more control over how services are invoked such as the ChannelFactory.

==========

        ChannelFactory<IMyContract> factory = new ChannelFactory<IMyContract>(binding, endpointAddress);
        return factory.CreateChannel();   

//==> It throw exception: MonoTouch does not support dynamic proxy code generation. Override this method or its caller to return specific client proxy instance

JKennedy
  • 18,150
  • 17
  • 114
  • 198
BlueSky
  • 747
  • 6
  • 19
  • This also affects ClientBase clients of WCF services. _"MonoTouch does not support dynamic proxy code generation. Override this method or its caller to return specific client proxy instance"._ Trying to override the ChannelBase as described @Tyson's answer does not work as the `Invoke()` method is only available for some target frameworks and is not part of netstandard2.0. We are left with a Begin/EndInvoke() that only throws Null ref ex. – StingyJack Sep 10 '18 at 00:56
  • 1
    Hi @StingyJack ! Yes! That is what happends to me, I have no Invoke available. So, what I have to do to connect WCF from iOS in Xamarin.Forms with .Net standard 2.0? – Ignacio Sep 16 '18 at 18:54
  • @Ignacio - I don't know yet. You _should_ be able to use the set of `BeginInvoke()` and `EndInvoke()` inside the `ChannelBase` and accomplish the same as the `Invoke()`, but its not working for me. I get null reference exceptions, I think are because the runtime is not able to match the Begin invocation to the correct End invocation. I tried a few different ways, and still no joy = – StingyJack Sep 16 '18 at 22:05
  • 1
    @StingyJack I could make it work one minute ago! What I have done is regenerated the Connected Service Reference file with svcutil in the version of Visual Studio 15.8.1 with this command svcutil /async /tcv:Version35 https://... then using this on the sync methods: var iar = BeginInvoke("YourMethod", _args, null, null); return (string)EndInvoke("YourMethod", Array.Empty(), iar);. – Ignacio Sep 16 '18 at 22:13
  • Thanks for the tip @Ignacio, I don't actually have any service references or connected services (have long used IChannelFactory to enable sharing of types rather than generate new types at every service consumer), but if that works I'm sure I can find what is different between my attempt and the generated code. – StingyJack Sep 16 '18 at 23:09

2 Answers2

20

ChannelFactory<T> has a virtual method CreateChannel(). If this is not overridden, it uses dynamic code generation, which fails on MonoTouch.

The solution is to override it and provide your own compile-time implementation.

Below is an old service implementation of mine that at least used to work on MonoTouch. I split it up into 2 partial classes - the first one being linked in all builds, the 2nd only in the iOS builds (allowing the dynamic generation mechanism to still work on windows).
I've stripped it down to only contain 1 service call.

TransactionService.cs:

public partial class TransactionService : ClientBase<IConsumerService>, IConsumerService
{

    public TransactionService()
    {
    }

    public TransactionService(string endpointConfigurationName) : 
        base(endpointConfigurationName)
    {
    }

    public TransactionService(string endpointConfigurationName, string remoteAddress) : 
        base(endpointConfigurationName, remoteAddress)
    {
    }

    public TransactionService(string endpointConfigurationName, EndpointAddress remoteAddress) : 
        base(endpointConfigurationName, remoteAddress)
    {
    }

    public TransactionService(Binding binding, EndpointAddress remoteAddress) : 
        base(binding, remoteAddress)
    {
    }

    public AccountBalanceResponse GetAccountBalance( AccountBalanceQuery query )
    {
        return Channel.GetAccountBalance( query );
    }
}  

TransactionService.iOS.cs: ConsumerServiceClientChannel which executes the calls via reflection)

public partial class TransactionService
{
    protected override IConsumerService CreateChannel()
    {
        return new ConsumerServiceClientChannel(this);
    }

    private class ConsumerServiceClientChannel : ChannelBase<IConsumerService>, IConsumerService
    {

        public ConsumerServiceClientChannel(System.ServiceModel.ClientBase<IConsumerService> client) :
            base(client)
        {
        }

        // Sync version
        public AccountBalanceResponse GetAccountBalance(AccountBalanceQuery query)
        {
            object[] _args = new object[1];
            _args[0] = query;
            return (AccountBalanceResponse)base.Invoke("GetAccountBalance", _args);
        }

        // Async version
        public IAsyncResult BeginGetAccountBalance(AccountBalanceQuery query, AsyncCallback callback, object asyncState )
        {
            object[] _args = new object[1];
            _args[0] = query;
            return (IAsyncResult)base.BeginInvoke("GetAccountBalance", _args, callback, asyncState );
        }


        public AccountBalanceResponse EndGetAccountBalance(IAsyncResult asyncResult)
        {
            object[] _args = new object[0];
            return (AccountBalanceResponse)base.EndInvoke("GetAccountBalance", _args, asyncResult);
        }

    }
}

EDIT: I just tested this with the latest MT (5.2) - it no longer needs all that extra boiler plate I had in there before, just the CreateChannel() override. I've cleaned up the sample code to match.

EDIT2: I added an async method implementation.

Tyson
  • 14,726
  • 6
  • 31
  • 43
  • Here it says it cannot find the class ChannelBase. And I do not see how I can use this with asynchronous methods. Thank you! – Jonas Sourlier Apr 16 '12 at 11:36
  • @cheeesus ChannelBase is within the `System.ServiceModel.Channels` namespace - Make sure you have the required using statement. – Tyson Apr 16 '12 at 11:39
  • 1
    @cheeesus You simply call `BeginInvoke` rather than `Invoke` (but still send the synchronous style method name string (e.g. dont prefix 'Begin' on the start of the method name passed in). I'll edit the answer. – Tyson Apr 16 '12 at 11:41
  • thanks, that did it. I wonder why it did not work when I tried it that way in the first place. – Jonas Sourlier Apr 16 '12 at 12:49
  • This helps. I ended up creating a T4 template, made it generate, added some pre-processor directives, so I can target Monotouch, or keep it standard. Took a few hours to get it going, but wouldn't have got this far without this post as reference. Also, if you are doing Monotouch (or anything these modern days), you should only be using the Async functions, so throw in a "Not implemented" exception for the Synchronous calls to prevent lazy programming :). Thanks for the hints! – TravisWhidden Feb 20 '15 at 16:32
  • I know this is old, sorry for the necro... how do you get the partial class to only compile for iOS? – Jesse Sep 08 '16 at 18:24
  • 1
    @Jesse This is going way back now, but from memory I think I had them in separate files and only included the ios specific class in the ios project. Or just use build flags and #if's. – Tyson Sep 09 '16 at 07:27
  • @Tyson Thanks for your clear sample! I have a problem here... it says: ChannelBase does not contain a definition for Invoke... What can I do? Thank you in advanced. – Ignacio Sep 16 '18 at 15:21
1

I think you might be confusing terms here - ChannelFactory is a generic type, not a dynamic.

According to MonoTouch documentation, although there's limitations to the Generics support in MonoTouch, ChannelFactory should be okay here.

Have you tried using ChannelFactory?