Tuesday, July 28, 2009

Silverlight Client Access Policy

How to consume a WCF service from a Silverlight application, where the WCF service is not hosted in IIS.  When a service is not hosted in IIS you have to serve the client access policy xml file manually to the client.  IIS does this job for you if you're hosting using IIS. (Not sure about WAS would be interested to find out at some point).  

Without serving the client policy xml file you will get a SecurityException. Something along the lines of:
{System.Security.SecurityException ---> System.Security.SecurityException: Security error. 
at MS.Internal.InternalWebRequest.Send() 
at System.Net.BrowserHttpWebRequest.BeginGetResponseImplementation() 
at System.Net.BrowserHttpWebRequest.InternalBeginGetResponse(AsyncCallback callback, Object state) 
at System.Net.AsyncHelper.<>c__DisplayClass4.b__3(Object sendState) --- End of inner exception stack trace --- 
at System.Net.AsyncHelper.BeginOnUI(BeginMethod beginMethod, AsyncCallback callback, Object state) 
at System.Net.BrowserHttpWebRequest.BeginGetResponse(AsyncCallback callback, Object state) 
at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelAsyncRequest.CompleteSend(IAsyncResult result) 
at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelAsyncRequest.OnSend(IAsyncResult result)}

Here's a solution:


You need to modify you're WCF service app.config.
    1 <?xml version="1.0" encoding="utf-8" ?>
    2 <configuration>
    3   <system.serviceModel>
    4     <behaviors>
    5       <serviceBehaviors>
    6         <behavior name="enableMetaData" >
    7           <serviceMetadata httpGetEnabled="true" />
    8         </behavior>
    9       </serviceBehaviors>
   10       <endpointBehaviors>
   11         <behavior name="webHttpBehavior">
   12           <webHttp/>
   13         </behavior>
   14       </endpointBehaviors>
   15     </behaviors>
   16     <services>
   17       <service name="SampleNamespace.MyService" behaviorConfiguration="enableMetaData">
   18         <host >
   19           <baseAddresses>
   20             <add baseAddress="net.tcp://localhost:9000"/>
   21             <add baseAddress="http://localhost:9001"/>
   22           </baseAddresses>
   23         </host>
   24         <endpoint address="Calculator" binding="netTcpBinding" contract="SampleNamespace.IMyService" />
   25         <endpoint address="Calculator" binding="basicHttpBinding" contract="SampleNamespace.IMyService" />
   26         <endpoint address="" binding="webHttpBinding" behaviorConfiguration="webHttpBehavior" contract="SampleNamespace.IClientAccessPolicy" />
   27       </service>
   28     </services>
   29   </system.serviceModel>
   30 </configuration>


Add an endpoint to your service for the IClientAccessPolicy. It needs to be Http.  Notice the behaviours are configured to allow http Get.

[ServiceContract]
public interface IClientAccessPolicy
{
    [OperationContract, WebGet(UriTemplate = "/clientaccesspolicy.xml")]
    Stream GetClientAccessPolicy();
}
...

Add the implementation to the existing service...
public class MyService : IMyService, IClientAccessPolicy
    {
        //Existing code...
 
        public Stream GetClientAccessPolicy()
        {
            const string result = @"<?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>";
 
            if (WebOperationContext.Current != null)
                WebOperationContext.Current.OutgoingResponse.ContentType = "application/xml";
            return new MemoryStream(Encoding.UTF8.GetBytes(result));
        }
    }
Navigating to http://localhost:9001/clientaccesspolicy.xml should give this result in your browser:<?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>