Wikipedia defines a mashup application as “a Web page or application that uses and combines data, presentation or functionality from two or more sources to create new services.”

The mashup application that’s shown in Figure 1 certainly fits this definition. This application lets users browse through customer records and associated orders, map a customer’s location, and display and rate videos that are submitted by customers. These are the kinds of capabilities that I’ll explore in this two-part series as I walk you through the process of creating mashup applications in SharePoint 2010.

 

Figure 1: Sample mashup application in SharePoint
Figure 1: Sample mashup application in SharePoint

 

Before I started to develop my sample mashup application, I defined the following goals for the application’s design and functionality:

  • no page refreshes
  • sandbox compatibility
  • a flexible component-oriented design that permits easy modification and multiple configuration options

Throughout this article, I’ll point out how each of these goals was achieved.

 

Integrating External Data

The sample mashup application combines data from five different data sources. These are the Adventureworks SQL Server database, a comma-delimited text file, Bing maps, videos stored in a SharePoint Assets library, and ratings data stored in SharePoint’s social database. This article focuses on the first three data sources.
 

Figure 2: Customer Order Inspector Silverlight application components
Figure 2: Customer Order Inspector Silverlight application components

 

The mashup application contains three main components. The first component is a Silverlight application that lets you examine customer data, as shown in Figure 2. I refer to this as the Customer Order Inspector Silverlight application. The out-of-the-box Silverlight Web Part displays the Customer Order Inspector Silverlight application on the SharePoint website. In this case, the Chrome Type property for the Web Part is set to None to hide the Web Part title (see Figure 3).

 

Figure 3: Setting the Chrome Type property in the Web Part tool pane
Figure 3: Setting the Chrome Type property in the Web Part tool pane

 

The customer data that is displayed in the Silverlight application is stored in the Adventureworks SQL database and also in the comma-delimited text file. You use a .NET Assembly Connector to retrieve the data from the separate data sources. The .NET Assembly Connector connects to both the Microsoft SQL Server database and the comma-delimited text file. It queries both data sources and merges the query results into a single entity in Business Connectivity Services. See my article “Business Connectivity Services Part 2: Advanced Data Access Techniques” to learn how to create the .NET Assembly Connector that’s used in the mashup application. Figure 2 illustrates how the data sources map to the UI and to all components that support the Silverlight application.

After you deploy the .NET Assembly Connector to the SharePoint server, create an external list based on the external content type that the .NET Assembly Connector defines. The Silverlight application uses the Silverlight SharePoint client object model to query the data in the external list. In the Silverlight application, the loadCustomers method executes when the Silverlight application is initialized.

Listing 1 shows this process in action. First, the GetByTitle method creates a list object that corresponds to the external list. The external list is based on the external content type defined in the .NET Assembly Connector. Then a CAMLquery is created to return the customer information from the external list. You may notice that this pattern is identical to the pattern that’s used to query data from a regular SharePoint list. The GetItems method uses the CAML query to return a ListItemCollection class that, in turn, is loaded into the ClientContext class. Finally, the success and failure callback methods are registered, and the ExecuteQueryAsync method invokes the operations on the SharePoint server.

Using this approach to query the external data maintains sandbox compatibility. You can also maintain sandbox compatibility by writing this component as a Web Part and leveraging the SharePoint managed client object model or the ECMA client object models to query the external list. In Part 2 of this series, I’ll demonstrate how this is done by using the ECMA client object model. You may also learn more about this pattern in my third BCS article, “Business Connectivity Services, Part 3: Building Custom UI Components.” Note that you can’t use the BDC runtime APIs to query the data in a sandbox-compatible solution. The BDC runtime APIs are not available in Silverlight applications. Additionally, they can be used only in Web Parts that run with full trust.
 

 

Adding Mapping Capabilities

The second component in the mashup application is the mapping component. I refer to this as the Silverlight Mapping application. You can see this application in Figure 4. Just as I did with the Customer Order Inspector Silverlight application, I use the out-of-the-box Silverlight Web Part to display the Silverlight Mapping application on the SharePoint website. For this piece of the mashup application, the Chrome Type property for the Silverlight Web Part is set to the default value. This makes the Web Part title, ”Silverlight Web Part (2),” visible. You can change the title in the Web Part tool pane. I set this Web Part’s Chrome Type property differently to help demonstrate the flexibility you have to define UI elements in the mashup application when you take this approach.

 

Figure 4: Mapping components in the mashup application
Figure 4: Mapping components in the mashup application

 

 


