Exercise 1: Adding an Out of Browser Application

In this part of the lab, you will add a second Silverlight application to the solution. The event administration application needs to run with elevated privileges to be able to use certain Silverlight features, which in turn means it needs to run out of browser. We want to leave the end-user-facing part of the application as it is, and while it’s possible to write a single Silverlight application that can run both inside and outside the browser (and you can show different UIs in each if necessary), there’s no real benefit to doing that in this case because the two parts of the application do quite different things. There’s no point in making the user download a larger .xap than necessary, so splitting the application in two makes sense here.

Add a New Application

  1. Open the SlEventManager solution in Visual Studio 2010.
  2. Add a new Silverlight Application project to the solution called EventAdministration.
  3. Host it in the existing SlEventManager.Web solution
  4. Enable use of WCF RIA Services
  5. Allow Visual Studio to generate a new test page (setting it as the new start page), with debugging enabled.

    Figure 1

    Adding a New Silverlight Application

    Note:
    If you build the solution right now, you’ll get compilation errors. This is because the original projects were built with the Silverlight Business Application template, which relies on building certain files into both client and server. (In general, linking multiple Silverlight projects to a single web project doesn’t cause problems. The problems here are due to how this particular template uses RIA Services.) If you look at the original SlEventManager project and expand its Web\Resources folder, you’ll see some linked files

    Figure 2

    Linked Files

    Note:
    The file names in the Visual Basic starter are slightly different than the C# one’s. The file names are RegistrationDataResources.resx and ValidationErrorResources.resx

    Note:
    The little arrow overlay icon indicates that these files (and also the Designer.cs files you’ll see if you expand the icons) are references to files that live elsewhere, rather than belonging to this project. If you select one of them and look at the Properties panel, you’ll see that the FullPath property refers to a folder in the SlEventManager.Web project—it has a Resources folder that contains these files—the icons you see in the SlEventManager project are just links pointing to those files. The types generated as a result of the RIA Services link depend on these resources being present in both projects. (This enables messages such as validation errors to be shared across the client and server code.)

    There’s an unfortunate problem we need to work around here: resource sharing between the server and client works with the Silverlight Business Application template presumes a common namespace—in our case, both the original projects shared the name SlEventManager, with the web project just adding .Web on the end. By linking to the shared resource files in a Web subfolder from the client SlEventManager project, the resources end up in the SlEventManager.Web namespace in both cases. But our new project has a default namespace of EventAdministration, which means that if we tried to link the files in in the same way, the resources would end up in a different namespace: EventAdministration.Web. This will break the shared code. The simplest solution to this is to modify the project default namespace.

Configure the New Project

  1. Go to the new project’s Properties page and to the Silverlight tab.
  2. Set the Default namespace in C# of Root namespace in Visual Basic to SlEventManager. (You can leave the assembly name as it is, and of course you can’t change the project name, because you already have a project called SlEventManager.)
  3. Add similar links in your new project by first going to the new EventAdministration project and creating a Web folder with a Resources subfolder.
  4. Use Add Existing Item to add links to the ErrorResources.resx and RegistrationDataResources.resx files in the web project. (Do not add links to the associated .Designer.cs or .Designer.vb files.)

    Note:
    To add the file as a link, click on the dropdown arrow at the right of the Add button in the Add Existing Item dialog, and choose the Add As Link menu item. Otherwise, it will make a copy of the files, which makes it possible for the two projects to get out of sync.

    Figure 3

    Add Linked Files

    Note:
    You now need to configure these two .resx files to be associated with the generated .Designer.cs files. This isn’t quite as straightforward as adding links to those files.

  5. For each .resx file, go to the Properties panel and set the Custom Tool property to PublicResXFileCodeGenerator. When

    Note:
    When you do that, Visual Studio will realize that it needs to link to the relevant generated file, and your .resx file in Solution Explorer should now contain the relevant generated file.The project should now build.

  6. Go into App.xaml.cs and change the EventAdministration namespace near the top to SlEventManager by choosing Refactor and Rename from the menu bar.(Visual Basic project don’t need do it)
  7. Say yes when Visual Studio asks if you want to change the namespace across the entire project. (This will also update the MainPage.xaml and MainPage.xaml.cs files for you.)(Visual Basic project don’t need do it)
  8. Go to the project properties page.
  9. Change the startup object from EventAdministration.App to SlEventManager.App

