PerformancePoint Monitoring doesn't have as robust a prescription as Silverlight Blueprint for SharePoint, but yes, PerformancePoint Monitoring & Silverlight can be used together.
PerformancePoint dashboards are SharePoint web part pages, so web parts or master pages using Silverlight for menus, visualization, charting, or any other thing, are right at home in (or surrounding) a PerformancePoint dashboard.
Another integration point is the web service PmService.asmx. You can learn more about this service through PerformancePoint SDK documentation of its client proxy. In this article we’ll call the GenerateView method of this service to retrieve and work with a scorecard/KPI data set, and visualize that data in Silverlight 2.
The Objective
Here’s a simple scorecard containing everyone’s favorite KPI, “Freight Cost Below 29”.
We’ll render this same data lovingly within a Silverlight 2 application that looks like:
If a scorecard contained additional cells, we could obviously do a lot more… a value trend sparkline, or multiple KPI slices in a ticker-style UI… but we’ll start with a simple <Button/>.
Step 1: PmService.asmx & Cross-Domain Callers
Here’s an essential development/security tip: If our Silverlight application isn’t hosted at the same host/port as PmService.asmx, we’ll need to deal with cross-domain calling issues. Learn more at MSDN: Make a Service Available Across Domain Boundaries or Tim Heuer's excellent Silverlight blog.
Step 2: Creating/Configuring The Service Reference
Configuring a service reference for a Silverlight 2 project is familiar for those who've built .NET client/service applications, but perhaps not for PerformancePoint Monitoring developers… we can't use the previously mentioned client proxy because its assembly references the .NET client runtime, not the Silverlight runtime.
So we generate new, Silverlight-specific, client proxy and associated classes from WSDL by using Visual Studio’s “Add Service Reference” against http://myperformancepointserver:40000/WebService/PmService.asmx?wsdl.
The generated proxies contains references to “ArrayOfXElement”, which keeps them from compiling. For the purposes of this article, you can remove any code that references ArrayOfXElement until the proxy does compile.
Step 3: Call GenerateView
We create an instance of PmServiceSoapClient, the client proxy generated from WSDL, and call the service method GenerateView. The GUID parameters to GenerateView are Scorecard ID and ConfiguredView ID. (We find these in a.bswx file containing the scorecard.)
PmServiceSoapClient client = new PmServiceSoapClient();
client.GenerateViewCompleted += new EventHandler<GenerateViewCompletedEventArgs>(client_GenerateViewCompleted);
client.GenerateViewAsync(
new Guid("3449fb9f-458b-44d4-8509-f75e1ad99e78"),
new Guid("7bb670e0-2ad8-4ed9-a1e1-0e63003d4402"),
null,
null);
Step 4: Get The Scorecard Data Set
Silverlight forces the asynchronous calling pattern, and so the handler for GenerateViewCompleted’s called on a non-UI thread. It's important that changes to elements within the visual be made on the UI thread. Our method UpdateVisualization updates the visual using data from e.Result, so we use Dispatcher.BeginInvoke to be sure it’s called on the UI thread:
void client_GenerateViewCompleted(object sender, GenerateViewCompletedEventArgs e)
{
Dispatcher.BeginInvoke(() => UpdateVisualization(e.Result));
}
Step 5: Bind the GridViewData to the Visuals
Here's UpdateVisualization, which grabs the (0,0) GridCell and binds a few values to a button through a data model class:
public void UpdateVisualization(GridViewData gridViewData)
{
// This example works off the 1-row header, 1-column header
// structure of our simple example scorecard.
IEnumerable<GridHeaderItem> coordinates = new[] {
gridViewData.RootRowHeader.Children[0],
gridViewData.RootColumnHeader.Children[0] };
// GetCell is an extension method, see Appendix
GridCell cell = gridViewData.GetCell(coordinates);
string text = cell.DisplayElements[0].Description;
GridColor gridColor = cell.FormatInfo.BackColor;
button.DataContext = new DataModel {
Description = text,
StatusColor = new SolidColorBrush(Color.FromArgb(gridColor.A, gridColor.R, gridColor.G, gridColor.B))
};
}
Here’s the data model class:
public class DataModel
{
public string Description { get; set; }
public SolidColorBrush StatusColor { get; set; }
}
And the relevant XAML:
<Button x:Name="button" Height="21" Width="90">
<Button.Content>
<StackPanel Orientation="Horizontal">
<Ellipse Width="12" Height="12" Fill="{Binding StatusColor}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Description}" VerticalAlignment="Center"/>
</StackPanel>
</Button.Content>
</Button>
And the result:
This isn’t fancy, but hopefully it gets you past the major hurdles, and on to a much more interesting dynamic visualization using Silverlight 2.
Appendix: GetCell for GridViewData
Here's an extension method for the WSDL-generated GridViewData, GetCell. This is used to look up a cell for a given combination of headers.
The cell keying algorithm could change in future versions of PerformancePoint. Please let me know if you run into issues using this.
/// <summary>
/// Returns the GridCell for the coordinates defined by the GridHeaderItems. Returns
/// null if the coordinates refer to an empty/out-of-range cell.
/// </summary>
public static GridCell GetCell(this GridViewData t, IEnumerable<GridHeaderItem> coordinates)
{
string key = generateKey(coordinates);
DictionaryWrapperOfStringGridCell table = t.Cells.Table;
for (int i = 0; i < table.Keys.Length; i++)
{
if (table.Keys[i] == key)
{
return table.Values[i];
}
}
return null;
}
private static string generateKey(IEnumerable<GridHeaderItem> coordinates)
{
return coordinates
.Where(coordinate => null != coordinate.DimensionName)
.OrderBy(coordinate => coordinate.DimensionName)
.Select(coordinate => string.Format("{0}:{1}{2}_", coordinate.DimensionName, coordinate.DimensionValue, coordinate.Id))
.Aggregate(new StringBuilder(), (stringBuilder, keyPart) => stringBuilder.Append(keyPart), sb => sb.ToString());
}