You are here:   Blog
Register   |  Login

Latest Microsoft LightSwitch Blogs

Note: This article applies to LightSwitch in Visual Studio 11 (LightSwitch V2) It’s common for developers to add static images and text to their screens to help guide their users through the application.  Although you can easily add images that come a database to your screens, up
Read more...

Matt Sampson has posted part 3 of a multi-part blog post this week that completes the walkthrough of creating an application around the popular public transit CommuterApi OData Service. This post focuses on creating a RIA Data Service
Read more...

Eric Erhardt has posted part 2 in his series on using stored procedures in LightSwitch. In the second part, he describes how you can use Visual Studio LightSwitch to update database records using stored procedures.  A lot of database administrators only allow modifications to data through
Read more...

The first release of Visual Studio LightSwitch (LightSwitch V1) allows users to define relationships between tables within the intrinsic/built-in data source (ApplicationData).  When attaching to existing data sources, LightSwitch will import the relationships defined within the data
Read more...

Eric Erhardt has posted part 1 of a series on using stored procedures in LightSwitch.  In the first part, he describes how you can execute a stored procedure when a user clicks a button on your LightSwitch screen. The blog post is here – Read more...

Dec 16

Written by: Mihail Mateev
12/16/2011 9:06 PM  RssIcon

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.

Intro_Reporting_Schema01a 

 

The Report Designer is a Reporting System integrated by the following components:

  1. 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.
  2. 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.
  3. 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.

Intro_Reporting_Schema01b

 

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.

 

Intro_Reporting_Schema01c 

  1. Expression area - The expression you are building is displayed in the text field of this area.
  2. Operators area - In this area you can select operators to add to the expression.
  3. Fields, Variables and Parameters area - Select database fields, global variables, and parameters to add your expression.
  4. 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.

UsingIgReportingInLightSwitch_Pic06

UsingIgReportingInLightSwitch_Pic07

 

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.

UsingIgReportingInLightSwitch_Pic02 

 

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.

 UsingIgReportingInLightSwitch_Pic03

 

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

UsingIgReportingInLightSwitch_Pic04 

 

Add for a selected country “Germany” . The report viewer will display only customers from Germany.

 UsingIgReportingInLightSwitch_Pic05

 

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

 UsingIgReportingInLightSwitch_Pic08

 

Run the application. You will see the whole data.

 UsingIgReportingInLightSwitch_Pic09

 

Add for a selected country “France” . The report viewer will display only customers from France.

UsingIgReportingInLightSwitch_Pic10 

 

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

Your name:
Gravatar Preview
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Add Comment   Cancel 

Microsoft Visual Studio is a registered trademark of Microsoft Corporation / LightSwitch is a registered trademark of Microsoft Corporation