Configure for Out-of-Browser

Note:
Our administration application won’t be able to run at all in-browser, so the next thing we need to do is to add some UI that detects when the user has tried to open the application in the browser. If the application’s not already installed, we can offer to install it for out-of-browser use, but otherwise, we just need to direct the user to run it as a normal app

  1. Add two user controls to the project, one called WebUiNotInstalled and one called WebUiInstalled.
  2. Add the following content to WebUiInstalled:

    XAML

    <TextBlock TextWrapping="Wrap"> This application cannot run inside the web browser. It is already installed on your computer, so please run it in the usual way. </TextBlock>

  3. For the WebUiNotInstalled, we need to give the user the option to install the application. Add the following XAML inside the Grid:

    XAML

    <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <TextBlock TextWrapping="Wrap"> This application cannot run inside the web browser. To install it on your computer, please click the Install... button. </TextBlock> <Button x:Name="installButton" Grid.Row="1" Content="Install..." />

  4. Add a Click handler to the button. Inside this handler, kick off the installation of the application with the following code:

    C#

    Application.Current.Install();

    Visual Basic

    Application.Current.Install()
  5. Next, the MainPage.xaml.cs or MainPage.xaml.vb codebehind needs to decide which of these two UIs to show. In the constructor, after the call to InitializeComponent, add this code:

    C#

    if (!Application.Current.IsRunningOutOfBrowser) { if (Application.Current.InstallState == InstallState.Installed) { LayoutRoot.Children.Add(new WebUiInstalled()); } else { LayoutRoot.Children.Add(new WebUiNotInstalled()); } }
  6. Visual Basic

    If Not Application.Current.IsRunningOutOfBrowser Then If Application.Current.InstallState = InstallState.Installed Then LayoutRoot.Children.Add(New WebUiInstalled()) Else LayoutRoot.Children.Add(New WebUiNotInstalled()) End If End If
  7. Open the EventAdministration project’s property pages, and in the Silverlight tab, check the Enable running application out of the browser button.

    Figure 4

    Enabling Out-of-Browser

  8. In the SlEventManager.Web project in the Solution Explorer, right-click on EventAdministrationTestPage.html and select Set as Start Page.
  9. Run the application.
  10. You should see the UI that offers to install the application. Click the Install… button. You should see the usual confirmation dialog:

    Figure 5

    Prompt to Install Out-of-Browser Application

  11. Check the Desktop checkbox and click OK. You’ll then see the application launch out-of-browser with an empty window. It’s empty because so far, MainPage.xaml doesn’t load any UI at all if we’re running out of browser.
  12. Close both the OOB window and the browser window.
  13. Run the application again, and this time you should see the UI that tells you the application is already installed.
  14. Close the browser.
  15. Try running from the Windows Start menu or the Desktop where you installed it. Again you should see an empty window.

Setting up for Debugging OOB

