Udostępnij za pośrednictwem


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 https://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.

  1. Create a new Silverlight Project (linked to an IIS Web Site)

    1. Launch Visual Web Developer from the Start Menu
    2. File->Open Web Site->Local IIS->Default Web Site. Click Open
    3. File->Add->New Project->Visual C#->Silverlight->Silverlight Application
    4. Name:SampleClient, Locaiton:c:\Demo,  Click OK
    5. 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.
    6. This will add a SampleClientTestPage.html to your Web site which we will run to test the application.
  2. Find a Web Service to consume

    1. In my case I searched using https://live.com for a Stock Quote Service which I found one at https://www.webservicex.net/stockquote.asmx
  3. Back at our Silverlight project, add a Service Reference to the WSDL

    1. Select the SampleClient project in the Solution Explorer window
    2. Project->Add Service Reference and type https://www.webservicex.net/stockquote.asmx in the Address and click Go
    3. Specify a friendly Namespace, in this case StockQuoteService
    4. Click OK
  4. Add a simple UI to call the Service

    1. In the Page.xaml editor type the following code inside the <UserControl></UserControl> tags:     <Grid x:Name="LayoutRoot" Background="Azure">
              <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>
    2. Right click the Button_Click text above and select the "Navigate to Event Handler" context menu.
    3. Enter the following code to call the Web Service     private void Button_Click(object sender, RoutedEventArgs e)
          {
              var service = new StockQuoteService.StockQuoteSoapClient();
              service.GetQuoteCompleted += service_GetQuoteCompleted;
              service.GetQuoteAsync(_symbolTextBox.Text);
          }
    4. 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.
    5. Finally, add the following function to handle the result of the Web Service     void service_GetQuoteCompleted(object sender, StockQuoteService.GetQuoteCompletedEventArgs e)
          {
              var el = System.Xml.Linq.XElement.Parse(e.Result);
              _resultsListBox.DataContext = el.Element("Stock").Elements();
          }
  5. Compile the application. Build->Build Solution.

  6. At this point we are ready to test our application, to run it just navigate to https://localhost/SampleClientTestPage.html or simply select the SampleClientTestPage.html in the Solution Explorer and click View In Browser.

  7. 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:

    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

  1. 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
  2. Modify the Web Service configuration to call a local Web Service instead
    1. Back in Visual Web Developer, open the file ServiceReferences.ClientConfig
    2. Modify the address="https://www.webservicex.net/stockquote.asmx" to be instead address="https://localhost/stockquote.asmx", it should look like:     <client>
              <endpoint address="https://localhost/stockquote.asmx"
      binding="basicHttpBinding" bindingConfiguration="StockQuoteSoap"
      contract="StockQuoteService.StockQuoteSoap" name="StockQuoteSoap" />
      </client>
  3. 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
    1. Add a new Web.config to the https://localhost project (Add new item->Web.config)
    2. Add the following content: <?xml version="1.0" encoding="UTF-8"?>
      <configuration>
          <system.webServer>
              <rewrite>
                  <rules>
                      <rule name="Stock Quote Forward" stopProcessing="true">
                          <match url="^stockquote.asmx$" />
      <action type="Rewrite" url="https://www.webservicex.net/stockquote.asmx" />
      </rule>
                  </rules>
              </rewrite>
          </system.webServer>
      </configuration>
  4. This rule basically uses regular expression to match the requests for StockQuote.asmx and forwards them to the real Web Service.
  5. Compile everything by running Build->Rebuild Solution
  6. Back in your browser refresh the page to get the new, enter MSFT in the symbol and press Go!
  7. And Voila!!! everything works.

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):

  1. Install Visual Web Developer 2008 Express
  2. Install Silverlight Tools for Visual Studio 2008 SP 1
  3. Install Application Request Routing for IIS 7.

Comments

  • Anonymous
    November 14, 2008
    PingBack from http://www.tmao.info/calling-web-services-from-silverlight-using-iis-70-and-arr/
  • Anonymous
    November 15, 2008
    Would this work with an https end point?
  • Anonymous
    November 15, 2008
    Yes it will, just specify the https:// url and it will work.
  • Anonymous
    March 17, 2009
    Hi is there any extra steps to enable ARR? i have installed it but my app always points to localhost instead of webservicex.net thanks
  • Anonymous
    March 17, 2009
    Where exactly should i place the rewrite tags? in the web.config of the web application?
  • Anonymous
    March 17, 2009
    Yes, you can place them in the web.config of the application. This is what will trigger the "proxy" capabilities of ARR.
  • Anonymous
    March 17, 2009
    By the way if you are using the RTW version you will need to go to the Application Request Routing Proxy feature at the Server - level and enable the proxy capabilities. For security reasons this "flag" was added on RTW.
  • Anonymous
    March 17, 2009
    Is that the URL rewriting feature? i cant see the Application Request Routing Proxy feature however i can see that my rule is recognized in that feature module, this is my rule   <rewrite>     <rules>       <rule name="Country Forward" stopProcessing="true">         <match url="^country.asmx$" />         <action type="Rewrite" url="http://www.webservicex.net/country.asmx" />       </rule>     </rules>   </rewrite>and this is how i call the service           var countrySvc = new ServiceCountryDetails.countrySoapClient(               new System.ServiceModel.BasicHttpBinding(),               new System.ServiceModel.EndpointAddress("http://www.webservicex.net/country.asmx"));           countrySvc.GetCurrenciesCompleted +=new EventHandler<Client.Data.ServiceCountryDetails.GetCurrenciesCompletedEventArgs>(               (s, a) =>               {                   foreach (var currencyCode in a.Result)                   {                   }               });           countrySvc.GetCurrenciesAsync();however i get the cross-domain exception when i hit the calling, am i doing something wrong? thanks
  • Anonymous
    March 17, 2009
    The comment has been removed
  • Anonymous
    March 17, 2009
    nop i only see the URL Rewrite feature, may i have a bad installation?antoher question i see webservicex.net has crossdomain.xml on the server however it looks its not enough to call web services directly, is there any other way to call services from this site? thanks for your help