Reposted with VB.Net code
From Database to DataGrid
The next step in Project Turing is to create a first iteration of the Silverlight application that will retrieve data from our database. Using our technology of choice: the Data Entity Framework coupled with .Net RIA Services.
.Net RIA Services will allow us to create business logic on the server and to access that same set of classes on the client. We will retrieve the data from these business classes and display them in a data grid within a business application, that is a multipage Silverlight application.
.NET RIA Services
While a great deal has been written about RIA Services, I find when talking with Silverlight developers that there is still some confusion about where RIA Services are best used and what the key benefits are. This really isn’t terribly surprising given how new the RIA Services platform is. Despite the incredible work done by Brad Abrams and others on the dev team to document RIA Services, it will take a while for many Silverlight programmers to fully integrate the platform into their designs
Project Turing offers an ideal opportunity to provide an incremental introduction to RIA Services and an illustration of the immediate benefits of the platform.
Just before the progress took the digression into Linq, we created our basic database tables, and the initial architecture of Project Turing. Here is a snapshot of the database as it exists at this point in the project:
(click on image for full size)
Our goal now is to retrieve this data using the Data Entity Framework and to make it available to the DataGrid using .NET RIA Services.
What You Need To Program With RIA Services Project
For this part of Project Turing you’re going you need to install the latest .Net RIA Services framework. You can find complete instructions and all of the libraries that you need here. (There is no cost.)
You also want to create or download the Turing database, which you can install to SQL Server 2005, SQL Server 2008, or SQL express. You can download a backup of this database and restore it to your system, from here. Alternatively you can download a script to re-create the database from here.
Getting Started
We will build the application incrementally, starting with a simple version and adding complexity as we go. Specifically, we will break down the steps of interacting with the database into a series of smaller tasks.
The goal in this first example will be to create a shell of a multipage application, and within that a page to which we’ll add a basic DataGrid. To get started open Visual Studio and create a new project. In the Project Type, select Visual C#/Silverlight, and in the templates select Silverlight Business Application. I named my project TuringRIAServices.
A fairly sophisticated Silverlight application is created for you out-of-the-box. Your solution consists of two projects, and your Xaml project has three subdirectories: Assets, Libs, and Views.
The views folder contains the initial pages already provided in the application. These include Xaml files for logging in, a Home page, a custom error window and an about box.
MainPage.xaml acts as a shell and a dispatcher to the pages that are in the Views folder. The Assets folder contains your first styles Xaml page, but you are free to add more (as you are to all the folders).
To begin open MainPage.xaml and let’s examine some of what has been put in place by Visual Studio.
<Grid x:Name="LayoutRoot" Style="{StaticResource LayoutRootGridStyle}"> <Border x:Name="ContentBorder" Style="{StaticResource ContentBorderStyle}"> <navigation:Frame x:Name="ContentFrame" Style="{StaticResource ContentFrameStyle}" Source="/Home" Navigated="ContentFrame_Navigated" NavigationFailed="ContentFrame_NavigationFailed"> <navigation:Frame.UriMapper> <uriMapper:UriMapper> <uriMapper:UriMapping Uri="" MappedUri="/Views/Home.xaml"/> <uriMapper:UriMapping Uri="/{pageName}" MappedUri="/Views/{pageName}.xaml"/> </uriMapper:UriMapper> </navigation:Frame.UriMapper> </navigation:Frame> </Border> <Grid Style="{StaticResource NavigationOuterGridStyle}"> <Grid x:Name="NavigationGrid" Style="{StaticResource NavigationGridStyle}"> <Border x:Name="BrandingBorder" Style="{StaticResource BrandingBorderStyle}"> <StackPanel x:Name="BrandingStackPanel" Style="{StaticResource BrandingStackPanelStyle}"> <ContentControl Style="{StaticResource LogoIcon}"/> <TextBlock x:Name="ApplicationNameTextBlock" Style="{StaticResource ApplicationNameStyle}" Text="Application Name"/> </StackPanel> </Border> <Border x:Name="LinksBorder" Style="{StaticResource LinksBorderStyle}"> <StackPanel x:Name="LinksStackPanel" Style="{StaticResource LinksStackPanelStyle}"> <HyperlinkButton x:Name="Link1" Style="{StaticResource LinkStyle}" NavigateUri="/Home" TargetName="ContentFrame" Content="home"/> <Rectangle x:Name="Divider1" Style="{StaticResource DividerStyle}"/> <HyperlinkButton x:Name="Link2" Style="{StaticResource LinkStyle}" NavigateUri="/About" TargetName="ContentFrame" Content="about"/> </StackPanel> </Border> </Grid> <Border x:Name="loginContainer" Style="{StaticResource LoginContainerStyle}"/> </Grid> </Grid>
Before taking this apart lets look at what is created if you build and run this without making any changes.
(Click image for full size)
You can see in the image that there is a frame around the home page, which has within it a logo, the name of the application, a button for the currently selected page, a button for the about page, and a small button for the login dialog.
This corresponds directly of course to the Xaml above.
On line 2, we create a border, which uses the static resource predefined by Visual Studio and stored in Styles.Xaml within the Assets folder.
Within that border a Frame object is defined on line 3 through 11, within the navigation namespace defined at the top of the file.
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
The frame has three critical properties:
- The source.
- The name of the method to call after successfully navigating to the page.
- The name of the method to call in the event of failure.
Nested within the frame is a mapper, whose job is to map pages to URIs.
Note on line 8, the generic mapping that is used with the substitution parameter pagename. This is a subtle but very powerful capability that allows you to create essentially a template of the relationship between pages in their URI and avoid duplicating this code for every page.
The second major UI area within this Xaml file begins on lines 14 and 15 with the creation of an outer and inner grid.
The content control defined on line 20 is used to display the logo and on line 21 is a standard textblock, where the text is set to Application Name. This would be a good opportunity to change that text to “Project Turing RIA Services”
The second area is itself divided in to two areas each with its own water, and in the lower area beginning on line 27. Line 30 shows the hyperlink button for the homepage and on line 35 is the hyperlink button for the about page. Adding a new button only requires an additional hyperlink button. In this section, the navigation is handled by the section about.
You may find it worthwhile and interesting to open up the style.xaml file, and take a look at the static resources that were created by this template.
Adding A DataGrid
We will add the Silverlight control DataGrid to a new Silverlight page. To do so, right-click on the project and choose Add –> New Item. In the Add Item dialog box, choose Silverlight under categories and Silverlight Page in the templates pane.
Name the new page BlogsDataGrid and click Add.
At the top of this page, we will place a header as we will on each new page. The header consistents of a ScrollViewer, within which we will place a StackPanel, within which we will place a Toolbox with the name of the page as shown here:
Note: this design, and much in these examples, comes from the Walk-Through created by the development team and used here with their permission, for which I and my Carpal Tunnel Syndrome are very grateful.
<ScrollViewer BorderThickness="0"
VerticalScrollBarVisibility="Auto"
Padding="12,0,12,0"
Margin="-12">
<StackPanel Margin="0,12,0,12"
Orientation="Vertical">
<TextBlock Text="Blogs"
Style="{StaticResource HeaderTextStyle}" />
</StackPanel>
</ScrollViewer>
Finally, drag a DataGrid from the toolbox into the grid.
Note: the advantage of dragging from the toolbox rather than writing the Xaml by hand is that Visual Studio will create the necessary namespace and use that as a prefix for the DataGrid |
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
Once Visual Studio has fixed up the namespace, modify the data grid element to include the properties Name (BlogsDataGrid), MinHeight (150), and IsReadOnly (true).
<navigation:Page
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
x:Class="TuringRiaServices.Blogs"
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:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
mc:Ignorable="d"
d:DesignWidth="640"
d:DesignHeight="480"
Title="Blogs Page">
<Grid x:Name="LayoutRoot">
<ScrollViewer BorderThickness="0"
VerticalScrollBarVisibility="Auto"
Padding="12,0,12,0"
Margin="-12">
<StackPanel Margin="0,12,0,12"
Orientation="Vertical">
<TextBlock Text="Blogs"
Style="{StaticResource HeaderTextStyle}" />
</StackPanel>
</ScrollViewer>
<data:DataGrid x:Name="blogsDataGrid"
MinHeight="150"
IsReadOnly="True"></data:DataGrid>
</Grid>
</navigation:Page>
Your page is ready, but there is no way to navigate to it. Return to MainPage.Xaml and let’s add the logic for an additional button.
First, take a look at the solution Explorer, and if BlogsDataGrid.Xaml is not in the views folder drag it there now.
As noted above, all we need to do here is add a hyperlink button for our new page. This will go immediately below the hyperlink button for the about box
<HyperlinkButton x:Name="Link3"
Style="{StaticResource LinkStyle}"
NavigateUri="/Blogs"
TargetName="ContentFrame"
Content="Blogs" />
Build and run the application and be sure to click on the new button for the Blogs page. Please take note of the very elegant handling of the exception that is thrown, because we wired up for a same blogs. But our actual page is named BlogsDataGrid.
While we’re fixing that link, lets take note of the fact that Visual Studio has named each of the hyperlink buttons. Link one, link two etc. , while it is natural and common to continue this as we have done above, I greatly prefer to name the hyperlink buttons to indicate their function. And so I will rename Link1 HomeLinkButton; Link2 AboutLinkButton and our Link3 BlogsLinkButton. I will also fix up the NavigateURI to the full name of the page.
One defensible choice is to continue the pattern provided by Visual Studio, but it is my personal approach to rename all three on the premise that good naming practice makes for good documentation.
<UserControl x:Class="TuringRIA_Services.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation" xmlns:uriMapper="clr-namespace:System.Windows.Navigation;assembly=System.Windows.Controls.Navigation" xmlns:dataControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480"> <Grid x:Name="LayoutRoot" Style="{StaticResource LayoutRootGridStyle}"> <Border x:Name="ContentBorder" Style="{StaticResource ContentBorderStyle}"> <navigation:Frame x:Name="ContentFrame" Style="{StaticResource ContentFrameStyle}" Source="/Home" Navigated="ContentFrame_Navigated" NavigationFailed="ContentFrame_NavigationFailed"> <navigation:Frame.UriMapper> <uriMapper:UriMapper> <uriMapper:UriMapping Uri="" MappedUri="/Views/Home.xaml" /> <uriMapper:UriMapping Uri="/{pageName}" MappedUri="/Views/{pageName}.xaml" /> </uriMapper:UriMapper> </navigation:Frame.UriMapper> </navigation:Frame> </Border> <Grid Style="{StaticResource NavigationOuterGridStyle}"> <Grid x:Name="NavigationGrid" Style="{StaticResource NavigationGridStyle}"> <Border x:Name="BrandingBorder" Style="{StaticResource BrandingBorderStyle}"> <StackPanel x:Name="BrandingStackPanel" Style="{StaticResource BrandingStackPanelStyle}"> <ContentControl Style="{StaticResource LogoIcon}" /> <TextBlock x:Name="ApplicationNameTextBlock" Style="{StaticResource ApplicationNameStyle}" Text="Project Turing RIA Services" /> </StackPanel> </Border> <Border x:Name="LinksBorder" Style="{StaticResource LinksBorderStyle}"> <StackPanel x:Name="LinksStackPanel" Style="{StaticResource LinksStackPanelStyle}"> <HyperlinkButton x:Name="HomeLinkButton" Style="{StaticResource LinkStyle}" NavigateUri="/Home" TargetName="ContentFrame" Content="home" /> <Rectangle x:Name="Divider1" Style="{StaticResource DividerStyle}" /> <HyperlinkButton x:Name="AboutLinkButton" Style="{StaticResource LinkStyle}" NavigateUri="/About" TargetName="ContentFrame" Content="about" /> <HyperlinkButton x:Name="BlogsLinkButton" Style="{StaticResource LinkStyle}" NavigateUri="/BlogsDataGrid" TargetName="ContentFrame" Content="Blogs" /> </StackPanel> </Border> </Grid> <Border x:Name="loginContainer" Style="{StaticResource LoginContainerStyle}" /> </Grid> </Grid></UserControl>
This time clicking on the blogs hyperlink does not throw an exception but rather brings you to the blogs page, where the outline of the DataGrid is drawn but of course there is as of yet no data. Let’s fix that now.
Adding Business Logic And Data
Our next principal task is to retrieve data from the database and present it to the DataGrid. As noted in previous mini tutorials and postings for this project, there are a number options for retrieving data from a database. One of the most powerful is to use the Entity Framework.
When a developer says technology is “powerful,” we typically mean that it does a lot of the routine plumbing for us, and that it is highly flexible. Certainly both are true of the Entity Framework.
A quick look at Julia Lerman’s Programming Entity Framework (O’Reilly Media, 2009) is both reassuring and somewhat intimidating; her book is over 750 pages. This stands as testimony to the enormous flexibility of the Entity Framework.
Programming Entity Framework (9780596520281): Julia Lerman: Books
ISBN: 059652028X ISBN-13: 9780596520281 |
That said, using the Entity Framework to retrieve data from a relatively straightforward relational database can be explained quite quickly. I will demonstrate enough of the Entity Framework to get a feel for the role that it plays and the level of abstraction that it provides, but in no way will this be a comprehensive survey of the Entity Framework in its entirety.
To begin, add a new item to Turing RIA Services.web. In the Add New Item dialog select Data in the Categories pane, and choose ADO.net Entity Data Model in the templates pane, and name the new Entity Data Model TuringBlogs. Indicate to the Entity Data Model Wizard that you wish to generate your model from an existing database.
(Click image for full size)
You’ll be prompted to create or reuse a connection string for your database
(Click image for full size)
Note that using the second radio button “include the sensitive data in the connection string,” will slightly simplify your code, but expose a significant security hole.
After connecting to your database, you’ll be asked to choose which objects you wish to include. Expand the tables, and select the Blogs table.
(Click image for full size)
When you click finish, the entity data model will appear in the designer. To verify that everything is correct, build the solution.
Transporting the Data Class To The Client With .Net RIA Services
You’re halfway home. You now have an entity (object) that represents the data that is stored in relational tables in SQL Server. The next step is to create business classes that will represent and manipulate that data on the server, and be equally available on the client. That is in a nutshell, what.net RIA Services does for a living.
Once again right-click on the Turing RIA Services Web project, and add a new item. However this time, in the Categories pane, select Web and in the templates pane Domain Service Class. Name the new item BlogsService and click Add.
(Click image for full size)
Click Add which will open the Add New Domain Service Class dialog. Select Blogs from the list of entities (okay, in this case it’s the only item in the list). In the same dialog, be sure to check Enable Editing, which enables the CRUD functionality, and at the bottom of the dialog, click “Generate associated classes for metadata.” Then click OK.
(Click image for full size)
Open the class of this generates for you: BlogsService.cs and modifying the GetBlogs method as follows:
C#
//Replace this generated code: public IQueryable GetBlogs() { return this.Context.Blogs; } // with this custom code: public IQueryable GetBlogs() { return this.Context.Blogs.OrderBy(b => b.BlogName); }
VB.Net
'Replace this generated code: Public Function GetBlogs() As IQueryable(Of Blogs) Return Me.Context.Blogs End Function ' with this custom code: Public Function GetBlogs() As IQueryable(Of Blogs) Return Me.Context.Blogs.OrderBy(Function(b) b.BlogName) End Function
In this first example, our customization is ordering the results by the name of the blog.
Define It on the Server, Invoke It on the Client
When you rebuild the solution not only will the classes that you’ve created in the server be built, but a corresponding set of proxy classes will be built in the client project.
Thus the class and its method will be available on the client side, and therefore available to our DataGrid. At the top of the code behind page will need to add three using statements; one to bring in the web project, and the other two for RIA Services:
C#
using TuringRIAServices.Web; using System.Windows.Ria.Data; using System.Windows.Ria.ApplicationServices;
VB.Net
Imports TuringRIAServices.Web Imports System.Windows.Ria.Data Imports System.Windows.Ria.ApplicationServices
In the generated code in the client project, the BlogsContext is generated based on the BlogsService you created on the server. To see the definition, turn on show all files, and locate the file TuringRIAServices.web.g.cs / TuringRIAServices.web.g.vb. Inside the previously hidden Generated_Code folder. In that file, you will find the definition for the BlogsContext.
C#
public sealed partial class BlogsContext : DomainContext { partial void OnCreated(); public BlogsContext() : this(new HttpDomainClient( new Uri("DataService.axd/TuringRIA_Services-Web-BlogsService/", System.UriKind.Relative))) {} public BlogsContext(Uri serviceUri) : this(new HttpDomainClient(serviceUri)) {} public BlogsContext(DomainClient domainClient) : base(domainClient) { this.OnCreated(); } public EntityList Blogs { get { return base.Entities.GetEntityList(); } } public EntityQuery GetBlogsQuery() { return base.CreateQuery("GetBlogs", null, false, true); } protected override EntityContainer CreateEntityContainer() { return new BlogsContextEntityContainer(); } internal sealed class BlogsContextEntityContainer : EntityContainer { public BlogsContextEntityContainer() { this.CreateEntityList(EntityListOperations.All); } } }
VB.Net
Public NotInheritable Partial Class BlogsContext Inherits DomainContext Partial Private Sub OnCreated() End Sub Public Sub New() Me.New(New HttpDomainClient(New Uri("DataService.axd/TuringRIA_Services-Web-BlogsService/", System.UriKind.Relative))) End Sub Public Sub New(ByVal serviceUri As Uri) Me.New(New HttpDomainClient(serviceUri)) End Sub Public Sub New(ByVal domainClient As DomainClient) MyBase.New(domainClient) Me.OnCreated() End Sub Public ReadOnly Property Blogs() As EntityList(Of Blogs) Get Return MyBase.Entities.GetEntityList(Of Blogs)() End Get End Property Public Function GetBlogsQuery() As EntityQuery(Of Blogs) Return MyBase.CreateQuery(Of Blogs)("GetBlogs", Nothing, False, True) End Function Protected Overrides Function CreateEntityContainer() As EntityContainer Return New BlogsContextEntityContainer() End Function Friend NotInheritable Class BlogsContextEntityContainer Inherits EntityContainer Public Sub New() Me.CreateEntityList(Of Blogs)(EntityListOperations.All) End Sub End Class End Class
Returning to BlogsDataGrid.xaml.cs, instantiate a BlogsContext as a member variable,
C#
private BlogsContext blogsContext = new BlogsContext();
VB.Net
Private blogsContext As New BlogsContext()
Here is the complete BlogsDataGrid.xaml.cs / BlogsDataGrid.xaml.vb
C#
private BlogsContext blogsContext = new BlogsContext();
VB.Net
Private blogsContext As New BlogsContext()
Here is the complete BlogsDataGrid.xaml.cs (BlogsDataGrid.xaml.vb in case of VB.Net):
C#
using System.Windows.Controls; using System.Windows.Navigation; using TuringRIA_Services.Web; using System.Windows.Ria.Data; using System.Windows.Ria.ApplicationServices; namespace TuringRIA_Services { public partial class BlogsDataGrid : Page { private BlogsContext blogsContext = new BlogsContext(); public BlogsDataGrid() { InitializeComponent(); this.blogsDataGrid.ItemsSource = blogsContext.Blogs; blogsContext.Load( blogsContext.GetBlogsQuery() ); } protected override void OnNavigatedTo( NavigationEventArgs e ) { } } }
VB.Net
Imports System.Windows.Controls Imports System.Windows.Navigation Imports TuringRIA_Services.Web Imports System.Windows.Ria.Data Imports System.Windows.Ria.ApplicationServices Namespace TuringRIA_Services Partial Public Class BlogsDataGrid Inherits Page Private blogsContext As New BlogsContext() Public Sub New() InitializeComponent() Me.blogsDataGrid.ItemsSource = blogsContext.Blogs blogsContext.Load(blogsContext.GetBlogsQuery()) End Sub Protected Overrides Sub OnNavigatedTo(ByVal e As NavigationEventArgs) End Sub End Class End Namespace
In the constructor, you ask the BlogsContext object to retrieve its Blogs property (an entity list of Blogs) and this collection is assigned as the ItemsSource of the DataGrid. The following line instructs the Blog Context to call its Load member method, passing in the query retrieved by calling GetBlogQuery(). This will be the query that we customized earlier.
The query as you remember, retrieves all of the blogs, ordered by the name of each blog. This collection is assigned to the DataGrid and the page is displayed when you click on the blogs button.
(Click image for full size)
What have you learned, Dorothy?
In this first iteration of moving data from our SQL Server database to the DataGrid on our client, we have examined using the Entity Framework for creating an object orientated model of the data in the database, and using RIA Services both to provide a common set of business classes on the server and the client and to provide transportation of the data from server-side to client-side.
In the next posting on Project Turing, I will examine both more complex, data retrieval and how we go about modifying the data in the database based on user actions on the client.
Please note that due to Carpal Tunnel Syndrome this posting was dictated and then transcribed by computer. I apologize for any resulting absurdities. |
Novice | Previous: Linq | Next: CRUD Operations with Entity Frameworks |
Advanced | Previous: Linq | Previous: Linq |
6 Responses to Project Turing: Beginning RIA Services