We have a problem now: running the application from within Visual Studio will always show the web UI that tells us we can’t run the application from the web. This is going to make meaningful debugging of the application difficult. Now that we’ve got it installed, we need to modify the way we launch the application for debugging in Visual Studio.

  1. Open the EventAdministration project’s property pages and go to the Debug tab.
  2. Select the Installed out-of-browser application radio button.

    Figure 6

    Setting Up Debugging for Out-of-Browser

    Note:
    Normally you’d then set the EventAdministration project as the startup project, and you’d be able to debug the application purely in out-of-browser mode. However, this particular application won’t work without the web server project also running—unlike some OOB apps, this one cannot usefully run offline.

  3. Right click on the solution and choose Properties.
  4. In the CommonProperties → Startup Project section, choose Multiple startup projects, and configure both EventAdministration and SlEventManager.Web to run.

    Figure 7

    Multiple Startup Projects

  5. Run the application again, and you should now find that Visual Studio launches both the web browser and the out-of-browser windows. The first time you do that, you’ll see this dialog:

    Figure 8

    Warning Dialog

  6. This dialog appears when you debug a project with the RIA Services Link enabled. Check the checkbox and click Yes, because in fact you are running the web project as well as the Silverlight application.

    Note:
    The dialog doesn’t take into account out-of-browser RIA Services scenarios.The debugger will attach itself to both Silverlight applications (and also to the web application).

    When you use out-of-browser debugging support, you don’t need to worry about deploying updates to the application each time you build. Visual Studio will always debug the most recently built version.

  7. Add another user control to the EventAdministration project, called OobUi.This will be the main out of browser user interface.
  8. Add a TextBlock in there with the text “OOB” so that this control is distinct.
  9. In the MainPage.xaml.cs or MainPage.xaml.vb , modify the code that decides which UI to show, so that it show this new OobUi control when we’re out of the browser:

    C#

    if (!Application.Current.IsRunningOutOfBrowser) { if (Application.Current.InstallState == InstallState.Installed) { LayoutRoot.Children.Add(new WebUiInstalled()); } else { LayoutRoot.Children.Add(new WebUiNotInstalled()); } } else { LayoutRoot.Children.Add(new OobUi()); }
  10. Visual Basic

    If Not Application.Current.IsRunningOutOfBrowser Then If Application.Current.InstallState = InstallState.Installed Then LayoutRoot.Children.Add(New WebUiInstalled()) Else LayoutRoot.Children.Add(New WebUiNotInstalled()) End If Else LayoutRoot.Children.Add(New OobUi) End If
  11. Run the application again and confirm that you see the OobUi when you run out of browser.

    Note:
    Because you need to run the web project and the Silverlight project, both in-browser and out-of-browser versions of your application will be running. Since the debugger attaches to both, debugging this kind of code can occasionally be surprising. If you put breakpoints in this code to follow it through, you need to remember that you’ll be debugging two processes simultaneously taking different paths through the same code.

Check for Updates

  1. The next few steps will alert the user when an update is available for the application.
  2. Open OobUi.xaml.cs or OobUi.xaml.vb and add an event handler for the Application.Current.CheckAndDownloadUpdatedCompleted event inside the OobUi constructor.

    C#

    Application.Current.CheckAndDownloadUpdateCompleted += new CheckAndDownloadUpdateCompletedEventHandler( Current_CheckAndDownloadUpdateCompleted);
  3. Visual Basic

    AddHandler Application.Current.CheckAndDownloadUpdateCompleted, AddressOf Current_CheckAndDownloadUpdateCompleted
  4. Add the following method call to check for updates, in the OobUi constructor:

    C#

    Application.Current.CheckAndDownloadUpdateAsync();
  5. Visual Basic

    Application.Current.CheckAndDownloadUpdateAsync()
  6. Add the event handler that tells the user if an update is available:

    C#

    void Current_CheckAndDownloadUpdateCompleted( object sender, CheckAndDownloadUpdateCompletedEventArgs e) { if (e.UpdateAvailable) { MessageBox.Show("An update is available for this application. Please close the application and restart it."); } }
  7. Visual Basic

    Sub Current_CheckAndDownloadUpdateCompleted(ByVal sender As Object, ByVal e As CheckAndDownloadUpdateCompletedEventArgs) If e.UpdateAvailable Then MessageBox.Show("An update is available for this application. Please close the application and restart it.") End If End Sub

Wire the UI to the Domain Service

