A few folks commented that they’d like to see something in my series updating my Mix09 talk “building business applications with Silverlight 3” in Visual Basic.  VB is *super* important in the business application space, so I have no problem accommodating that request.   By while I was at it I thought i’d also show a WPF client for RIA Services via the very cool ADO.NET Data Services support we have.  This is much like the WinForms support I showed earlier. 

The demo requires (all 100% free and always free):

  1. VS2008 SP1 (Which includes Sql Express 2008)
  2. Silverlight 3 RTM
  3. .NET RIA Services July '09 Preview

Also, download the full demo files and, of course, check out the running application.

You can see the full series here

In Part 1: Navigation Basics there is no code whatsoever, so the VB version looks just like what I posted.

In Part 2: Rich Data Query there is some more interesting code snippets.   First, let’s look at the DomainService

   1: <EnableClientAccess()> _
   2: Public Class SuperEmployeeDomainService
   3:     Inherits LinqToEntitiesDomainService(Of NORTHWNDEntities)
   4:  
   5:     Public Function GetSuperEmployees() As IQueryable(Of SuperEmployee)
   6:         Dim q = From emp In Me.Context.SuperEmployeeSet _
   7:                 Where emp.Issues > 100 _
   8:                 Order By emp.EmployeeID
   9:         Return q
  10:     End Function
  11:  
  12:  
  13:     Public Sub UpdateSuperEmployee(ByVal currentSuperEmployee As SuperEmployee)
  14:         Me.Context.AttachAsModified(currentSuperEmployee, Me.ChangeSet.GetOriginal(currentSuperEmployee))
  15:     End Sub
  16:     Public Sub InsertSuperEmployee(ByVal superEmployee As SuperEmployee)
  17:         Me.Context.AddToSuperEmployeeSet(superEmployee)
  18:     End Sub

Here we are defining the Query, Update and Insert methods.

Then to show off some of our POCO support, I created a method to return the origin counts

Public Function GetOrigins() As IQueryable(Of Origin)
 
     Dim q = (From emp In Context.SuperEmployeeSet _
              Select emp.Origin).Distinct().Select(Function(name) New Origin With {.Name = name, .Count = Context.SuperEmployeeSet.Count(Function(emp) emp.Origin.Trim() = name.Trim())})
     q = q.Where(Function(emp) emp.Name IsNot Nothing)
     Return q
 End Function

and defined a POCO class to return that data to the client to show in my AutoComplete box

Public Class Origin
    Public Sub New()
    End Sub
    Private _Name As String
    <Key()> _
    Public Property Name() As String
        Get
            Return _Name
        End Get
        Set(ByVal value As String)
            _Name = value
        End Set
    End Property
    Private _Count As Integer
    Public Property Count() As Integer
        Get
            Return _Count
        End Get
        Set(ByVal value As Integer)
            _Count = value
        End Set
    End Property
End Class

image

On the client side in the Silverlight project I end up doing most things through binding in Xaml, so those are exactly the same in VB.  But I did create a AddNewEmployee ChildWindow

Partial Public Class AddNewWindow
    Inherits ChildWindow
    Private _NewEmployee As SuperEmployee
    Public Property NewEmployee() As SuperEmployee
        Get
            Return _NewEmployee
        End Get
        Set(ByVal value As SuperEmployee)
            _NewEmployee = value
        End Set
    End Property
 
    Public Sub New()
        InitializeComponent()
 
        NewEmployee = New SuperEmployee()
        NewEmployee.LastEdit = DateTime.Now.[Date]
        Me.newEmployeeForm.CurrentItem = NewEmployee
    End Sub
 
    Private Sub OKButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
        newEmployeeForm.CommitEdit()
        Me.DialogResult = True
    End Sub
 
    Private Sub CancelButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
        Me.DialogResult = False
    End Sub
 
End Class

And then we just raise this event

Private Sub AddNew_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
    Dim w = New AddNewWindow()
 
    AddHandler w.Closed, AddressOf addNewWindow_Closed
 
    w.Show()
 
End Sub
 
Private Sub addNewWindow_Closed(ByVal sender As Object, ByVal e As EventArgs)
    Dim win = TryCast(sender, AddNewWindow)
    Dim context = TryCast(dds.DomainContext, SuperEmployeeDomainContext)
    If win.DialogResult = True Then
        context.SuperEmployees.Add(win.NewEmployee)
    End If
End Sub

image

Part 3: Authentication – No code whatsoever here…   This is exactly the same between VB and C#.

Part 4: SEO, Export to Excel and Out of Browser

For SEO, we enabled deeplinking by handing a couple of events…

'Executes when the user navigates to this page.
Protected Overrides Sub OnNavigatedTo(ByVal e As System.Windows.Navigation.NavigationEventArgs)
    Dim qs = NavigationContext.QueryString
 
    If qs.ContainsKey("EmpId") Then
        dds.FilterDescriptors.Add(New FilterDescriptor("EmployeeID", FilterOperator.IsEqualTo, qs("EmpId")))
    End If
 
End Sub

image_thumb[100]

and