The Silverlight Mapping application receives an address from the Customer Order Inspector Silverlight application when you click the Locate Customer button. The Silverlight Mapping application uses the Silverlight Bing Maps control to display the satellite image for the address. Although this example uses Bing maps, you can use any mapping technology in a mashup application. To add the Silverlight maps control to a Silverlight application, you first must include the namespace in the Silverlight user control, as follows:

xmlns:m="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl"

After you register the namespace, add the Silverlight maps control to the Silverlight application by using XAML code, as follows:

<m:Map x:Name="MyMap" Mode="AerialWithLabels" CredentialsProvider="..." Margin="0,0,0,15" />

 

Silverlight-to-Silverlight Communication

Because one of the goals for the mashup application is to make sure that no page refreshes occur, I have the two Silverlight applications communicating directly with each other. In this example, the Silverlight Customer Order Inspector application passes the address for the currently selected customer directly to the Silverlight Mapping application. This process eliminates the need to use QueryString variables or other methods that require server-side processing and page refreshes.

To pass data between Silverlight applications, you must write specific code in both the sending and the receiving Silverlight applications. The System.Windows.Messaging namespace contains classes that support this type of communication. When you click the Locate Customer button, the locateCustomerButton_Click event handler (see Listing 2) in the Customer Order Inspector Silverlight application fires. First, the method determines whether a customer is selected in the application. If a customer is selected, an instance of the LocalMessageSender class is constructed. The constructor reads the name of the Silverlight application to which it will send data, and also specifies the domain to which to send the message. These elements define the messaging channel that the Silverlight applications use to communicate. The SendAsync method takes the data from the Customer object that’s associated with the currently selected customer and sends it to the receiving Silverlight application.

Listing 3 shows the code that’s used to receive the message from the Customer Order Inspector Silverlight application. First, a generic list of strings is created. This generic list contains the domains from which the receiving Silverlight application (in this case, the Silverlight Mapping application) can accept messages. In this scenario, the added domains are intranet.contoso.com and localhost. This practice makes the mashup application more secure by limiting the number of domains from which the Silverlight application can receive messages. Next, an instance of the LocalMessageReceiver class is constructed. The constructor defines the name of the receiving Silverlight application, and also specifies the domains the Silverlight application will accept messages from. These elements define the messaging channel the Silverlight application uses to communicate.

Next, the MessageReceived event handler is registered with the instance of the LocalMessageReceiver class. This event handler fires when the Silverlight application receives a message from another Silverlight application. In this example, I have called the GeocodeAddress method, and the Message property in the MessageReceivedEventArgs class is used to pass in the address that’s received from the Customer Orders Inspector Silverlight application.

Finally, you call the Listen method on the instance of the LocalMessageReceiver class. If you do not call this method, the receiver Silverlight application never processes any messages that are sent to it. Essentially, this method turns on message receiving capabilities.

The GeocodeAddress method (see Listing 4) uses the Bing APIs to execute an Asynchronous request to the Bing Maps Web services.

Finally, the client_GeocodeCompleted method (see Listing 5) processes the result from the Bing Maps API and updates the maps control in the Silverlight Mapping application. To programmatically interact with the Silverlight maps control, you use classes in the Microsoft.Maps.MapControl namespace.

Both the GeocodeAddress and client_GeocodeCompleted methods are taken from the Bing Maps Silverlight Control interactive SDK, and are slightly modified for this example. See the Bing Maps Silverlight Control Interactive SDK page for more information about geocoding an address and displaying it in the Silverlight Bing Maps control.

 

Functionality That’s Intriguing and Easy to Build

A mashup application that pulls data from multiple external data sources and services can provide powerful functionality in many line of business (LOB) applications. Creating a mashup application is rather straightforward. In this example, I combined data from a SQL Server database and a text file, created an external list based on the combined data source, queried the data from a Silverlight application, and provided complementary mapping functionality. I also met my mashup goals because all this functionality is sandbox-compatible and occurs without any page refreshes.

In Part 2 of this series, I'll enhance the mashup application and demonstrate how to integrate videos that are stored in a SharePoint Assets library by using the out-of-the-box SharePoint Silverlight Media Player. I’ll also demonstrate how to use the SharePoint ratings features to rate the videos and how to use Silverlight-to-web-page communication to make sure that no page refreshes occur.

 

 

Listing 1: Using the Silverlight SharePoint client object model to query an external list

private void loadCustomers()