Now that our application is choosing the right UI to show based on how it is launched, we need to start wiring the OobUi into our domain service.To begin with, we need to log in, so we need the user to provide credentials. You might be tempted to copy across the login UI code from the SlEventManager project. However, doing so requires copying substantial amounts of infrastructure code over, so it’s easier just to build our own login UI.

  1. Add a new Silverlight Child Window to the EventAdministration project, called LoginWindow.
  2. Replace the content of the window’s Grid with this

    XAML

    <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="200" /> </Grid.ColumnDefinitions> <Border x:Name="invalidCredentials" Visibility="Collapsed" Grid.ColumnSpan="2" Padding="3" Margin="3" HorizontalAlignment="Center" Background="Red"> <TextBlock Foreground="White" FontWeight="Bold" Text="Either the user name or password is incorrect" /> </Border> <TextBlock Text="User name:" Grid.Row="1" Margin="3" VerticalAlignment="Center" /> <TextBox x:Name="userNameText" Grid.Row="1" Grid.Column="1" Margin="3" /> <TextBlock Text="Password:" Grid.Row="2" Margin="3" VerticalAlignment="Center" /> <PasswordBox x:Name="passwordText" Grid.Column="2" Grid.Row="2" Margin="3" /> <StackPanel Grid.Row="3" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Right" > <Button x:Name="OkButton" Content="OK" MinWidth="75" Margin="10" Click="OKButton_Click" /> <Button x:Name="CancelButton" Content="Cancel" MinWidth="75" Margin="10" Click="CancelButton_Click" /> </StackPanel>
  3. Delete the Width and Height properties of the LoginWindowand set the Title to Login.
  4. In the LoginWindow.xaml.cs or LoginWindow.xaml.vb codebehind, add this namespace declaration:

    C#

    using System.ServiceModel.DomainServices.Client.ApplicationServices;
  5. Visual Basic

    Imports System.ServiceModel.DomainServices.Client.ApplicationServices
  6. Then replace the body of the OKButton_Click handler method with this:

    C#

    WebContext.Current.Authentication.Login( new LoginParameters(userNameText.Text, passwordText.Password), loginOperation => { if (loginOperation.LoginSuccess) { this.DialogResult = true; } else { invalidCredentials.Visibility = Visibility.Visible; } }, null);
  7. Visual Basic

    WebContext.Current.Authentication.Login(New LoginParameters(userNameText.Text, passwordText.Password), Sub(loginOperation) If loginOperation.LoginSuccess Then Me.DialogResult = True Else invalidCredentials.Visibility = Visibility.Visible End If End Sub, Nothing)
    Note:
    This attempts to log the user in, and if it fails, shows an error, and if it succeeds, closes the login child window.For this to work, that WCF RIA Services need to know how it’s supposed to be authenticating.

  8. In the App.xaml for the OOB project, add the following after the closing Application.Resources tag (but before the closing Application tag) so

    XAML

    <Application.ApplicationLifetimeObjects> <app:WebContext> <app:WebContext.Authentication> <appsvc:FormsAuthentication/> </app:WebContext.Authentication> </app:WebContext> </Application.ApplicationLifetimeObjects>
  9. You’ll need to add these namespace declarations to the root element:

    XAML

    xmlns:appsvc="clr-namespace:System.ServiceModel.DomainServices.Client.ApplicationServices;assembly=System.ServiceModel.DomainServices.Client.Web" xmlns:app="clr-namespace:SlEventManager"
  10. Go to OobUi.xaml and add a button with Content="Login" and add a Click handler. Add this code in the handler

    C#

    LoginWindow w = new LoginWindow(); w.Show();
  11. Visual Basic

    Dim w As New LoginWindow() w.Show()
  12. Run the application.
  13. Click the Login button. The login UI should appear much as it does in the web-hosted Silverlight application from earlier labs. Try logging in with invalid credentials. The UI should show a message saying “The username or password is incorrect”. (This might be quite slow first time you try it.)
  14. Try logging in with correct credentials. (Use “administrator” and “P@ssw0rd”.) The login should succeed, verifying that the credentials are being correctly validated.

    Note:
    There are two additional steps you could do if you’d like to improve the user experience. Neither of these is necessary for the rest of the lab, but would make the application better.

    You could add code to change the login button to a logout one once you’ve logged in—you can log out by calling the WebContext.Current.AuthenticationService.Logout method.

    You could add a BusyIndicator to the login control, and make it visible while waiting for a response from the service, so that the user knows something is happening. The BusyIndicator control is in the Silverlight toolkit. (In the November 2009 toolkit, it’s in the ‘experimental’ band, so you’ll need to add the relevant pieces to the project yourself—you won’t find it in the Visual Studio Toolbox.)

