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
Comments
Anonymous
July 29, 2009
Very Great Article. I like it. I will try this style in my next project. Keep it up.Anonymous
July 29, 2009
Excellent Series Of Articles Please How to integrate Silverlight Controls With ASP.Net MVC ?Anonymous
July 30, 2009
The comment has been removedAnonymous
July 30, 2009
Bell -- i am not sure.. do you have the WPF Toolkit installed? http://www.codeplex.com/wpfAnonymous
July 30, 2009
The comment has been removedAnonymous
July 30, 2009
The WPF Toolkit fixed the project errors, thanks. Any quick way to workaround the db issue? I'm not eager to install sql 2008 express just for this app.Anonymous
July 30, 2009
Sure -- you could use the in memory data scenario I describe here.. http://blogs.msdn.com/brada/archive/2009/07/22/business-apps-example-for-silverlight-3-rtm-and-net-ria-services-july-update-part-6-poco-and-authentication-provider.aspx I didn't port it to VB, but it shouldn't be too hard to pull over..Anonymous
July 30, 2009
Isn't is strange that while the sln will build, when the app is run, and after the db error, there are 52 errors? They go away if I rebuild, at least till the app tries to run again...but isn't that kind of weird?Anonymous
July 31, 2009
The comment has been removedAnonymous
August 02, 2009
William Bartholomew - thanks! We will get that fixed.