Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

System.UnsupportedException using WCF on Windows Phone 7

Has anyone been able to communicate using WCF on Windows Phone Series 7 emulator?

I've been trying for the past two days and it's just happening for me. I can get a normal Silverlight control to work in both Silverlight 3 and Silverlight 4, but not the phone version. Here are two versions that I've tried:

Version 1 - Using Async Pattern

BasicHttpBinding basicHttpBinding = new BasicHttpBinding();
EndpointAddress endpointAddress = new EndpointAddress("http://localhost/wcf/Authentication.svc");
Wcf.IAuthentication auth1 = new ChannelFactory<Wcf.IAuthentication>(basicHttpBinding, endpointAddress).CreateChannel(endpointAddress);

AsyncCallback callback = (result) =>
{

    Action<string> write = (str) =>
    {
        this.Dispatcher.BeginInvoke(delegate
        {
            //Display something
        });
    };

    try
    {
        Wcf.IAuthentication auth = result.AsyncState as Wcf.IAuthentication;
        Wcf.AuthenticationResponse response = auth.EndLogin(result);
        write(response.Success.ToString());
    }
    catch (Exception ex)
    {
        write(ex.Message);
        System.Diagnostics.Debug.WriteLine(ex.Message);
    }
};

auth1.BeginLogin("user0", "test0", callback, auth1);

This version breaks on this line:

Wcf.IAuthentication auth1 = new ChannelFactory<Wcf.IAuthentication>(basicHttpBinding, endpointAddress).CreateChannel(endpointAddress);

Throwing System.NotSupportedException. The exception is not very descriptive and the callstack is equally not very helpful:


   at System.ServiceModel.DiagnosticUtility.ExceptionUtility.BuildMessage(Exception x)
   at System.ServiceModel.DiagnosticUtility.ExceptionUtility.LogException(Exception x)
   at System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(Exception e)
   at System.ServiceModel.ChannelFactory`1.CreateChannel(EndpointAddress address)
   at WindowsPhoneApplication2.MainPage.DoLogin()
   ....

Version 2 - Blocking WCF call

Here is the version that doesn't use the async pattern.

[System.ServiceModel.ServiceContract]
public interface IAuthentication
{
    [System.ServiceModel.OperationContract]
    AuthenticationResponse Login(string user, string password);
}

public class WcfClientBase<TChannel> : System.ServiceModel.ClientBase<TChannel> where TChannel : class {
        public WcfClientBase(string name, bool streaming)
            : base(GetBinding(streaming), GetEndpoint(name)) {
            ClientCredentials.UserName.UserName = WcfConfig.UserName;
            ClientCredentials.UserName.Password = WcfConfig.Password;
        }
        public WcfClientBase(string name) : this(name, false) {}

        private static System.ServiceModel.Channels.Binding GetBinding(bool streaming) {
            System.ServiceModel.BasicHttpBinding binding = new System.ServiceModel.BasicHttpBinding();
            binding.MaxReceivedMessageSize = 1073741824;
            if(streaming) {
                //binding.TransferMode = System.ServiceModel.TransferMode.Streamed;
            }
            /*if(XXXURLXXX.StartsWith("https")) {
                binding.Security.Mode = BasicHttpSecurityMode.Transport;
                binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
            }*/
            return binding;
        }

        private static System.ServiceModel.EndpointAddress GetEndpoint(string name) {
            return new System.ServiceModel.EndpointAddress(WcfConfig.Endpoint + name + ".svc");
        }

        protected override TChannel CreateChannel()
        {
            throw new System.NotImplementedException();
        }
    }


auth.Login("test0", "password0");

This version crashes in System.ServiceModel.ClientBase<TChannel> constructor. The call stack is a bit different:


   at System.Reflection.MethodInfo.get_ReturnParameter()
   at System.ServiceModel.Description.ServiceReflector.HasNoDisposableParameters(MethodInfo methodInfo)
   at System.ServiceModel.Description.TypeLoader.CreateOperationDescription(ContractDescription contractDescription, MethodInfo methodInfo, MessageDirection direction, ContractReflectionInfo reflectionInfo, ContractDescription declaringContract)
   at System.ServiceModel.Description.TypeLoader.CreateOperationDescriptions(ContractDescription contractDescription, ContractReflectionInfo reflectionInfo, Type contractToGetMethodsFrom, ContractDescription declaringContract, MessageDirection direction)
   at System.ServiceModel.Description.TypeLoader.CreateContractDescription(ServiceContractAttribute contractAttr, Type contractType, Type serviceType, ContractReflectionInfo& reflectionInfo, Object serviceImplementation)
   at System.ServiceModel.Description.TypeLoader.LoadContractDescriptionHelper(Type contractType, Type serviceType, Object serviceImplementation)
   at System.ServiceModel.Description.TypeLoader.LoadContractDescription(Type contractType)
   at System.ServiceModel.ChannelFactory1.CreateDescription()
   at System.ServiceModel.ChannelFactory.InitializeEndpoint(Binding binding, EndpointAddress address)
   at System.ServiceModel.ChannelFactory1..ctor(Binding binding, EndpointAddress remoteAddress)
   at System.ServiceModel.ClientBase1..ctor(Binding binding, EndpointAddress remoteAddress)
   at Wcf.WcfClientBase1..ctor(String name, Boolean streaming)
   at Wcf.WcfClientBase`1..ctor(String name)
   at Wcf.AuthenticationClient..ctor()
   at WindowsPhoneApplication2.MainPage.DoLogin()
   ...