Loading the Dashboard

Now that we can authenticate with our server, let’s add some code to show the information this administrator dashboard needs to display. We need an extra service operation to retrieve a list of attendee registrations that we’ve not yet acknowledged.

  1. Add the following method to the EventManagerDomainService in the SlEventManager.Web project’s Services folder:

    C#

    [RequiresRole("Event Administrators")] public IQueryable<AttendeeEvent> GetUnacknowledgedAttendeeEventsWithEvents() { return from attendeeEvent in this.ObjectContext.AttendeeEvents.Include("Event") where !attendeeEvent.IsAcknowledged select attendeeEvent; }
  2. Visual Basic

    <RequiresRole("Event Administrators")> Public Function GetUnacknowledgedAttendeeEventsWithEvents() As IQueryable(Of AttendeeEvent) Return From attendeeEvent In Me.ObjectContext.AttendeeEvents.Include("Event") Where (Not attendeeEvent.IsAcknowledged) Select attendeeEvent End Function
  3. In the EventManagerDomainService.metadata.cs or EventManagerDomainService.metadata.vb file, find the AttendeeEventMetadata class and add an [Include] attribute to the Event field.

    Note:
    This service operation will enable the client to discover when attendees have registered for an event and their registration has not yet been acknowledged. However, the information about the user’s name and email lives elsewhere: we’ve been using the ASP.NET Membership and Profile handling for that. So we need to add more to the service to make that accessible to the client.

  4. Add a new class to the web project’s Models folder called UserDisplayDetails. Add this using declaration:

    C#

    using System.ComponentModel.DataAnnotations;
  5. Visual Basic

    Imports System.ComponentModel.DataAnnotations
  6. implement the class as follows:

    C#

    public class UserDisplayDetails { [Key] public int AttendeeID { get; set; } public string FriendlyName { get; set; } public string Email { get; set; } }
  7. Visual Basic

    Public Class UserDisplayDetails <Key()> Public Property AttendeeID() As Integer Public Property FriendlyName() As String Public Property Email() As String End Class
    Note:
    The Key attribute is required when returning custom types from a WCF RIA Service. It is used to determine the logical identity of an object. (Multiple service calls may end up returning the same object, and a key makes it possible to reconcile these back to the same instance on the client.) Without this, we would get an error if we tried to return objects of this type.

  8. Add the following method to EventManagerDomainService, which returns the display details for the requested attendee IDs:

    C#

    [RequiresRole("Event Administrators")] public IEnumerable<UserDisplayDetails> GetUserDisplayDetails(int[] attendeeIds) { var dbAttendeeQuery = from attendee in this.ObjectContext.Attendees where attendeeIds.Contains(attendee.AttendeeID) select new { attendee.AttendeeID, attendee.AspNetUserId }; var allUsers = Membership.GetAllUsers().OfType<MembershipUser>(); var results = from attendeeInfo in dbAttendeeQuery.ToList() let aspNetUser = allUsers.FirstOrDefault(mu => ((Guid) mu.ProviderUserKey) == attendeeInfo.AspNetUserId) where aspNetUser != null let prof = ProfileBase.Create(aspNetUser.UserName) where prof != null select new UserDisplayDetails { AttendeeID = attendeeInfo.AttendeeID, Email = aspNetUser.Email, FriendlyName = prof.GetPropertyValue("FriendlyName") as string }; return results; }
  9. Visual Basic

    <RequiresRole("Event Administrators")> Public Function GetUserDisplayDetails(ByVal attendeeIds() As Integer) As IEnumerable(Of UserDisplayDetails) Dim dbAttendeeQuery = From attendee In Me.ObjectContext.Attendees Where attendeeIds.Contains(attendee.AttendeeID) Select New With {Key attendee.AttendeeID, Key attendee.AspNetUserId} Dim allUsers = Membership.GetAllUsers().OfType(Of MembershipUser)() Dim results = From attendeeInfo In dbAttendeeQuery.ToList() Let aspNetUser = allUsers.FirstOrDefault(Function(mu) (CType(mu.ProviderUserKey, Guid)) = attendeeInfo.AspNetUserId) Where aspNetUser IsNot Nothing Let prof = System.Web.Profile.ProfileBase.Create(aspNetUser.UserName) Where prof IsNot Nothing Select New UserDisplayDetails With {.AttendeeID = attendeeInfo.AttendeeID, .Email = aspNetUser.Email, .FriendlyName = TryCast(prof.GetPropertyValue("FriendlyName"), String)} Return results End Function
  10. You’ll need to add this using directives to the file for this to compile:

    C#

    using System.Web.Profile; using SlEventManager.Web.Models;
  11. Visual Basic

    Imports System.Web.Profile Imports SlEventManager.Web.Models
  12. Add a DataGrid to the OobUI.xaml file called unacknowledgedEvents.
  13. Set its AutoGenerateColumns property to True.
  14. Add another button labeled “Get”, and add a Click handler.
  15. In the code behind, add this namespace directive:

    C#

    using SlEventManager.Web.Services;
  16. Visual Basic

    Imports SlEventManager.Web.Services
  17. Then in the “Get” button’s Click handler, add the following code to fetch the list of unacknowledged event registrations, and then to fetch the name and email details for all the attendees in question:

    C#

    EventManagerDomainContext ctx = new EventManagerDomainContext(); ctx.Load(ctx.GetUnacknowledgedAttendeeEventsWithEventsQuery(), loadUnackOp => { int[] attendeeIds = (from atev in loadUnackOp.Entities select atev.AttendeeID).Distinct().ToArray(); ctx.Load(ctx.GetUserDisplayDetailsQuery(attendeeIds), loadDetailsOp => { var all = loadDetailsOp.Entities.ToArray(); unacknowledgedEvents.ItemsSource = all; }, null); }, null);
  18. Visual Basic

    Dim ctx As New EventManagerDomainContext() ctx.Load(ctx.GetUnacknowledgedAttendeeEventsWithEventsQuery(), Sub(loadUnackOp) Dim attendeeIds() As Integer = ( From atev In loadUnackOp.Entities Select atev.AttendeeID).Distinct().ToArray() ctx.Load(ctx.GetUserDisplayDetailsQuery(attendeeIds), Sub(loadDetailsOp) Dim all = loadDetailsOp.Entities.ToArray() unacknowledgedEvents.ItemsSource = all End Sub, Nothing) End Sub, Nothing)
  19. Run the application.
  20. Log in as “administrator” (password “P@ssw0rd”).
  21. Click the Get button, and the grid should be populated with the name and email of any attendee registered for an event who has not yet received an acknowledgement email. (This will probably be just the “ian” user, unless you’ve registered more users in your application.)
  22. Close the application.