Private Sub dataGrid1_SelectionChanged(ByVal sender As System.Object, ByVal e As System.Windows.Controls.SelectionChangedEventArgs)
    Dim emp = TryCast(dataGrid1.SelectedItem, SuperEmployee)
    If emp IsNot Nothing Then
        PermalinkTextBox.Text = (Application.Current.Host.Source.ToString().Replace("ClientBin/MyApp.xap", "") & "#/Home?EmpId=") & emp.EmployeeID
    End If
End Sub

image_thumb[98]

The server side code for Sitemap.aspx and the alternate content in default.aspx is identical.. expect for a small bit of URL rewritting code in default.aspx to ensure both the server and client have access to the deep link.

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
 
    Dim empId As String = Request.QueryString("EmpId")
    Dim deepLink = "/Home?EmpId=" & empId
 
    If empId IsNot Nothing Then
        Response.Write("<script type=text/javascript>window.location.hash='#" & deepLink & "';</script>")
    End If
 
End Sub

The bit about exporting to Excel is super easy as well..

Private Sub ExportToExcel_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
    Dim context = TryCast(dds.DomainContext, SuperEmployeeDomainContext)
    Dim s = Application.GetResourceStream(New Uri("excelTemplate.txt", UriKind.Relative))
    Dim dialog = New SaveFileDialog()
 
    dialog.DefaultExt = "*.xml"
    dialog.Filter = "Excel Xml (*.xml)|*.xml|All files (*.*)|*.*"
 
    If dialog.ShowDialog() = False Then
        Exit Sub
    End If
 
    Using sw = New StreamWriter(dialog.OpenFile())
        Dim sr = New StreamReader(s.Stream)
        While Not sr.EndOfStream
            Dim line = sr.ReadLine()
            If line = "***" Then
                Exit While
            End If
            sw.WriteLine(line)
        End While
 
        For Each emp In context.SuperEmployees
            sw.WriteLine("<Row>")
            sw.WriteLine("<Cell><Data ss:Type=""String"">{0}</Data></Cell>", emp.Name)
            sw.WriteLine("<Cell><Data ss:Type=""String"">{0}</Data></Cell>", emp.Origin)
            sw.WriteLine("<Cell><Data ss:Type=""String"">{0}</Data></Cell>", emp.Publishers)
            sw.WriteLine("<Cell><Data ss:Type=""Number"">{0}</Data></Cell>", emp.Issues)
            sw.WriteLine("</Row>")
        Next
        While Not sr.EndOfStream
            sw.WriteLine(sr.ReadLine())
        End While
    End Using
End Sub

After a bit of formatting… we get:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Finally, in  Part 5: Astoria, Add Service Reference and WinForms…  Defining the ADO.NET Data Services in the web project looks like:

Public Class SuperEmployeeWebDataService
 
    Inherits DataService(Of [SuperEmployeeDomainService])
    Implements IServiceProvider
 
    ' This method is called only once to initialize service-wide policies.
    Public Shared Sub InitializeService(ByVal config As IDataServiceConfiguration)
        ' TODO: set rules to indicate which entity sets and service operations are visible, updatable, etc.
        ' Examples:
        config.SetEntitySetAccessRule("*", EntitySetRights.All)
        config.SetServiceOperationAccessRule("*", ServiceOperationRights.All)
    End Sub


image

Then for some variety,  I created a WPF client for this service.  I also made use of the DataGrid control from the very cool WPF Control Toollit

first I load the data…

Private Sub LoadButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles LoadButton.Click
    Context = New SuperEmployeeDomainService( _
        New Uri("http://localhost:4558/SuperEmployeeWebDataService.svc/"))
    Context.MergeOption = MergeOption.AppendOnly
 
    Dim q = From emp In Context.SuperEmployee _
    Where emp.Issues > 10 _
    Order By emp.Name _
    Select emp
 
    Dim savedCursor = Cursor
    Cursor = Cursors.Wait
    Me.DataGrid1.ItemsSource = q.ToList()
    Cursor = savedCursor
End Sub

Then update whenever there is a change.

Private Sub DataGrid1_CurrentCellChanged(ByVal sender As System.Object, ByVal e As System.EventArgs)
    Dim selectedItem As SuperEmployee = Me.DataGrid1.CurrentItem
 
    If selectedItem Is Nothing Then
        Return
    End If
 
    Dim q = From emp In Context.SuperEmployee _
                Where emp.EmployeeID = selectedItem.EmployeeID _
                Select emp
    Dim employee = q.FirstOrDefault()
 
    employee.Gender = selectedItem.Gender
    employee.Issues = selectedItem.Issues
    employee.LastEdit = selectedItem.LastEdit
    employee.Name = selectedItem.Name
    employee.Origin = selectedItem.Origin
    employee.Publishers = selectedItem.Publishers
    employee.Sites = selectedItem.Sites
 
    Context.UpdateObject(employee)
 
    Dim savedCursor = Cursor
    Cursor = Cursors.Wait
 
    Context.SaveChanges()
 
    Cursor = savedCursor
 
End Sub

image