Dec
16
Written by:
Mihail Mateev
12/16/2011 9:06 PM
NetAdvantage Reporting is a tool that allows developers to create modern, innovative, visually appealing reports by providing an easy-to-use design experience in Visual Studio.
This article is about how to use Infragistics Reporting with OData in Visual Studio LightSwitch.
First you will understand what is NetAdvantage Reporting and what are its main modules. At the end you will see two samples about report integration in LightSwitch.
In both cases is used RIA service wrapper for OData Source, but the approach to integration could be used with different data source.
Infragistics Reporting Modules
Reporting contains several main parts:
- Report Designer
- Report Data Explorer
- Report Service
- Controls
- Expression Assistant
Report Designer
Anatomy of the Report Designer
The Report Designer is the work environment of NetAdvantage Reporting, the place where you design and layout your reports.
The Report Designer is a Reporting System integrated by the following components:
- Alignment Configuration Toolbar - This toolbar is used for resizing and aligning the items placed on the Design Surface. For details, refer to The Alignment Configuration Toolbar.
- Designer Surface - The largest area of the Report Designer, divided into several sections. It is the actual work area in which you design your report. For details, refer to Knowing the Designer Surface and Report Sections.
- Report Data Explorer Window - This component is used to create, modify, and display data sources and parameters. For details, refer to The Report Data Explorer.
Report Data Explorer
The Report Data Explorer is part of the NetAdvantage Reporting work environment. This Report Designer component displays, in a treeview-like form, the available data sources (with their corresponding data fields) and the parameters’ definitions. You are allowed to create, modify, and display data sources and parameters.
Report Service (not used for LightSwitch)
The Report Service is a Windows Communication Foundation (WCF) service that allows the remote execution of NetAdvantage Reporting reports. It is typically used in scenarios in which reports are rendered in Silverlight or WPF clients, and connecting directly to the database is not an option. Reports are always processed on the server and the only information that the client receives is the final result.
NetAdvantage Reporting supports also client-side rendering in Silverlight, which works without involving the Report Service. This second scenario requires that the data needed for the report is already located in the client (i.e., in XML files, through RIA Services, or any other web service call). This is the approach, that we could use in LightSwitch applications.
Controls
Reporting controls represents a specific report component that has particular features and functionalities for displaying content, and can be inserted in any report at design-time. NetAdvantage Reporting comes with Table, Chart, Label, Image and Horizontal Line controls.
Expression Assistant
The Expression Assistant dialog is the tool you use to build the expressions needed for your report. This dialog has several distinct areas; the purpose of each of them is explained briefly below.
- Expression area - The expression you are building is displayed in the text field of this area.
- Operators area - In this area you can select operators to add to the expression.
- Fields, Variables and Parameters area - Select database fields, global variables, and parameters to add your expression.
- Functions area - All functions that you can add to the expression are available here.
Report Rendering
- Server-Side Rendering (not used in LightSwitch). In this case, the Viewer retrieves a report from a Reporting Service (ReportService.svc). This schema separates the report’s user interface handled by the Viewer (the client side) from the actual report and rendering engine that run on the server. Server-side rendering requires a running Report Service.
- Client-Side Rendering(the right approach in LightSwitch) NetAdvantage Reporting supports also client-side rendering in Silverlight, which works without involving the Report Service. This second scenario requires that the data needed for the report is already located in the client (i.e., in XML files, through RIA Services, or any other web service call). With client-side rendering the report is fully rendered by the client without any server participation.
RIA service wrapper for OData Source
RIA service wrapper is the approach that allows you to use OData in Visual Studio LightSwitch. A great article on this topic is How to create a RIA service wrapper for OData Source.
The basic steps of creating the RIA service wrapper are as follows:
- Create a class library project.
- Add a WCF Service Reference to the project to provide access to the external OData source.
- Add a WCF RIA DomainService to expose the OData DataServiceContext.
You could use the same approach to integrate Infragistics reports in LightSwitch applications. In the samples is demonstrated OData Northwind Test Service.
You need implementation of these steps after adding WCF RIA DomainService to expose the OData DataServiceContext.
Initialize DomainServiceContext.
You need to initialize the domain service context using the OData service. To make this you could override the Initialize method
private NorthwindServiceReference.NorthwindEntities _context;
public override void Initialize(System.ServiceModel.DomainServices.Server.DomainServiceContext context)
{
base.Initialize(context);
// Initialize the context.
// Or you can store the connection string in your web.config
_context = new NorthwindServiceReference.NorthwindEntities(new Uri("http://services.odata.org/Northwind/Northwind.svc"));
}
Expose Queries
You need to add functions to expose queries for each OData entity. You need to add a query function for each entity type that you like to expose. For LightSwitch each entity type should has a parameterless query exposed, with the QueryAttribute applied. This allows us to identify which query represents the “Select *” operation for that entity type. Then you acn apply additional filters to this query from LightSwitch.
[Query(IsDefault = true)]
public IQueryable<NorthwindServiceReference.Category> GetCategories()
{
return _context.Categories;
}
[Query(IsDefault = true)]
public IQueryable<NorthwindServiceReference.Customer> GetCustomers()
{
return _context.Customers;
}
public IQueryable<NorthwindServiceReference.Customer> GetCustomersbyCountry(string country)
{
if (null == country || country.Equals(string.Empty))
{
return GetCustomers();
}
return GetCustomers().Where(c => c.Country == country);
}
[Query(IsDefault = true)]
public IQueryable<NorthwindServiceReference.Product> GetProducts()
{
return _context.Products;
}
Primary Keys Definition
LightSwitch requires that each imported entity has a primary key defined. Unfortunately, the entities defined by the service reference do not include attributes to identify the key. However, RIA Services provides a mechanism to annotate an existing class with additional attributes using a metadata class. We need to add a KeyAttribute to the key property for each entity on our OData service.
[MetadataType(typeof(Customer.Metadata))]
public partial class Customer
{
internal sealed class Metadata
{
[Key()]
public int CustomerID { get; set; }
}
}
Relationships Definition
Unfortunately, the entities imported into LightSwitch do not have relationships between them. The entities defined by our OData service reference do not contain enough information for LightSwitch to properly infer any relationships.
You need to add additional information to our metadata classes on the DomainService to identity the relationships. This could be done by applying an AssociationAttribute on any properties that represent a relationship.
[MetadataType(typeof(Product.Metadata))]
public partial class Product
{
internal sealed class Metadata
{
[Key()]
public int ProductID { get; set; }
[Association("FK_Products_Categories", "CategoryID", "CategoryID", IsForeignKey = true)]
public Category Category { get; set; }
}
}
[MetadataType(typeof(Category.Metadata))]
public partial class Category
{
internal sealed class Metadata
{
[Key()]
public int CategoryID{ get; set; }
[Association("FK_Products_Categories", "CategoryID", "CategoryID", IsForeignKey = false)]
public DataServiceCollection<Product> Products { get; set; }
}
}
Integration of Infragistics Reporting
There are different possible ways to integrate Infragistics Reports in LightSwitch applications:
- Using custom control libraries
- Creating custom control extensions
In this post will be demonstrated the easiest approach: using custom control libraries. Sample applications use custom Silverlight control library to wrap report and report viewer.
Report Data Source
Report needs data source to get data.
First sample will use WCF RIA Services to load required data. This approach has a disadvantage:
You have a double load of data in WCF RIA. One time via LightSwitch for the screen data (screen query) and the second time - for Report. This leads to more traffic, worse performance and the need for additional code for data consistency.
The second sample uses Screen Query (LightSwitch collection) as a data source for the report. This approach is simpler and much better integration and in a Infragistics reports.
Requirements:
Visual Studio LightSwitch 2011
Microsoft SQL Server 2008 R2 Express or higher version.
NetAdvantage Reporting Vol.11.2
Visual Studio LightSwitch Application
Add a LightSwitch application to the solution and use as a data source WCF RIA Services from our library.
Create a search screen using GetCustomersbyCountry query. Parameter appears as a data item (Customercountry in this case). Add new custom control (select from the custom library
Sample 1
Integration using the Report Data Source directly from WCF RIA Service
Report
Add a report using Customer entity and style it. Do care about report parameters.
Report Viewer
Let’s use in the client application method GetCustomersbyCountry in the instance of the class DomainDataSource, which is used with WCF RIA Services. TextBox will be used to visualized parameter for your query.
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition />
</Grid.RowDefinitions>
<riaControls:DomainDataSource x:Name="domainDataSource" LoadedData="DomainDataSourceLoadedData"
AutoLoad="False"
QueryName="GetCustomersbyCountryQuery">
<riaControls:DomainDataSource.DomainContext>
<Web:NorthwindDomainContext />
</riaControls:DomainDataSource.DomainContext>
<riaControls:DomainDataSource.QueryParameters>
<riaControls:Parameter ParameterName="country" Value="{Binding ElementName=countryTextBox, Path=Text}" />
</riaControls:DomainDataSource.QueryParameters>
</riaControls:DomainDataSource>
<StackPanel Height="40" HorizontalAlignment="Left" Orientation="Horizontal" VerticalAlignment="Top">
<sdk:Label Content="Select Country:" Margin="5" VerticalAlignment="Center" FontSize="12" FontWeight="Bold" />
<TextBox Name="countryTextBox" Width="100" Margin="5" Text="{Binding ElementName=IgReportingPage, Path=Country}" TextChanged="CountryTextBoxTextChanged" />
<Button Command="{Binding Path=LoadCommand, ElementName=domainDataSource}" Content="Load" Margin="5" Width="100" Style="{StaticResource LoadButton}" Name="customerDomainDataSourceLoadButton" />
</StackPanel>
<ig:XamReportViewer Grid.Row="1" Margin="20" Name="xamReportViewer1">
<ig:XamReportViewer.RenderSettings>
<ig:ClientRenderSettings DefinitionUri="/IgReportingLibrary;component/NwReport.igr">
<ig:ClientRenderSettings.DataSources>
<!-- Overrides Order_Detail data source defined at design time-->
<ig:DataSource TargetDataSource="Customer" ItemsSource="{Binding ElementName=domainDataSource}" />
</ig:ClientRenderSettings.DataSources>
</ig:ClientRenderSettings>
</ig:XamReportViewer.RenderSettings>
</ig:XamReportViewer>
</Grid>
Report Wrapper
Add a dependency property named Country. You will use it to receive required parameter from LightSwitch.
#region public string Country
/// <summary>
///
/// </summary>
public string Country
{
get { return GetValue(CountryProperty) as string; }
set { SetValue(CountryProperty, value); }
}
/// <summary>
/// Identifies the Country dependency property.
/// </summary>
public static readonly DependencyProperty CountryProperty =
DependencyProperty.Register(
"Country",
typeof(string),
typeof(ReportingControl),
new PropertyMetadata(null, OnCountryPropertyChanged));
/// <summary>
/// CountryProperty property changed handler.
/// </summary>
/// <param name="d">ReportingControl that changed its Country.</param>
/// <param name="e">Event arguments.</param>
private static void OnCountryPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ReportingControl source = d as ReportingControl;
var value = e.NewValue as string;
var oldVvalue = e.OldValue as string;
if (source != null && value != oldVvalue)
{
source.domainDataSource.Load();
}
}
#endregion public string Country
Load data in the DomainDataSource instance programmatically when TextBox content has changed.
#region CountryTextBoxTextChanged
private void CountryTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
domainDataSource.Clear();
domainDataSource.QueryName = "GetCustomersbyCountryQuery";
domainDataSource.QueryParameters.Clear();
domainDataSource.QueryParameters.Add(new Parameter { ParameterName = "country", Value = countryTextBox.Text });
domainDataSource.Load();
}
#endregion //CountryTextBoxTextChanged
Create a second UserControl instance in the project, named LightSwitchReportingControl. Include the first control with the report viewer inside it. Bind the Country property to a specific data source from LightSwitch screen:
Country="{Binding Screen.Customercountry}"
Customercountry will be a parameter, used when make queries from the LightSwitch application using WCF RIA Services.
<Grid x:Name="LayoutRoot" Background="White">
<loc:ReportingControl Margin="0" x:Name="reportingControl1" Country="{Binding Screen.Customercountry}" />
</Grid>
You could use only one user control. The use of two controls is to have possibility to test the first one outside of LightSwitch environment
LightSwitch Screen
Create a search screen using GetCustomersbyCountry query. Parameter appears as a data item (Customercountry in this case). Add new custom control (select from the custom library LightSwitchReportingControl (user control, designed to use data from Customercountry data source).
Run the application. When the TextBox for selected country is empty you will see the whole data.
Add for a selected country “Germany” . The report viewer will display only customers from Germany.
Source code for Sample 1 you could download here.
Sample 2
Integration using the Report Data Source using a LightSwitch screen query
Report Viewer
Report Viewer user control is is a similar to the control, demonstrated in the Sample 1. Here are no WCF RIA element – no DomainDataSource and DomainContext.
In the XamReportViewer you should set for ClientRenderSettings DataSource where to bind ItemsSource to to dependency property from this user control (Customers2 in this case).
<ig:XamReportViewer Grid.Row="1" Margin="20" Name="xamReportViewer1" >
<ig:XamReportViewer.RenderSettings>
<ig:ClientRenderSettings DefinitionUri="/IgReportingLibrary;component/NwReport.igr">
<ig:ClientRenderSettings.DataSources>
<ig:DataSource TargetDataSource="Customer2" ItemsSource="{Binding ElementName=LayoutRoot, Path=Parent.Customers2}" />
</ig:ClientRenderSettings.DataSources>
</ig:ClientRenderSettings>
</ig:XamReportViewer.RenderSettings>
</ig:XamReportViewer>
You could see the whole control layout here:
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Height="40" HorizontalAlignment="Left" Orientation="Horizontal" VerticalAlignment="Top">
<sdk:Label Content="Select Country:" Margin="5" VerticalAlignment="Center" FontSize="12" FontWeight="Bold" />
<TextBox Name="countryTextBox" Width="100" Margin="5" Text="{Binding ElementName=IgReportingPage, Path=Country}" TextChanged="CountryTextBoxTextChanged" />
<Button Click="CustomerDomainDataSourceLoadButtonClick" Content="Load" Margin="5" Width="100" Style="{StaticResource LoadButton}" Name="customerDomainDataSourceLoadButton" />
</StackPanel>
<ig:XamReportViewer Grid.Row="1" Margin="20" Name="xamReportViewer1" >
<ig:XamReportViewer.RenderSettings>
<ig:ClientRenderSettings DefinitionUri="/IgReportingLibrary;component/NwReport.igr">
<ig:ClientRenderSettings.DataSources>
<ig:DataSource TargetDataSource="Customer2" ItemsSource="{Binding ElementName=LayoutRoot, Path=Parent.Customers2}" />
</ig:ClientRenderSettings.DataSources>
</ig:ClientRenderSettings>
</ig:XamReportViewer.RenderSettings>
</ig:XamReportViewer>
</Grid>
Add two dependency properties IEnumerable CustomersCollection and IList Customers2.
The first one you could bind to the screen query (LightSwitch collection, that implements IEnumerable).
The second one you could use as data source for your report.
Of course, you could use only one collection and appropriate value converter.
CustomersCollection implementation:
#region public IEnumerable CustomersCollection
/// <summary>
///
/// </summary>
///
public IEnumerable CustomersCollection
{
get { return GetValue(CustomersCollectionProperty) as IEnumerable; }
set { SetValue(CustomersCollectionProperty, value); }
}
/// <summary>
/// Identifies the CustomersCollection dependency property.
/// </summary>
public static readonly DependencyProperty CustomersCollectionProperty =
DependencyProperty.Register(
"CustomersCollection",
typeof(IEnumerable),
typeof(ReportingControl),
new PropertyMetadata(null, OnCustomersCollectionPropertyChanged));
/// <summary>
/// CustomersCollectionProperty property changed handler.
/// </summary>
/// <param name="d">ReportingControl that changed its CustomersCollection.</param>
/// <param name="e">Event arguments.</param>
private static void OnCustomersCollectionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ReportingControl source = d as ReportingControl;
IEnumerable value = e.NewValue as IEnumerable;
var changed = e.NewValue as INotifyCollectionChanged;
if(changed == null)
{
return;
}
changed.CollectionChanged -= ChangedCollectionChanged;
changed.CollectionChanged += ChangedCollectionChanged;
}
static void ChangedCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedAction act = e.Action;
GetCustomers(_report.CustomersCollection, _report);
}
#endregion public IEnumerable CustomersCollection
Customers2 implementation:
#region public IList<Customer> Customers2
/// <summary>
///
/// </summary>
public IList<Customer> Customers2
{
get
{
return GetValue(CustomersProperty2) as IList<Customer>;
}
set { SetValue(CustomersProperty2, value); }
}
/// <summary>
/// Identifies the Customers dependency property.
/// </summary>
public static readonly DependencyProperty CustomersProperty2 =
DependencyProperty.Register(
"Customers2",
typeof(IList<Customer>),
typeof(ReportingControl),
new PropertyMetadata(null));
#endregion public IList<Customer> Customers2
Method GetCustomers is used to get via reflection properties and property values from the screen query. You could implement this logic also in a value converter.
#region public static void GetCustomers
public static void GetCustomers(IEnumerable col, ReportingControl rep)
{
var customers = new List<Customer>();
if (col == null)
{
return; // null;
}
foreach (var x in col)
{
var cust = new Customer { };
Type t = x.GetType();
PropertyInfo[] pi = t.GetProperties();
foreach (PropertyInfo p in pi)
{
object o = null;
switch (p.Name)
{
case "CustomerID":
cust.CustomerID = p.GetValue(x, null).ToString();
break;
case "Address":
o = p.GetValue(x, null);
if (o != null)
{
cust.Address = o.ToString();
}
break;
case "City":
o = p.GetValue(x, null);
if (o != null)
{
cust.City = o.ToString();
}
break;
case "Country":
o = p.GetValue(x, null);
if (o != null)
{
cust.Country = o.ToString();
}
break;
case "CompanyName":
o = p.GetValue(x, null);
if (o != null)
{
cust.CompanyName = o.ToString();
}
break;
case "ContactName":
o = p.GetValue(x, null);
if (o != null)
{
cust.ContactName = o.ToString();
}
break;
case "Phone":
o = p.GetValue(x, null);
if (o != null)
{
cust.Phone = o.ToString();
}
break;
case "PostalCode":
o = p.GetValue(x, null);
if (o != null)
{
cust.PostalCode = o.ToString();
}
break;
case "Fax":
o = p.GetValue(x, null);
if (o != null)
{
cust.Fax = o.ToString();
}
break;
case "Region":
o = p.GetValue(x, null);
if (o != null)
{
cust.Region = o.ToString();
}
break;
}
}
customers.Add(cust);
}
rep.Customers2 = customers;
rep.xamReportViewer1.RenderReport();
}
#endregion //public static void GetCustomers
Attach an event handler to the click event of the button in the control to be possible manually to redraw the report (optional)
Manual update of the the report viewer via XamReportViewer.RenderReport().
private void CustomerDomainDataSourceLoadButtonClick(object sender, RoutedEventArgs e)
{
GetCustomers(this.CustomersCollection, this);
xamReportViewer1.RenderReport();
}
Create a second UserControl instance in the project, named LightSwitchReportingControl. Include the first control with the report viewer inside it. Bind the Country property to a specific data source from LightSwitch screen:
Country="{Binding Screen.Customercountry}" (like in the Sample 1). Bind the report viewer DataContext to Screen.GetCustomersbyCountry (screen query – it is a collection from a specific for the LightSwitch framework type.Bind the CustomersCollection to the DataContext.
Customercountry will be a parameter, used to filter data.
<UserControl x:Class="IgReportingLibrary.LightSwitchReportingControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:loc="clr-namespace:IgReportingLibrary"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<loc:ReportingControl Margin="0" x:Name="reportingControl1"
Country="{Binding Screen.Customercountry}"
DataContext="{Binding Screen.GetCustomersbyCountry}"
CustomersCollection="{Binding}" />
</Grid>
</UserControl>
LightSwitch Screen
Create a search screen using GetCustomersbyCountry query. Parameter appears as a data item (Customercountry in this case). Add new custom control (select from the custom library LightSwitchReportingControl (user control, designed to use data from Customercountry data source).
Run the application. You will see the whole data.
Add for a selected country “France” . The report viewer will display only customers from France.
The approach used in Sample 2 could be used to create Infragistics Reporting LightSwitch Extension. How to do this will be shown in a later article.
Source code for Sample 2 you could download here.
1 comment(s) so far...
Mary
LightSwitch Help Website >Blog - Using the Infragistics Reporting with OData In Visual Studio LightSwitch # Mary
By TrackBack on
1/15/2012 6:18 AM
|