Any ideas?

like image 638
Igor Zevaka Avatar asked Jan 22 '23 10:01

Igor Zevaka


1 Answers

As scottmarlowe pointed out, the automagicly generated service refrence just works. I have set upon the mission to work out just why the bloody hell it works and the manual version doesn't.

I found the culprit and it is ChannelFactory. For some reason new ChannelFactory<T>().CreateChannel() just throws an exception. The only solution I found is to provide your own implementation of the channel. This involves:

  1. Override ClientBase. (optional).
  2. Override ClientBase.CreateChannel. (optional).
  3. Subclass ChannelBase with a specific implementation of your WCF interface

Now, ClientBase already provides an instance of the channel factory thru ChannelFactory property. If you simply call CreateChannel off that you would get the same exception. You need to instantiate a channel that you define in step 3 from within CreateChannel.

This is the basic wireframe of how it all looks put together.

[DataContractAttribute]
public partial class AuthenticationResponse {
[DataMemberAttribute]
public bool Success {
    get; set;
}

[System.ServiceModel.ServiceContract]
public interface IAuthentication
{
    [System.ServiceModel.OperationContract(AsyncPattern = true)]
    IAsyncResult BeginLogin(string user, string password, AsyncCallback callback, object state);
    AuthenticationResponse EndLogin(IAsyncResult result);
}

public class AuthenticationClient : ClientBase<IAuthentication>, IAuthentication {

    public AuthenticationClient(System.ServiceModel.Channels.Binding b, EndpointAddress ea):base(b,ea)
    {
    }

    public IAsyncResult BeginLogin(string user, string password, AsyncCallback callback, object asyncState)
    {
        return base.Channel.BeginLogin(user, password, callback, asyncState);
    }

    public AuthenticationResponse EndLogin(IAsyncResult result)
    {
        return Channel.EndLogin(result: result);
    }

    protected override IAuthentication CreateChannel()
    {
        return new AuthenticationChannel(this);
    }

    private class AuthenticationChannel : ChannelBase<IAuthentication>, IAuthentication
    {
        public AuthenticationChannel(System.ServiceModel.ClientBase<IAuthentication> client)
        : base(client)
        {
        }

        public System.IAsyncResult BeginLogin(string user, string password, System.AsyncCallback callback, object asyncState)
        {
            object[] _args = new object[2];
            _args[0] = user;
            _args[1] = password;
            System.IAsyncResult _result = base.BeginInvoke("Login", _args, callback, asyncState);
            return _result;
        }

        public AuthenticationResponse EndLogin(System.IAsyncResult result)
        {
            object[] _args = new object[0];
            AuthenticationResponse _result = ((AuthenticationResponse)(base.EndInvoke("Login", _args, result)));
            return _result;
        }
    }
}

TLDR; If you want to use your own WCF code on WP7 you need to create your own channel class and not rely on ChannelFactory.

like image 107
Igor Zevaka Avatar answered Feb 05 '23 06:02

Igor Zevaka