{

             List customersList = Common.ClientContext.Web.Lists.GetByTitle("Customers");

             CamlQuery query = new Microsoft.SharePoint.Client.CamlQuery();

  query.ViewXml = @"<View>" +

                                         @"<Query><OrderBy>" +

                                                     @"<FieldRef Name='City' Ascending='True' />" +

                                  @"</OrderBy></Query>" +

                                                        @"<ViewFields>" +

                                      @"<FieldRef Name='Identifier1' />" +

                                      @"<FieldRef Name='FirstName' />" +

                                      @"<FieldRef Name='LastName' />" +

                                      @"<FieldRef Name='Address' />" +

                                      @"<FieldRef Name='City' />" +

                                      @"<FieldRef Name='State' />" +

                                      @"<FieldRef Name='PostalCode' />" +

                                      @"<FieldRef Name='Country' />" +

                                      @"<FieldRef Name='EmailAddress' />" +

                                      @"<FieldRef Name='Phone' />" +

                                      @"<FieldRef Name='ShippingCompany' />" +

                                      @"<FieldRef Name='ShippingAccountNumber' />" +

                                  @"</ViewFields>" +

                              @"</View>";

            Common.Customers = customersList.GetItems(query);

            Common.ClientContext.Load(Common.Customers);           

            Common.ClientContext.ExecuteQueryAsync(onQuerySucceeded, onQueryFailed);

            statusTextBlock.Text += "Loading Customers . . .\n";

Listing 2: Using the LocalMessageSender class to send a message to another Silverlight application

using System.Windows.Messaging;

private void locateCustomerButton_Click(object sender, RoutedEventArgs e)

{

   if (Common.Customer == null)

   {

      HtmlPage.Window.Alert("Select a customer.");

   }

   else

   {

      LocalMessageSender localMessageSender =

        new LocalMessageSender("Silverlight Mapping Application",

      System.Windows.Messaging.LocalMessageSender.Global);

      localMessageSender.SendAsync(Common.Customer.Address + " " +

      Common.Customer.City + " " +

      Common.Customer.State + " " +

      Common.Customer.PostalCode);

   }

}

Listing 3: Using the LocalMessageReceiver class to receive a message from another Silverlight application

List<string> allowedSenderDomains = new List<string>();

allowedSenderDomains.Add("intranet.contoso.com");

allowedSenderDomains.Add("localhost");

LocalMessageReceiver receiver = new LocalMessageReceiver("Silverlight Mapping Application",

   ReceiverNameScope.Global, allowedSenderDomains);

receiver.MessageReceived += (object sender, MessageReceivedEventArgs e) =>

{               

   GeocodeAddress(e.Message.ToString());               

};

receiver.Listen();

Listing 4: Using the Bing Maps API to geocode an address asynchronously

private void GeocodeAddress(string address)

{

   PlatformServices.GeocodeRequest request = new PlatformServices.GeocodeRequest();

   request.Culture = MyMap.Culture;

   request.Query = address;

   request.ExecutionOptions = new PlatformServices.ExecutionOptions();

   request.ExecutionOptions.SuppressFaults = true;

   request.Options = new PlatformServices.GeocodeOptions();

   request.Options.Filters = new ObservableCollection<PlatformServices.FilterBase>();

   PlatformServices.ConfidenceFilter filter = new PlatformServices.ConfidenceFilter();

   filter.MinimumConfidence = PlatformServices.Confidence.High;

   request.Options.Filters.Add(filter);

   Output.Text = "Geocoding address: " + address;

   geocodesInProgress++;

   MyMap.CredentialsProvider.GetCredentials(

    (Credentials credentials) =>

   {

      request.Credentials = credentials;

      GeocodeClient.GeocodeAsync(request, address);

   });

}

Listing 5: Using the Bing Maps API to update the Silverlight map control

using Microsoft.Maps.MapControl;

private void client_GeocodeCompleted(object sender, PlatformServices.GeocodeCompletedEventArgs e)

{

   string outString;

   geocodesInProgress--;

   try

   {

      if (e.Result.ResponseSummary.StatusCode != PlatformServices.ResponseStatusCode.Success)

      {

         outString = "Error Geocoding. Status code: " + e.Result.ResponseSummary.StatusCode.ToString();

      }

      else if (0 == e.Result.Results.Count)

      {

         outString = "No results found.";

      }

      else

      {

         outString = "Address located: " + e.Result.Results[0].DisplayName;

         Location loc = GeocodeLayer.AddResult(e.Result.Results[0]);

         MyMap.SetView(loc, 18);

      }

   }

   catch

   {

      outString = "Exception raised.";

   }

   Output.Text = outString;

}