The challenge today is really simple. All we have to do is to write Silverlight Vista Sidebar Gadget, that consumes either WCF, ASMX or REST based service. Really simple, isn’t it? Let’s start
Build server side services
We should start from services. This is very straight forward mission. Here the logic I want to implement
public string Echo(string input)
{
return string.Format("ACK from {0}", input);
}
Well, WCF? We should mark service and operation contracts. That’s all
[ServiceContract(Namespace = "")]
public class EchoService
{
[OperationContract]
public string Echo(string input)
{
return string.Format("ACK from WCF with {0}", input);
}}
This does not works. Why? Silverlight knows only consumes ASP.NET compatible (simplified) web services, thus we should add following attribute to the our class attributes collection
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class EchoService
{
Now, the service is discoverable and accessible by Silverlight. Great news. Now let’s put it into our shared host. Hmm, we got strange error: “Deploying WCF Services: This collection already contains an address with scheme http.” What the hell is it?
This is shared hosting problem. Your host provider uses virtual IP and host addresses and has number of different web services, sitting on the same shared host. How to solve it?
Simple, all you have to do is to specify your own service host factory. Here the example of classes to put into code behind
class SLHostFactory : ServiceHostFactory
{
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
SLHost customServiceHost =
new SLHost(serviceType, new Uri("[Your URL goes here]",UriKind.Absolute));
return customServiceHost;
}
}class SLHost : ServiceHost
{
public SLHost(Type serviceType, params Uri[] baseAddresses)
: base(serviceType, baseAddresses)
{ }
protected override void ApplyConfiguration()
{
base.ApplyConfiguration();
}
}
And one attribute into your service tag
Factory="SLHostFactory"
Now it works. So what’s next? Build ASMX web service. This is even simpler
[WebMethod]
public string Echo(string input)
{
return string.Format("ACK from web service with {0}", input);
}
We done, now either WCF and Web services are accessible from your Silverlight application. So, add Service reference and consume it
Building client side
Inside code behind of your Silverlight project, you should define two proxies – one for Web Service and another for WCF service. Bother services implements the same interface, so it should not be a problem
ServerEcho.EchoServiceClient proxy;
WebServiceEcho.EchoWebServiceSoapClient wsProxy;
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
proxy = new ServerEcho.EchoServiceClient();
proxy.EchoCompleted += new EventHandler<ServerEcho.EchoCompletedEventArgs>(proxy_EchoCompleted);wsProxy = new SLGadget.WebServiceEcho.EchoWebServiceSoapClient();
wsProxy.EchoCompleted += new EventHandler<SLGadget.WebServiceEcho.EchoCompletedEventArgs>(wsProxy_EchoCompleted);
}
Silverlight work only asynchronously, thus you should begin to understand, that synchronous programming is for pussies :). Consume it
private void WCF_Click(object sender, RoutedEventArgs e)
{
proxy.EchoAsync(txt.Text);
}private void WS_Click(object sender, RoutedEventArgs e)
{
wsProxy.EchoAsync(txt.Text);
}
And Update output
void wsProxy_EchoCompleted(object sender, SLGadget.WebServiceEcho.EchoCompletedEventArgs e)
{
txt.Text = e.Error == null ? e.Result : (e.Error.InnerException != null ? e.Error.InnerException.ToString() : e.Error.Message);
}void proxy_EchoCompleted(object sender, ServerEcho.EchoCompletedEventArgs e)
{
txt.Text = e.Error == null ? e.Result : (e.Error.InnerException != null ? e.Error.InnerException.ToString() : e.Error.Message);
}
Now let’s run it. What? Another error? Security? Access denied? Of cause you have no 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="*" />
</cross-domain-policy>
What? You have it and still getting the same error? Look into sniffer. You application is looking for other file, named clientaccesspolicy.xml. Why? According the documentation, you can use either… Hm, another bug with WCF consuming. Never mind, let’s put it too
<?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>
Very well, now we are ready to run our application. It works! So, the only thing we should do is to pack it into MyGadget.gadget directory and put inside %userprofile%\appdata\local\microsoft\windows sidebar\gadgets together with gadget.xml manifest.
But… It stopped working… What’s the problem?
Very client side networking in Silverlight
The problem is, that SideBar executes it’s gadgets with local path, not with network path. Silverlight cannot use any network provider, when running locally. Why? Actually I do not know (maybe to prevent local applications development). so what to do?
Simple! Microsoft SideBar knows to run cross domain AJAX without any warnings and problems. So why not to use external XmlHttp from JavaScript for network access. Let’s do it
First we should initialize XMLHttpRequest object in JavaSctipt
var xObj;
function getEchoWCF(text) {
if(xObj == null) {
xObj = new XMLHttpRequest();
}
else if(xObj) {
xObj.abort();
}
Then create SOAP request to WCF or WebService
var sURL = "[Path yo your service]";
//Build SOAP
var sReq = "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"><s:Body><Echo><input>"+text+"</input></Echo></s:Body></s:Envelope>";
xObj.open("POST", sURL, true);
xObj.setRequestHeader( "Content-Type", "text/xml; charset=utf-8" );
xObj.setRequestHeader( "Cache-Control", "no-cache" );
…
xObj.send(sReq);
After the request created and send we should handle result. So we need an access from HTML page, hosting Silverlight object to Silverlight. Simple. “ScriptableMember - ScriptableType”, remember?
[ScriptableType]
public partial class Page : UserControl
{
…
[ScriptableMember]
public void UpdateResponse(string result)
{
Now return the result
xObj.onreadystatechange = function() {
if (xObj.readyState === 4) {
if (xObj.status && xObj.status === 200) {
var control = document.getElementById("silverlightControl");
control.Content.Page.UpdateResponse(xObj.responseText);
}
}
But this is not enough. We also should know to call Javascript from Silverlight… This is really simple
private void JS_Click(object sender, RoutedEventArgs e)
{
HtmlPage.Window.Invoke("getEchoWCF", txt.Text);
}
We done. Now you can pack your Silverlight control, together with hosting HTML and Javascript into windows sidebar gadget and use it even with external network support.
Have a good day and be nice people.
1 comment:
This is brilliant post..really interesting to read and know many new things here.
web designing company
Post a Comment