Adding ViewModels

The information being displayed isn’t quite what we need. We also need to show the event for which the user is registered. We’ll add a per item view model class for this.

  1. In the EventAdministration project, add a ViewModels folder.
  2. Create a UnacknowledgedRegistrationViewModel class in the ViewModels folder.
  3. Add the following properties to the View Model:

    C#

    public class UnacknowledgedRegistrationViewModel { public string EventTitle { get; set; } public string UserDisplayName { get; set; } public string UserEmail { get; set; } }
  4. Visual Basic

    Public Class UnacknowledgedRegistrationViewModel Public Property EventTitle() As String Public Property UserDisplayName() As String Public Property UserEmail() As String End Class
  5. That will be the view model class used to represent individual items in the list. We should also add a view model for the whole OobUi.
  6. In the ViewModels folder, add an OobUiViewModel class.
  7. Copy the ViewModelBase class from the SlEventManager project into this project, and make it the base class of OobUiViewModel.
  8. Add the following using declarations:

    C#

    using System.Collections.Generic; using System.Linq; using SlEventManager.Web.Services;
  9. Visual Basic

    Imports System.Collections.Generic Imports System.Linq Imports SlEventManager.Web.Services
  10. The view model will need to provide a collection to act as the data grid’s source:

    C#

    private IList<UnacknowledgedRegistrationViewModel> _unacknowledgedRegistrations; public IList<UnacknowledgedRegistrationViewModel>UnacknowledgedRegistrations { get { return _unacknowledgedRegistrations; } set { if (_unacknowledgedRegistrations != value) { _unacknowledgedRegistrations = value; OnPropertyChanged("UnacknowledgedRegistrations"); } } }
  11. Visual Basic

    Private _unacknowledgedRegistrations As IList(Of UnacknowledgedRegistrationViewModel) Public Property UnacknowledgedRegistrations() As IList(Of UnacknowledgedRegistrationViewModel) Get Return _unacknowledgedRegistrations End Get Set(ByVal value As IList(Of UnacknowledgedRegistrationViewModel)) If _unacknowledgedRegistrations IsNot value Then _unacknowledgedRegistrations = value OnPropertyChanged("UnacknowledgedRegistrations") End If End Set End Property
  12. Add the following field, since we will need a domain context to use the service:

    C#

    private EventManagerDomainContext ctx = new EventManagerDomainContext();
  13. Visual Basic

    Private ctx As New EventManagerDomainContext()
  14. Add an OnGet method that will handle the click.

    Note:
    This will be similar to the click handler you wrote earlier, but this time, rather than just showing the user names, it will generate an item for each event registration. (Individual users may register for multiple events.) And it builds an instance of the item view model for each row, providing the exact details we wish to show:

    C#

    public void OnGet() { ctx.Load(ctx.GetUnacknowledgedAttendeeEventsWithEventsQuery(), loadUnackOp => { int[] attendeeIds = (from atev in loadUnackOp.Entities select atev.AttendeeID).Distinct().ToArray(); ctx.Load(ctx.GetUserDisplayDetailsQuery(attendeeIds), loadDetailsOp => { var attendeeDetails = loadDetailsOp.Entities.ToDictionary(d =>d.AttendeeID); var registrations = from atev in loadUnackOp.Entities let details = attendeeDetails[atev.AttendeeID] select new UnacknowledgedRegistrationViewModel { EventTitle = atev.Event.EventTitle, UserDisplayName = details.FriendlyName, UserEmail = details.Email }; UnacknowledgedRegistrations = registrations.ToList(); }, null); }, null); }
  15. Visual Basic

    Public Sub OnGet() ctx.Load(ctx.GetUnacknowledgedAttendeeEventsWithEventsQuery(), Sub(loadUnackOp) Dim attendeeIds() As Integer = ( From atev In loadUnackOp.Entities Select atev.AttendeeID).Distinct().ToArray() ctx.Load(ctx.GetUserDisplayDetailsQuery(attendeeIds), Sub(loadDetailsOp) Dim attendeeDetails = loadDetailsOp.Entities.ToDictionary(Function(d) d.AttendeeID) Dim registrations = From atev In loadUnackOp.Entities Let details = attendeeDetails(atev.AttendeeID) Select New UnacknowledgedRegistrationViewModel With {.EventTitle = atev.Event.EventTitle, .UserDisplayName = details.FriendlyName, .UserEmail = details.Email} UnacknowledgedRegistrations = registrations.ToList() End Sub, Nothing) End Sub, Nothing) End Sub

Binding the ViewModels to the Views

  1. In the OobUi.xaml.cs or OobUi.xaml.vb code behind, add a field declaring and constructing an instance of OobUiViewModel.
  2. Add the following using statement so you can reference the View Model.

    C#

    using SlEventManager.ViewModels;
  3. Visual Basic

    Imports SlEventManager.ViewModels
  4. In the constructor, put a reference to the view model in the DataContext.
  5. Replace the entire Get button click handler with a call to the view model’s OnGet method.
  6. Finally, in OobUi.xaml, bind the DataGrid control’s ItemsSource to the UnacknowledgedRegistrations property of the view model:

    C#

    ItemsSource="{Binding Path=UnacknowledgedRegistrations}"
  7. Run the application, log in as administrator (P@ssw0rd), and click the Get button. You should now see a list showing event titles, names, and email addresses. There will be at least two entries because the “Ian Griffiths” user has registered for two events.