Calling Web Services from Silverlight using IIS 7.0 and ARR
During this PDC I attended Ian's presentation about WPF and Silverlight where he demonstrated the high degree of compatibility that can be achieved between a WPF desktop application and a Silverlight application. One of the differences that he demonstrated was when your application consumed Web Services since Silverlight applications execute in a sandboxed environment they are not allowed to call random Web Services or issue HTTP requests to servers that are not the originating server, or a server that exposes a cross-domain manifest stating that it is allowed to be called by clients from that domain.
Then he moved to show how you can work around this architectural difference by writing your own Web Service or HTTP end-point that basically gets the request from the client and using code on the server just calls the real Web Service. This way the client sees only the originating server and it allows the call to succeed, and the server can freely call the real Web Service. Funny enough while searching for a Quote Service I ran into an article from Dino Esposito in MSDN magazine where he explains the same issue and also exposes a "Compatibility Layer" which again is just code (more than 40 lines of code) to act as proxy to call a Web Service (except he uses the JSON serializer to return the values).
The obvious disadvantage is that this means you have to write code that only forwards the request and returns the response acting essentially as a proxy. Of course this can be very simple, but if the Web Service you are trying to call has any degree of complexity where custom types are being sent around, or if you actually need to consume several methods exposed by it, then it quickly becomes a big maintenance nightmare trying to keep them in sync when they change and having to do error handling properly, as well as dealing with differences when reporting network issues, soap exceptions, http exceptions, etc.
So after looking at this, I immediately thought about ARR (Application Request Routing) which is a new extension for IIS 7.0 (see http://www.iis.net/extensions) that you can download for free from IIS.NET for Windows 2008, that among many other things is capable of doing this kind of routing without writing a single line of code.
This blog tries to show how easy it is to implement this using ARR. Here are the steps to try this: (below you can find the software required), note that if you are only interested in what is really new just go to 'Enter ARR' section below to see the configuration that fix the Web Service call.
- Create a new Silverlight Project (linked to an IIS Web Site)
- Launch Visual Web Developer from the Start Menu
- File->Open Web Site->Local IIS->Default Web Site. Click Open
- File->Add->New Project->Visual C#->Silverlight->Silverlight Application
- Name:SampleClient, Locaiton:c:\Demo, Click OK
- On the "Add Silverlight Application" dialog choose the "Link this Silverlight control into an existing Web site", and choose the Web site in the combo box.
- This will add a SampleClientTestPage.html to your Web site which we will run to test the application.
- Find a Web Service to consume
- In my case I searched using http://live.com for a Stock Quote Service which I found one at http://www.webservicex.net/stockquote.asmx
- Back at our Silverlight project, add a Service Reference to the WSDL
- Select the SampleClient project in the Solution Explorer window
- Project->Add Service Reference and type http://www.webservicex.net/stockquote.asmx in the Address and click Go
- Specify a friendly Namespace, in this case StockQuoteService
- Click OK
- Add a simple UI to call the Service
- In the Page.xaml editor type the following code inside the <UserControl></UserControl> tags:
- Right click the Button_Click text above and select the "Navigate to Event Handler" context menu.
- Enter the following code to call the Web Service
- Now, since we are going to use XLINQ to parse the result of the Web Service which is an XML then we need to add the reference to System.Xml.Linq by using the Project->Add Reference->System.Xml.Linq.
- Finally, add the following function to handle the result of the Web Service
- Compile the application. Build->Build Solution.
- At this point we are ready to test our application, to run it just navigate to http://localhost/SampleClientTestPage.html or simply select the SampleClientTestPage.html in the Solution Explorer and click View In Browser.
- Enter a Stock Symbol (say MSFT) and press Go!, Verify that it breaks. You will see a small "Error in page" with a Warning icon in the status bar. If you click that and select show details you will get a dialog with the following message:
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="50" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="Symbol:" />
<TextBox Grid.Column="1" Grid.Row="0" x:Name="_symbolTextBox" />
<Button Grid.Column="2" Grid.Row="0" Content="Go!" Click="Button_Click" />
<ListBox Grid.Column="0" Grid.Row="1" x:Name="_resultsListBox" Grid.ColumnSpan="3"
ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold" Foreground="DarkBlue" />
<TextBlock Text=" = " />
<TextBlock Text="{Binding Path=Value}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
{
var service = new StockQuoteService.StockQuoteSoapClient();
service.GetQuoteCompleted += service_GetQuoteCompleted;
service.GetQuoteAsync(_symbolTextBox.Text);
}
{
var el = System.Xml.Linq.XElement.Parse(e.Result);
_resultsListBox.DataContext = el.Element("Stock").Elements();
}
Message: Unhandled Error in Silverlight 2 Application An exception occurred during the operation, making the result invalid.
Enter Application Request Routing and IIS 7.0
- Ok, so now we are running into the cross-domain issue, and unfortunately we don't have a cross-domain here is where ARR can help us call the service without writing more code
- Modify the Web Service configuration to call a local Web Service instead
- Back in Visual Web Developer, open the file ServiceReferences.ClientConfig
- Modify the address="http://www.webservicex.net/stockquote.asmx" to be instead address="http://localhost/stockquote.asmx", it should look like:
- This will cause the client to call the Web Service in the same originating server, now we can configure ARR/URL Rewrite rule to route the Web Service requests to the original end-point
- Add a new Web.config to the http://localhost project (Add new item->Web.config)
- Add the following content:
- This rule basically uses regular expression to match the requests for StockQuote.asmx and forwards them to the real Web Service.
- Compile everything by running Build->Rebuild Solution
- Back in your browser refresh the page to get the new, enter MSFT in the symbol and press Go!
- And Voila!!! everything works.
<endpoint address="http://localhost/stockquote.asmx"
binding="basicHttpBinding" bindingConfiguration="StockQuoteSoap"
contract="StockQuoteService.StockQuoteSoap" name="StockQuoteSoap" />
</client>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="Stock Quote Forward" stopProcessing="true">
<match url="^stockquote.asmx$" />
<action type="Rewrite" url="http://www.webservicex.net/stockquote.asmx" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
Summary
One of the features offered by ARR is to provide proxy functionality to forward requests to another server. One of the scenarios where this functionality is useful is when using it from clients that cannot make calls directly to the real data, this includes Silverlight, Flash and AJAX applications. As shown in this blog, by just using a few lines of XML configuration you can enable clients to call services in other domains without having to write hundreds of lines of code for each method. It also means that I get the original data and that if the WSDL were to change I do not need to update any wrappers. Additionally if using REST based services you could use local caching in your server relying on Output Cache and increase the performance of your applications significantly (again with no code changes).
Software used
Here is the software I installed to do this sample(amazing that all of it is completely free):