Search code examples
silverlightwcfcommunicationexception

WCF Silverlight CommunicationException


This post will be lengthy and I apologize on beforehand for that.

I'm trying to make a a Silverlight application communicate with a service I'm running from visual studio, it's not hosted in IIS at the moment. I'm trying to have as little code and configuration as possible for this prototype, just the bare minimum. The internet is both full of questions as answers regarding this, but none of them seem to apply to my particular predicament.

My service solution has a SimpleServiceAsyncResult, which implements the IAsyncResult interface, a service interface, ISimpleService, and the actual implementation of the service in SimpleService. This service is implemented as follows:

public class SimpleService : ISimpleService
{
    public IAsyncResult BeginSimpleMethod(string msg, AsyncCallback callback, object state)
    {
        return new SimpleServiceAsyncResult<string>(msg);
    }

    public string EndSimpleMethod(IAsyncResult result)
    {
        SimpleServiceAsyncResult<string> res = result as SimpleServiceAsyncResult<string>;
        return res.Result;
    }

    public string SimpleMethod(string msg)
    {
        return msg;
    }


    public Message ProvideCrossDomainFile()
    {
        FileStream fileStream = File.Open(@"CrossDomain.xml", FileMode.Open, FileAccess.Read, FileShare.Read);

        XmlReaderSettings settings = new XmlReaderSettings();
        settings.DtdProcessing = DtdProcessing.Parse;

        XmlReader reader = XmlReader.Create(fileStream, settings);

        Message result =  Message.CreateMessage(MessageVersion.None, "", reader);
        return result;
    }

    public Message ProvidePolicyFile()
    {
        FileStream fileStream = File.Open(@"ClientAccessPolicy.xml", FileMode.Open, FileAccess.Read, FileShare.Read);
        XmlReader reader = XmlReader.Create(fileStream);
        Message result = Message.CreateMessage(MessageVersion.None, "", reader);
        return result;
    }
}

There are 2 xml files in the project; ClientAccessPolicy.xml and CrossDomain.xml, which look like:

ClientAccessPolicy.xml

<?xml version="1.0" encoding="utf-8"?>
<access-policy>
  <cross-domain-access>
    <policy>
      <allow-from http-request-headers="*">
        <domain uri="*"/>
      </allow-from>
      <grant-to>
        <resource path="/" include-subpaths="true"/>
      </grant-to>
    </policy>
  </cross-domain-access>
</access-policy>

CrossDomain.xml

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
  <allow-access-from domain="*" />
  <allow-http-request-headers-from domain="*" headers="SOAPAction" />
</cross-domain-policy> 

Along with this is a App.config, which looks like:

App.config

<behaviors>
  <serviceBehaviors>
    <behavior name="SimpleServiceBehavior">
      <serviceMetadata httpGetEnabled="True"/>
    </behavior>
  </serviceBehaviors>
</behaviors>

Now, I wanted to see if all this goodness would actually work. So I created a solution with a console project, with only a Program.cs and App.config.

The program is of course nothing special, just the bare minimum:

       var factory = new ChannelFactory<ISimpleService>("SimpleService");
        ISimpleService service = factory.CreateChannel();

        var asResult = service.BeginSimpleMethod("tesmsg", null, null);

        Console.WriteLine("Data: " + service.EndSimpleMethod(asResult);

This all works nicely, I get the data I'm looking for back, so I'm happy. The next thing I wanted to achieve was getting some data from the same service, with a Silverlight app.

For this to happen I created a Silverlight project in my Client solution, created the service reference and did soms plumbing. I'm trying to get SimpleMethod executed by adding some code in my MainPage.xaml.cs:

    void SimpleMethodCompletedProxyCallback(object sender, SimpleMethodCompletedEventArgs e)
    {
        if (e.Error == null)
        {
            data = e.Result;
        }
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        SimpleServiceClient proxy = new SimpleServiceClient();

        proxy.SimpleMethodCompleted += new EventHandler<SimpleMethodCompletedEventArgs>(SimpleMethodCompletedProxyCallback);
        try
        {

            proxy.SimpleMethodAsync("teststring");
        }
        catch (CommunicationException cex)
        {
            label1.Content = cex.Message;
        }
    }

With this plumbing done I thought I would be able to get some data, but it keeps throwing a CommunicationException from the generated ServiceReference code, when it tries to invoke SimpleMethod:

        public string EndSimpleMethod(System.IAsyncResult result) {
            object[] _args = new object[0];
            string _result = ((string)(base.EndInvoke("SimpleMethod", _args, result)));
            return _result;
        }

I've been surfing the web about this for a few hours, ending up with the CrossDomain.xml and ClientAccessPolicy.xml and the methods that would expose this, but whatever I try, i keep running into the same exception. i have absolutely no idea what to do next and am hoping you can help me a bit further with this.

Kind regards


Solution

  • In your service code, the SimpleService class implements one interface (ISimpleService), which I'm assuming is decorated with [ServiceContract] and defines five operation contracts (BeginSimpleMethod, EndSimpleMethod, SimpleMethod, ProvideCrossDomainFile, ProvidePolicyFile). Maybe ProvidePolicyFile is decorated with a [WebGet(UriTemplate = "/clientaccesspolicy.xml")] as well.

    Now, since you only have one interface, you have one of two things: the endpoint in the server is a SOAP endpoint (e.g., basicHttpBinding-based), or the endpoint is a REST endpoint (i.e., webHttpBinding/webHttp behavior).

    If it's the former, then the location of the operation to return the clientaccesspolicy.xml is not correct. Per the remarks section on the documentation for WebGetAttribute, it's a passive behavior which will only be honored if the endpoint has a WebHttpBehavior. Silverlight will be looking for that policy file in one place (http://your_service/clientaccesspolicy.xml) and it will be accessible somewhere else (http://your_service/endpoint, with a SOAPAction header indicating the method).

    If it's the latter, then Silverlight cannot by default call those endpoints (see posts for calling REST/POX and REST/JSON endpoints from SL), so it won't work either.

    The post at http://blogs.msdn.com/b/carlosfigueira/archive/2008/03/07/enabling-cross-domain-calls-for-silverlight-apps-on-self-hosted-web-services.aspx is AFAIK the "canonical" way for enabling SL to call services on self-hosted WCF services. You'll need two endpoints (likely in two interfaces), one for the service per se, one for the policy file(s). The post has more details on this problem.