Business Apps Example for Silverlight 3 RTM and .NET RIA Services July Update: Part 14: Visual Basic (VB) and WPF Support
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):
- VS2008 SP1 (Which includes Sql Express 2008)
- Silverlight 3 RTM
- .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
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
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
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
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
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("https://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