Chapter 19. Windows Presentation Foundation UI

Chapters 17 and 18 focused on creating business objects and enabling them with data access. The business objects, and the business logic they contain, are the centerpiece of any application, and those objects make the behaviors defined in your use cases (from Chapter 3) available for use when building applications.

But to the end user, the application is really more about the user interface than it is about data access or object-oriented design. The user experience is absolutely central to the application. So while the business logic and data access layers implement the majority of the application's functionality, it is the UI that makes or breaks the application.

In .NET 3.0, Microsoft introduced the WPF, which is the next-generation UI technology for the development of Windows applications.

Note

Closely related to WPF, Silverlight is used for building rich client applications that deploy and execute within a web browser. You can get more information about CSLA .NET for Silverlight at www.lhotka.net/cslalight.

In this chapter, I assume you have a basic understanding of WPF. This includes the concepts of XAML programming, how code-behind works in a WPF application, and XML namespaces.

WPF provides many of the best features of both Windows and web development. It is a rich, interactive, event-driven environment similar to Windows Forms, yet it uses an XML-based, stylable tag markup language to describe the presentation, much like the Web.

The challenge with WPF is that it has aspects that are similar to both Windows Forms and Web Forms development, yet it is fundamentally different from both of those technologies. This leads to a learning curve that you must overcome before you can be productive in the new environment.

Like Windows Forms, WPF provides powerful data binding support, using an event-driven model for interaction between the UI controls and the business objects. Like Web Forms, WPF uses a data control model (called a data provider control) to load data into a form. And like its ASP.NET equivalent, the ObjectDataProvider control is too limited to work with CSLA .NET-style business objects. To address this, CSLA .NET includes a CslaDataProvider control for WPF.

This chapter will cover the basic process of building a WPF UI on top of the business objects created in Chapters 17 and 18. To do this, I will use data binding, along with several CSLA .NET controls from the Csla.Wpf namespace. The resulting WPF forms have little code-behind, because most of the work is done through XAML or by the CSLA .NET controls.

Before getting into the creation of the UI, it is important to discuss one higher-level issue regarding custom authentication.

Custom Authentication in WPF

WPF is not thread-safe. This means that all UI work must be done on the single UI thread. Nevertheless, WPF does use background threads behind the scenes. The security infrastructure in .NET is driven off the current principal object, which is associated with a specific thread. This means that you can run into cases where your code is running on a thread that doesn't have the right principal.

This issue only applies if you're using custom authentication. If you're using Windows authentication, then all threads will use the WindowsPrincipal for the user logged into the workstation, and there's no issue.

There are a number of possible scenarios with custom authentication and custom principal objects, but they all have the same issue: one or more threads end up with the incorrect principal. This is problematic, because the authorization and data portal subsystems of CSLA .NET rely on the .NET principal object to do their work. If you can't rely on having a valid principal, authorization is difficult to implement, and the data portal can't reliably impersonate the client user identity on the application server.

The behavior of WPF in this area is intentional; Microsoft is trying to protect the application from a scenario in which one thread accidentally changes the principal on other threads. The unfortunate side effect of this protection is that it is nearly impossible to build a WPF application that uses custom authentication.

The core issue is that WPF doesn't allow a thread to alter the current principal for other threads. Even if you attempt to do this, the WPF infrastructure will often reset the principal to a default value.

Microsoft's recommended solution is to set the default principal value on the AppDomain:

AppDomain.CurrentDomain.SetThreadPrincipal(principal);

Once this line of code runs, all threads created from that point forward will use the new default principal. However, that's a serious limitation, because it means you need to execute this line of code before any background threads are created, including those on the thread pool. This means your login process can't use any asynchronous behaviors (no async data portal or service calls), and it must occur very early in your application's lifetime to ensure no other async operations have occurred.

Also, this line of code can only be called one time during the application's lifetime. So if your application allows the user to log out and then log in later (without closing the application), you won't be able to use this technique at all.

The CSLA .NET ApplicationContext object has a partial workaround to this issue that solves the problem for all CSLA .NET code, as well as code that uses ApplicationContext.User to access the current principal. It is a partial solution, because CSLA .NET can't ensure that the actual CurrentPrincipal on each thread is always correct, so while all CSLA .NET code will work fine, some .NET features—most notably Code Access Security (CAS)—may not function at all times.

I'm discussing this issue because WPF developers commonly encounter it. While CSLA .NET addresses this issue to a large degree, you should understand that it exists and that it is caused by WPF working to prevent one thread from altering the principal used by other threads.

As long as you use Csla.ApplicationContext.User to access the principal, which is what CSLA .NET itself does, you should be able to alter the principal value during the lifetime of the application and have a consistent value available to all threads.

Interface Design

The UI application can be found within the ProjectTracker solution. The project is named PTWpf. The PTWpf interface contains a single main form with a set of navigation links on the left and a content area on the right. The main form loads user controls into the content area dynamically, displaying them to the user. Figure 19-1 shows what the main form looks like.

Appearance of the main form

Figure 19.1. Appearance of the main form

Notice that the navigation items on the left deal with projects, resources, roles, and authentication. When the user clicks a link, a user control is loaded dynamically into the main area of the form. Figure 19-2 shows the application while the user is editing a project.

Editing a project

Figure 19.2. Editing a project

Of course, some dialog windows are used to collect input from the user as well, but the bulk of the application's functionality centers around the use of user controls hosted by the main form.

Table 19-1 lists the forms and controls that make up the interface.

Table 19.1. Forms and User Controls in PTWpf

Form/Control

Type

Description

MainForm

Window

The main form for the application

EditForm

UserControl

A custom base class for creating user controls

ListTemplateConverter

IValueConverter

Converts a Boolean value to a DataTemplate value

Login

Window

A login dialog to collect user credentials

RolesEdit

EditForm

Allows the user to edit the list of roles

ProjectSelect

Window

A dialog prompting the user to select from a list of projects

ProjectList

EditForm

Displays a list of projects

ProjectEdit

EditForm

Allows the user to view, add, or edit a project

ResourceSelect

Window

A dialog prompting the user to select from a list of resources

ResourceList

EditForm

Displays a list of resources

ResourceEdit

EditForm

Allows the user to view, add, or edit a resource

VisibilityConverter

IValueConverter

Converts a Boolean value to a Visibility value

The user control approach taken in this chapter gives you a great deal of flexibility. You can host the user controls in child forms in an MDI interface, as shown in this chapter, or you can host them in panes in a multipane interface. In short, by creating your "forms" as user controls, you gain the flexibility to use them in many different types of UI design.

User Control Framework

Loading a user control dynamically isn't difficult. The code needs to follow this basic process:

  1. Create the EditForm control.

  2. Remove any existing EditForm controls from view.

  3. Add a new EditForm to the Children collection.

  4. Hook the TitleChanged event for a new EditForm.

Although my intent with this chapter isn't for you to create a full-blown WPF UI framework, you must do some basic work to provide for a decent user experience.

The steps listed here require some interaction between each UserControl and the hosting MainForm. To standardize this process, I've created an EditForm class, which is a subclass of UserControl. This class provides some standard behaviors MainForm can count on for each control it hosts.

The EditForm Class

The primary purpose of the EditForm class is to standardize how each control is hosted in MainForm. However, it also provides some common behaviors that are useful to any data-bound form. Table 19-2 lists the behaviors provided by EditForm.

Table 19.2. Behaviors Implemented in EditForm

Behavior

Description

Principal changed

Enables a scheme by which MainForm can notify the active EditForm that the current .NET principal has changed

Provides a Title property

Extends UserControl by adding a Title property that can be set through XAML or code; includes an event so MainForm knows the Title has changed so it can update the main form caption

Error handling

Provides a standard way for any data provider to display exceptions that occur during data access

I'll discuss each behavior individually.

Principal Changed

When the current .NET principal changes, it means that the user has logged in or out of the application, so any authorization rules must be reapplied for the currently displayed EditForm. The exact impact of a user logging in or out is up to the individual EditForm, so the base class simply notifies the EditForm subclass by invoking a virtual method.

void EditForm_Loaded(object sender, System.Windows.RoutedEventArgs e)
  {
    ApplyAuthorization();
  }

  void IRefresh.Refresh()
  {
    ApplyAuthorization();
  }

  protected virtual void ApplyAuthorization()
  {
  }

The ApplyAuthorization() method is invoked both when the edit form is first loaded and then when MainForm calls the Refresh() method because the current principal has changed.

Each individual subclass can override ApplyAuthorization() to reapply authorization rules based on the new .NET principal. I'll show how this works when I discuss an actual edit form implementation.

Title Property

In this application, I want each edit form to be able to alter the top-level window caption. That caption is controlled by MainForm, so there needs to be some mechanism by which each edit control can ask MainForm to change the caption.

In WPF, data-bindable properties of controls are implemented as dependency properties. A dependency property is similar to a normal property, but it is tied into the WPF (and WF) infrastructure, so the property can be bound to values through XAML. Here's the Title property:

public event EventHandler TitleChanged;

  public static readonly DependencyProperty TitleProperty =
      DependencyProperty.Register("Title", typeof(string),
                                  typeof(EditForm), null);

  public string Title
  {
    get { return (string)GetValue(TitleProperty); }
    set
    {
      SetValue(TitleProperty, value);
      if (TitleChanged != null)
        TitleChanged(this, EventArgs.Empty);
    }
  }

The TitleChanged event is a standard event, and MainForm handles this event so that it's notified when the value has been changed. The property declaration itself is a dependency property, so the value can be set through XAML. For example, here's how an EditForm instance is declared:

<local:EditForm x:Class="PTWpf.ProjectEdit"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Edit a Project">

The Title property can be set here in the XAML or in the code behind the form. Either way, the TitleChanged event is raised, so MainForm can update the main window caption.

Error Handling

When using a WPF data provider for data access, handling exceptions is a little tricky. The reason is that the data access is triggered by the control, not by your code, so there's no way for you to wrap the operation in a try..catch block to handle any exception.

The standard technique is to handle the DataChanged event of the data provider control and to see if the Error property of the data provider is anything other than null. If it is, then an exception occurred.

For this sample application, I've chosen to simply display the exception details to the user. Rather than repeating that code in each individual edit form, the EditForm base class provides an event handler to handle all such exceptions.

protected virtual void DataChanged(object sender, EventArgs e)
  {
    var dp = sender as System.Windows.Data.DataSourceProvider;
    if (dp.Error != null)
      MessageBox.Show(
        dp.Error.ToString(),
        "Data error",
        MessageBoxButton.OK,
        MessageBoxImage.Exclamation);
  }

Each edit form is responsible for hooking its data provider DataChanged events to this handler, which can be done through XAML or code-behind. I'll show how this is done when I discuss individual edit form implementations.

The MainForm Window

The other area of common plumbing code is in the MainForm class itself. MainForm acts as the overall application shell, hosting and coordinating all other windows and edit forms. In this section, I'll discuss only the code required for interaction with each EditForm, leaving discussion of the navigation and authentication for later in the chapter.

The content area of MainForm is a DockPanel control.

<DockPanel Grid.Column="1" Name="contentArea"
                 Margin="5,15,20,15" />

This DockPanel fills all the available space on the right-hand side of the form, maintaining a bit of a margin to keep the content inside the rounded corners (see Figure 19-1). By naming the control contentArea, the code behind the form is able to manipulate the control.

Moving from one edit form to another by showing the correct content in contentArea, MainForm implements a ShowControl() method to enable navigation:

public static void ShowControl(UserControl control)
  {
    _mainForm.ShowUserControl(control);
  }
private void ShowUserControl(UserControl control)
  {
    UnhookTitleEvent(_currentControl);

    contentArea.Children.Clear();
    if (control != null)
      contentArea.Children.Add(control);
    _currentControl = control;

    HookTitleEvent(_currentControl);
  }

The ShowControl() method is easily accessible to all code in the project, because it is a static method that is public. However, the actual instance of MainForm must display the user control, so the static method delegates to an instance method on the actual MainForm instance. The _mainForm field references the one instance of MainForm, and its value is set in the constructor of MainForm.

The ShowUserControl() method unhooks the TitleChanged event from any existing control.

private void UnhookTitleEvent(UserControl control)
  {
    EditForm form = control as EditForm;
    if (form != null)
      form.TitleChanged -= new EventHandler(SetTitle);
  }

The ShowUserControl() method then clears the Children collection of contentArea, ensuring the DockPanel is empty. Then it adds the new user control to the Children collection, effectively displaying the new content.

Finally, it hooks the TitleChanged event of the new control.

private void HookTitleEvent(UserControl control)
  {
    SetTitle(control, EventArgs.Empty);
    EditForm form = control as EditForm;
    if (form != null)
      form.TitleChanged += new EventHandler(SetTitle);
  }

This method also immediately sets the main window caption based on the current title of the new user control. This is done by calling the SetTitle() method.

private void SetTitle(object sender, EventArgs e)
  {
    EditForm form = sender as EditForm;
    if (form != null && !string.IsNullOrEmpty(form.Title))
      _mainForm.Title =
        string.Format("Project Tracker - {0}", ((EditForm)sender).Title);
    else
      _mainForm.Title = string.Format("Project Tracker");
  }

Notice how MainForm remains in control of the main window caption, using the Title property from the current user control to alter the overall value. This helps keep the application maintainable, because the window caption is controlled in a central location.

At this point, you should have an understanding of how MainForm hosts user controls and how the EditForm base class makes it easier to create standardized user controls for this purpose.

Value Converters

WPF allows you to do a great deal directly through XAML, minimizing code behind each window or user control. One powerful technique you can use to shift code into XAML is to implement a value converter control. These controls convert a value of one type, such as bool, to another type or value.

To create a value converter, you create a class that implements the IValueConverter interface from the System.Windows namespace. This interface requires that you implement the methods listed in Table 19-3. These methods are invoked by WPF data binding as needed.

Table 19.3. Methods Defined by IValueConverter

Method

Description

Convert()

Converts a data source value into a UI value

ConvertBack()

Converts a UI value into a data source value

One common use of a value converter is to change a DateTime property from a business object into a properly formatted string for display to the user. That fits into most people's conception of how data binding works.

However, in WPF, data binding is far more useful and widespread. You can use data binding to connect controls to other controls or objects. The potential is amazing. For example, in PTWpf, I use data binding and value converters so the UI responds to authorization rules automatically. Based on whether the user is authorized to create or edit the business object, I'll use data binding to enable or disable UI controls, hide some controls entirely, and even change the way data is displayed in a ListBox control.

Doing this requires a couple of value converters to convert a bool value to a DataTemplate (to change how a ListBox is displayed) or to a Visibility value (to show or hide parts of the UI).

I'll discuss the VisibilityConverter first, because it is the simpler of the two.

VisibilityConverter

The VisibilityConverter class implements IValueConverter and is designed to convert a bool value to a Visibility value. It is used when binding part of the UI to an authorization property such as CanEditObject. This way, when the user can edit the object, the Visibility value can return Visible, and if the user isn't allowed to edit the object, the Visibility value will return Collapsed.

public class VisibilityConverter : System.Windows.Data.IValueConverter
{
  public object Convert(object value, Type targetType,
    object parameter, System.Globalization.CultureInfo culture)
  {
    if ((bool)value)
      return System.Windows.Visibility.Visible;
    else
      return System.Windows.Visibility.Collapsed;
  }

  public object ConvertBack(object value, Type targetType,
    object parameter, System.Globalization.CultureInfo culture)
  {
    return false;
  }
}

Only the Convert() method is fully implemented in this case, because the converter is used to bind from an ObjectStatus control to a UI control. Changes to the UI control don't bind back to ObjectStatus.

In the Convert() method, the value parameter is cast to a bool, and that value is used to determine which Visibility value to return as a result.

You'll see how this is used when I discuss the RolesEdit form.

ListTemplateConverter

The ListTemplateConverter is a little more complex, because it defines a couple of properties as well as implements IValueConverter.

public class ListTemplateConverter : System.Windows.Data.IValueConverter
{
  public System.Windows.DataTemplate TrueTemplate { get; set; }

  public System.Windows.DataTemplate FalseTemplate { get; set; }

  #region IValueConverter Members

  public object Convert(object value, Type targetType,
    object parameter, System.Globalization.CultureInfo culture)
  {
    if ((bool)value)
      return TrueTemplate;
    else
      return FalseTemplate;
  }

  public object ConvertBack(object value, Type targetType,
    object parameter, System.Globalization.CultureInfo culture)
  {
    return value;
  }

  #endregion
}

Again, only the Convert() method is fully implemented, because the converter is used to bind from an ObjectStatus control to a UI control. If there are changes to the UI control, they don't bind back to ObjectStatus.

The TrueTemplate and FalseTemplate properties are used to define the DataTemplate values to be returned based on the input bool value. In your XAML, this converter is defined as a resource, like this:

<local:ListTemplateConverter x:Key="ListTemplateConverter"
      TrueTemplate="{StaticResource editableTemplate}"
      FalseTemplate="{StaticResource readonlyTemplate}" />

The two properties are set to DataTemplate items defined in that same XAML. You'll see an example of this when I discuss the RolesEdit form later in this chapter.

Like the VisibilityConverter, this converter's logic simply uses the value parameter to determine which result to return. The value converter concept in WPF is powerful, and you can use it to shift a lot of code behind each page into reusable controls that can be used from the XAML defining each page.

Application Configuration

The application needs to provide some basic configuration information through the application's configuration file. In the client application configuration file, either you can provide connection strings so that the application can interact with the database directly, or you can configure the data portal to communicate with a remote application server. I discussed this basic concept in Chapter 15 when I covered the channel adapter implementation. Recall that the data portal supports several channels, including WCF, Remoting, Enterprise Services, and Web Services. You can create your own channels as well if none of these meet your needs.

In Chapter 1, I discussed the trade-offs between performance, scalability, fault tolerance, and security that come with various physical n-tier configurations. The most scalable solution for an intelligent client UI is to use an application server to host the data access layer, while the most performant solution is to run the data portal locally in the client process. In this chapter, I'll show first how to run the data portal locally, and then remotely using the WCF channel.

The configuration is controlled by the application's configuration file. In the Visual Studio project, this is named App.config.

Note

Naming the file App.config is important. Visual Studio 2008 will automatically copy the file into the appropriate bin directory, changing the name to match that of the program. In this case, it will change the name to PTWin.exe.config as it copies it into the bin directories. This occurs each time the project is built in Visual Studio.

Authentication

CSLA .NET controls authentication through the configuration file.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
  <add key="CslaAuthentication" value="Csla" />
  <add key="CslaPropertyChangedMode" value="Xaml" />
</appSettings>
</configuration>

The CslaAuthentication key shown here specifies the use of custom authentication. Chapter 8 implemented the PTPrincipal and PTIdentity classes specifically to support custom authentication, and the UI code in this chapter will use custom authentication as well.

If you want to use Windows authentication, change the configuration to the following:

<add key="CslaAuthentication" value="Windows" />

Of course, this change requires coding changes. To start, remove the PTPrincipal and PTIdentity classes from ProjectTracker.Library, as they will no longer be needed. Also, the login/logout functionality implemented in this chapter will become unnecessary. Specifically, the Login form and the code to display that form should be removed from the UI project.

The CslaPropertyChangedMode key shown here causes CSLA .NET to use the XAML model for raising the PropertyChanged event from all business objects. Recall from Chapters 7 and 10 that the PropertyChanged event must be raised differently for WPF/XAML data binding than for Windows Forms data binding, because the two data binding infrastructures treat the event a little differently.

It's important that this value be set for WPF applications; otherwise, data binding won't update the UI properly based on underlying changes to the business object.

Local Data Portal

The configuration file also controls how the application uses the data portal. To make the client application interact directly with the database, use the following (with your connection string changed to the connection string for your database):

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="CslaAuthentication" value="Csla" />
    <add key="CslaPropertyChangedMode" value="Xaml" />
  </appSettings>
<connectionStrings>
  <add name="PTracker" connectionString="your connection string"
    providerName="System.Data.SqlClient" />
  <add name="Security" connectionString="your connection string"
    providerName="System.Data.SqlClient" />
</connectionStrings>
</configuration>

Because LocalProxy is the default for the data portal's CslaDataPortalProxy setting, no actual data portal configuration is required, so the only settings in the configuration file are to control authentication and to provide the database connection strings.

Remote Data Portal

To make the data portal use an application server and communicate using the WCF channel, use the following configuration:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="CslaAuthentication" value="Csla" />
    <add key="CslaPropertyChangedMode" value="Xaml" />
  <add key="CslaDataPortalProxy"
    value="Csla.DataPortalClient.WcfProxy, Csla"/>
  </appSettings>
  <connectionStrings>
  </connectionStrings>
<system.serviceModel>
  <client>
    <endpoint name="WcfDataPortal"
              address="http://localhost:4147/WcfHost/WcfPortal.svc"
              binding="wsHttpBinding"
              contract="Csla.Server.Hosts.IWcfPortal" />
  </client>
</system.serviceModel>
</configuration>

The key lines for the WCF configuration are in bold. Of course, you need to change localhost:4147 to the name of the application server on which the data portal host is installed. Also, the WcfHost text needs to be replaced with the name of your virtual root on that server.

Before using this configuration, you must create and configure the WCF host virtual root. I'll show how you do this in the next section.

The most important thing to realize about the application configuration is that the data portal can be changed from local to remote (using any of the network channels) with no need to change any UI or business object code.

Configuring the Data Portal Server

When using a remote data portal configuration, the client communicates with an application server. Obviously, this means that an application server must exist and be configured properly. When the client data portal is configured to use WCF, you must supply a properly configured WCF application server.

Note

No data portal server is required when the data portal is configured for local mode. In that case, the "server-side" components run in the client process, and no application server is used or required.

The WcfHost website in the ProjectTracker download is an example of a WCF application server hosted in IIS. You may also choose to host the WCF server in a custom Windows service, in a custom EXE, or using WAS. These various hosts are similar to IIS in many ways, but are outside the scope of this book.

The WcfHost website is simple, because it relies on preexisting functionality provided by CSLA .NET, as discussed in Chapter 15. Table 19-4 lists the key elements of the website.

Table 19.4. Key Elements of the WcfHost Website

Element

Description

WcfPortal.svc

WCF service endpoint file, referencing the Csla.Server.Hosts.WcfPortal class

bin

Standard web bin folder containing Csla.dll, ProjectTracker.Library.dll, and any other assemblies required by the business library; the important thing is that your business assemblies and Csla.dll be present in this folder

web.config

Standard web.config file, but containing a system.serviceModel element to configure the WCF endpoint for the data portal

Any website containing the elements in Table 19-4 can act as a WCF data portal host. The web.config file must contain the configuration section for WCF, defining the endpoint for the data portal—for example:

<system.serviceModel>
    <services>
    <service name="Csla.Server.Hosts.WcfPortal">
      <endpoint address=""
                contract="Csla.Server.Hosts.IWcfPortal"
                binding="wsHttpBinding"/>
    </service>
    </services>
  </system.serviceModel>

WCF services are defined by their address, binding, and contract.

For a WCF service hosted in IIS, the address is defined by IIS and the name of the svc file, so it is not specified in the web.config file. As shown here, you may provide a "" address or no address at all. If you use some other hosting technique, such as WAS or a custom Windows service, you may need to specify the server address.

In the case of a CSLA .NET data portal endpoint, the contract is fixed; it must be Csla.Server.Hosts.IWcfPortal. The contract is defined by the interface implemented in the data portal, as discussed in Chapter 15. It may not be different from this value.

The binding can be any synchronous WCF binding. The only requirement imposed by the data portal is that the WCF binding must be synchronous; beyond that, any binding is acceptable. You may choose to use HTTP, TCP, named pipes, or other bindings. You may choose to configure the binding to use SSL, X.509 certificates, or other forms of encryption or authentication. All the features of WCF are at your disposal.

Remember that the data access code for your business objects will run on the application server. This means that the application server's web.config file must define the connection strings required by your data access code.

<connectionStrings>
  <add name="PTracker" connectionString="your connection string"
    providerName="System.Data.SqlClient" />
  <add name="Security" connectionString="your connection string"
    providerName="System.Data.SqlClient" />
  </connectionStrings>

This is the same configuration you'd put in the client's app.config file when using a local data portal configuration, but now these values must be on the server, because that is where the data access code will execute.

The CslaAuthentication value must be the same on both client and server. You can change it to Windows, as long as you make that change on both sides. If you do change this value to Windows, then you must ensure that the WCF host website is configured to require Windows authentication and to impersonate the calling user, because in that case, CSLA .NET will simply use the value provided by .NET.

At this point, you should understand how to configure the application and how to configure the data portal for either local (2-tier) or remote (3-tier) operation.

PTWpf Project Setup

The UI application can be found within the ProjectTracker solution. The project is named PTWpf and references the ProjectTracker.Library project, along with Csla.dll. ProjectTracker.Library is a project reference, while Csla.dll is a file reference.

When building applications using the CSLA .NET framework, it is best to establish a file reference to the framework assembly, but use project references between the UI and any business assemblies. This makes debugging easier overall, because it helps prevent accidental changes to the CSLA .NET framework project, while enabling fluid changes to both the business objects and UI code.

Let's go through the creation of the WPF UI. First, I'll discuss the layout and design of MainForm, then I'll cover the process of logging a user in and out.

With the common code out of the way, I'll discuss the process of maintaining the roles and project data in detail. At that point, you should have a good understanding of how to create lookup dialogs, as well as both grid-based and detail forms.

The MainForm Window

The MainForm form is the core of the application in that it provides navigation and hosts the user controls for display to the user. It coordinates the flow of the entire application.

As you can see from Figure 19-1 earlier in the chapter, the application has a graphic look and feel. This is implemented in MainForm by using a series of gradient-filled Rectangle and Path objects. All these graphic elements are contained in a Grid named BackgroundGrid; you can look at how this is done in the code download, which you can find either in the Source Code/Download area of the Apress website at www.apress.com/book/view/1430210192 or at www.lhotka.net/cslanet/download.aspx. My focus in this chapter is not on the graphics and layout concepts of WPF, so I'll focus on the navigation, data binding, and interaction with the business objects.

Navigation

The Grid named LayoutRoot contains the actual content of the form. This grid defines two columns: the left one contains the navigation controls, and the right one contains the contentArea control discussed earlier in this chapter.

Each section of the navigation area is contained in an Expander, which contains a list of Hyperlink controls—for example:

<Expander IsExpanded="True" Header="Projects"  >
        <ListBox>
          <ListBoxItem>
            <Hyperlink Name="ShowProjectListButton"
                       Click="ShowProjectList">Show list</Hyperlink>
          </ListBoxItem>
          <ListBoxItem>
            <Hyperlink Name="NewProjectButton"
                       Click="NewProject">Add new</Hyperlink>
          </ListBoxItem>
          <ListBoxItem>
            <Hyperlink Name="CloseProjectButton"
                       Click="CloseProject">Close project</Hyperlink>
          </ListBoxItem>
        </ListBox>
      </Expander>

Each Hyperlink control has a Click event handler, which is implemented in the code behind the form. Each event handler is responsible for implementing the behavior required by the user. For example, the NewProject() event handler looks like this:

private void NewProject(object sender, EventArgs e)
  {
    try
    {
      ProjectEdit frm = new ProjectEdit(Guid.Empty);
      ShowControl(frm);
    }
    catch (System.Security.SecurityException ex)
    {
      MessageBox.Show(ex.ToString());
    }
  }

Creating a new project is a matter of displaying the ProjectEdit form to the user. You do this by creating a new instance of ProjectEdit with an empty Guid value (to indicate a new project should be added) and calling the ShowControl() method to display the form in the content area of MainForm. Any security exceptions are caught and displayed to the user in a dialog. Another example is the CloseProject() handler.

private void CloseProject(object sender, RoutedEventArgs e)
  {
    ProjectSelect frm = new ProjectSelect();
    bool result = (bool)frm.ShowDialog();
    if (result)
    {
      Guid id = frm.ProjectId;
      ProjectCloser.CloseProject(id);
      MessageBox.Show("Project closed",
        "Close project", MessageBoxButton.OK, MessageBoxImage.Information);
    }
  }

This method is a little more complex, because it displays the ProjectSelect dialog to the user to choose which project to close. If the user chooses a project, the ProjectCloser business class (a command object stereotype) closes the specified project. This is an example of a navigation link that executes a behavior without displaying a new edit form to the user.

All the Click handlers for the navigation items work in a similar manner to those shown here. If you prefer, you could route the click events to some class other than the form itself—possibly a controller or presenter class that is responsible for implementing the behaviors triggered by the user clicking each item.

In any case, the code would be similar to what I'm showing here. Regardless of your UI design pattern, you'll need some code that is responsible for implementing the requested UI behavior and does so by leveraging the preexisting functionality in the business objects.

Login and Logout

The final bit of common functionality implemented in MainForm allows the user to log into or out of the application. It is important to realize that the ProjectTracker application allows unauthorized or guest users to view certain data, so those users can interact with the application even if they haven't logged in.

The login process is triggered when the application first loads and when the user clicks the Login button on the menu. In both cases, a LogInOut() method is called to handle the actual login/logout behavior.

void LogInOut(object sender, EventArgs e)
  {
    if (Csla.ApplicationContext.User.Identity.IsAuthenticated)
    {
      ProjectTracker.Library.Security.PTPrincipal.Logout();
      CurrentUser.Text = "Not logged in";
      LoginButtonText.Text = "Log in";
    }
    else
    {
      Login frm = new Login();
      frm.ShowDialog();
      if (frm.Result)
      {
        string username = frm.UsernameTextBox.Text;
        string password = frm.PasswordTextBox.Password;
        ProjectTracker.Library.Security.PTPrincipal.Login(
          username, password);
      }
if (!Csla.ApplicationContext.User.Identity.IsAuthenticated)
      {
        ProjectTracker.Library.Security.PTPrincipal.Logout();
        CurrentUser.Text = "Not logged in";
        LoginButtonText.Text = "Log in";
      }
      else
      {
        CurrentUser.Text =
          string.Format("Logged in as {0}",
          Csla.ApplicationContext.User.Identity.Name);
        LoginButtonText.Text = "Log out";
      }
    }

    ApplyAuthorization();
    IRefresh p = _currentControl as IRefresh;
    if (p != null)
      p.Refresh();
  }

If the current principal is authenticated, the following code will log that user out:

if (Csla.ApplicationContext.User.Identity.IsAuthenticated)
      {
        ProjectTracker.Library.Security.PTPrincipal.Logout();
        CurrentUser.Text = "Not logged in";
        LoginButtonText.Text = "Log in";
      }

On the other hand, if the current principal is not authenticated, then the following code will display the Login dialog to the user so he can enter his username and password:

Login frm = new Login();
        frm.ShowDialog();

The Login dialog form is a modal dialog that prompts the user for his credentials and provides those values to the calling code.

Back in the LogInOut() method, if the user clicks the Login button on the dialog, the username and password values will call PTPrincipal.Login() to validate the credentials.

string username = frm.UsernameTextBox.Text;
          string password = frm.PasswordTextBox.Password;
          ProjectTracker.Library.Security.PTPrincipal.Login(
            username, password);

The result is that Csla.ApplicationContext.User will either be an authenticated PTPrincipal or an UnauthenticatedPrincipal. Then the status of the principal object will be used to determine whether the user is logged in or not.

if (!Csla.ApplicationContext.User.Identity.IsAuthenticated)
        {
          ProjectTracker.Library.Security.PTPrincipal.Logout();
          CurrentUser.Text = "Not logged in";
          LoginButtonText.Text = "Log in";
        }
else
        {
          CurrentUser.Text =
            string.Format("Logged in as {0}",
            Csla.ApplicationContext.User.Identity.Name);
          LoginButtonText.Text = "Log out";
        }

If the user was authenticated, then the button text will be changed to Log out, and the user's name will be displayed on the form. Otherwise, the button text will be changed to Log in, and text indicating that the user isn't logged in will be displayed.

In any case, an ApplyAuthorization() method is called so that MainForm can update its display based on the user's identity (or lack thereof). Then the current user control (if any) is notified that the principal has changed.

ApplyAuthorization();
      IRefresh p = _currentControl as IRefresh;
      if (p != null)
        p.Refresh();

Each user control is responsible for handling this event and responding appropriately. Recall that the EditForm base control defines the ApplyAuthorization() method, so each user control can override that method as necessary.

The ApplyAuthorization() method in MainForm is responsible for enabling and disabling navigation items.

private void ApplyAuthorization()
  {
    this.NewProjectButton.IsEnabled =
      Csla.Security.AuthorizationRules.CanCreateObject(typeof(Project));
    this.CloseProjectButton.IsEnabled =
      Csla.Security.AuthorizationRules.CanEditObject(typeof(Project));
    this.NewResourceButton.IsEnabled =
      Csla.Security.AuthorizationRules.CanCreateObject(typeof(Resource));
  }

Notice how the actual authorization check is delegated to the per-type authorization methods provided by CSLA .NET. These methods, which I discussed in Chapter 12, were implemented specifically to enable scenarios like this. The idea is that MainForm has no idea whether particular users or roles are authorized to add Project objects. Instead, the Project class itself has that knowledge, and MainForm simply asks the authorization subsystem whether the current user is authorized.

The end result is good separation of concerns: MainForm is concerned with the UI details of enabling and disabling controls, while the actual rules are contained within the business layer.

The Login Window

The LogInOut() method in MainForm calls a Login dialog form to collect and authenticate the user's credentials. After gathering credentials from the user, this dialog form calls PTPrincipal.Login() to do the authentication itself.

Figure 19-3 shows the Login form layout.

Layout of the Login form

Figure 19.3. Layout of the Login form

The form defines a Result property, which is a bool that indicates whether the user clicked Login or not.

private bool _result;

  public bool Result
  {
    get { return _result; }
    set { _result = value; }
  }

  void LoginButton(object sender, EventArgs e)
  {
    _result = true;
    this.Close();
  }

  void CancelButton(object sender, EventArgs e)
  {
    _result = false;
    this.Close();
  }

When the user clicks either Login or Cancel, the Result property is set to a corresponding value, and the dialog is closed. Even though the dialog is closed and disappears from the screen, the dialog object still exists in memory, and its properties are available to code in the application. The LogInOut() method relies on the Result property, as well as the values in the UserNameTextBox and PasswordTextBox controls, to do its work.

You've seen how the basic structure of the application works, from the MainForm and Login windows to the EditForm base class. I'll now move on and show how to implement a couple of the actual edit forms and dialogs. I won't cover all the elements in the UI, because they follow some basic themes; once you've seen a couple, you can understand the rest.

The RolesEdit Form

The RolesEdit user control allows an authorized user to edit the roles a resource can hold when assigned to a project. This is one of the simplest types of UIs to create, because the business objects and the controls in the Csla.Wpf namespace already handle most of the work.

Figure 19-4 shows the form with an authenticated user editing data.

Authenticated user using the RolesEdit form

Figure 19.4. Authenticated user using the RolesEdit form

Most of the effort in building this form is in the XAML. I'll walk through it in some detail, since this is the first form I'm discussing.

Form Declaration

To start with, here's the form declaration:

<local:EditForm x:Class="PTWpf.RolesEdit"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="clr-namespace:PTWpf"
  xmlns:csla="clr-namespace:Csla.Wpf;assembly=Csla"
  xmlns:ptracker=
"clr-namespace:ProjectTracker.Library.Admin;assembly=ProjectTracker.Library">

Rather than using a Window or UserControl base class, this uses local:EditForm, which is the EditForm class from earlier in this chapter. Notice that the local: prefix is defined in this header:

xmlns:local="clr-namespace:PTWpf"

You can think of this like a using statement, bringing a namespace into scope for use in this form. That's true for all the xmlns statements, so you can see the Csla.Wpf and ProjectTracker.Library.Admin namespaces are also made available.

Form Resources

The next section of XAML defines some global resources that are available to the entire form.

<local:EditForm.Resources>
  <local:VisibilityConverter x:Key="VisibilityConverter" />
  <csla:IdentityConverter x:Key="IdentityConverter" />
  <csla:CslaDataProvider x:Key="RoleList"
                         ObjectType="{x:Type ptracker:Roles}"
                         FactoryMethod="GetRoles"
                         DataChanged="DataChanged"
                         ManageObjectLifetime="True">
  </csla:CslaDataProvider>
  <csla:ObjectStatus x:Key="RoleListStatus"
                     DataContext="{StaticResource RoleList}" />
  </local:EditForm.Resources>

I discussed the VisibilityConverter class earlier in the chapter; here you see how it is defined as a resource. The IdentityConverter, CslaDataProvider, and ObjectStatus classes were discussed in Chapter 10:

  • IdentityConverter: Works around a data refresh issue with WPF data binding

  • CslaDataProvider: Provides data binding access to the Roles business object

  • ObjectStatus: Elevates the status properties of the business object as dependency properties so they can be bound to UI elements

CslaDataProvider

I want to focus more on the CslaDataProvider, because this is the first time you've seen it in action. Like the WPF ObjectDataProvider, the CslaDataProvider control allows data binding to access an object as a data source. However, the CslaDataProvider goes far beyond the simpler ObjectDataProvider. It can not only create or retrieve a business object, but it can also cancel edits to the object and save the object—all entirely through XAML. Let's go through the previous code.

The ObjectType property specifies the type of business object to be retrieved. The ptracker:Roles type corresponds to the Roles class from the ProjectTracker.Library.Admin namespace.

The FactoryMethod property specifies the name of the static factory method on the Roles class that should be invoked to create or retrieve the object. In this case, the GetRoles() method is invoked.

The DataChanged property specifies an event handler for the DataChanged event. Remember that this form inherits from EditForm, which defines a DataChanged() method to handle this event, so the event is routed to that handler.

Finally, the ManageObjectLifeTime property specifies that the data provider should manage the lifetime of this business object. Setting this to True tells the CslaDataProvider to enable the abilities to cancel edits, add new items to the collection, and remove items from the collection through WPF commanding.

Because IsInitialLoadEnabled is not specified, it defaults to True, which means that the data provider will invoke the factory method and load the business object when the form is loaded initially. This means the form will automatically populate with data as it is loaded, which is what we desire.

Because IsAsynchronous is not specified, it defaults to False, which means that the data provider invokes the factory method on the UI thread. Setting that value to True causes the data to load on a background thread, and the UI displays the values when they have been returned to the UI thread.

ObjectStatus

The ObjectStatus control is bound to the data provider, so its DataContext, or data source, is the business object returned by the provider. This means it exposes properties such as IsSavable and CanEditObject from the business object, so you can use those values to control the state of the UI elements as needed. These properties are used, along with the VisibilityConverter and ListTemplateConverter controls discussed earlier in this chapter, to control how the UI reacts as the user logs in and out of the application.

Setting the DataContext

With the CslaDataSource defined as a resource, it can be used to set the DataContext for part or all of the form. In this case, it is set for all content controls of the form by specifying the DataContext on the Grid control that contains all other controls.

<Grid Name="MainGrid"
            DataContext="{Binding Source={StaticResource RoleList}}">

The DataContext property is set to a binding expression, so the source object becomes the RoleList data provider from the Resources defined earlier. Data binding understands that when a data source is a data provider control, it should really bind to the Data property of that data provider, which will be the Roles business object in this case.

Grid Resources

The Grid also defines its own Resources, including two DataTemplate elements and a ListTemplateConverter. A DataTemplate defines how each row of data in a ListBox (or similar control) is to be displayed. The default template for a ListBoxItem simply displays the ToString() value of the data object, which is rarely the desired result. Defining a DataTemplate allows you to control exactly how each row of data is displayed.

Two templates are defined in order to deal with authorization. If the user is not authorized to edit the values, a read-only template is used. The read-write template is used if the user is allowed to edit the values. This technique means that the form can define a single ListBox control, but the actual display can be easily altered based on the user's permissions by using the ListTemplateConverter.

Read-Only Template

Each DataTemplate defines a self-contained section of UI. For example, the read-only template looks like this:

<DataTemplate x:Key="lbroTemplate">
      <Grid>
        <StackPanel Orientation="Horizontal">
          <TextBlock Text="{Binding Path=Name}" Width="250" />
        </StackPanel>
      </Grid>
      </DataTemplate>

This means that each row of data in the ListBox will be displayed in a Grid control that contains a StackPanel that displays a TextBlock with a fixed width.

Read-Write Template

The read-write template is more complex, containing a couple of editable TextBox controls and a Button so the user can remove the item from the list.

<DataTemplate x:Key="lbTemplate">
      <Grid>
        <StackPanel Orientation="Horizontal">
          <TextBlock>Id:</TextBlock>
          <TextBox x:Name="IdTextBox"
                            Text="{Binding Path=Id,
                                  Converter={StaticResource IdentityConverter}}"
                             Width="100" />
          <csla:PropertyStatus Source="{Binding}"
                               Property="Id" Grid.Column="1"
                               Target="{Binding ElementName=IdTextBox}" />
          <TextBlock>Name:</TextBlock>
          <TextBox x:Name="NameTextBox"
                   Text="{Binding Path=Name,
                                  Converter={StaticResource IdentityConverter}}"
                   Width="250" />
          <csla:PropertyStatus Source="{Binding}"
                               Property="Name" Grid.Column="1"
                               Target="{Binding ElementName=NameTextBox}" />
          <Button
            Command="ApplicationCommands.Delete"
            CommandParameter="{Binding}"
            CommandTarget=
                 "{Binding Source={StaticResource RoleList},
                                   Path=CommandManager,
                                   BindsDirectlyToSource=True}"
            HorizontalAlignment="Left">Remove</Button>
        </StackPanel>
      </Grid>
      </DataTemplate>

There's a lot to this template, so I'll break it down.

Two TextBox controls allow the user to edit the Id and Name properties. Each TextBox has an associated PropertyStatus control, which provides visual cues for the authorization and validation rules associated with those business object properties. Let's look at the TextBox, which is bound to the Name property.

TextBox The TextBox bound to the Name property has an explicit name, because the PropertyStatus control will reference this control by its name. I'll discuss how that works when I cover the PropertyStatus control. Here's the TextBox control declaration:

<TextBox x:Name="NameTextBox"
                     Text="{Binding Path=Name,
                                    Converter={StaticResource IdentityConverter}}"
                     Width="250" />

The Text property is bound to the business object's Name property using a Binding expression. Notice that this expression also uses the IdentityConverter. This is required to avoid the field refresh issue I discussed in Chapter 10, where changes made by the business object to the user-entered value aren't always reflected in the UI. Having a value converter on the binding avoids that issue, and IdentityConverter should be used for any binding where you don't need a more specific value converter.

PropertyStatus The NameTextBox has an associated PropertyStatus control.

<csla:PropertyStatus Source="{Binding}"
                                 Property="Name" Grid.Column="1"
                                 Target="{Binding ElementName=NameTextBox}" />

As discussed in Chapter 10, the PropertyStatus control provides visual cues to the user based on the authorization and validation rules for the business object property. It enables and disables the target control (NameTextBox, in this case) depending on whether the user has read, write, or no access to the property. As shown in Figure 19-5, it displays any broken validation rule messages (for all three severities) for the property. Finally, if any async validation rules are being executed, it will display a busy animation so the user is aware that some background processing related to this property is underway.

The PropertyStatus control displaying a validation error

Figure 19.5. The PropertyStatus control displaying a validation error

Notice how the control is bound to the current DataContext by setting its Source property to {Binding}, and the business object property name is specified through the Property property.

I typically associate a PropertyStatus control with every detail control bound to a business object property to gain the visual cues it provides.

Using ValidatesOnDataErrors You should be aware that WPF itself does provide a more limited way to leverage the validation rules for the business object property. You could choose to declare the TextBox control's binding expression using the ValidatesOnDataErrors property.

Text="{Binding Path=Name,
                                    Converter={StaticResource IdentityConverter},
                                    ValidatesOnDataErrors=True}"

The ValidatesOnDataErrors property specifies that WPF should check the IDataErrorInfo interface of the business object, and if any Error severity rules have been broken, it will alter the display of the TextBox control. By default, it simply adds a thin red border around the control, as shown in Figure 19-6, but you can override that style as you choose.

WPF displaying a validation error with ValidatesOnDataErrors

Figure 19.6. WPF displaying a validation error with ValidatesOnDataErrors

You can use this feature with or without a PropertyStatus control; the feature and the control are totally unrelated and compatible.

Button Control with Commanding The Remove button uses WPF commanding to interact with the CslaDataProvider control. This is controlled through the Command, CommandTarget, and CommandParameter properties.

<Button
            Command="ApplicationCommands.Delete"
            CommandParameter="{Binding}"
            CommandTarget=
                 "{Binding Source={StaticResource RoleList},
                                   Path=CommandManager,
                                   BindsDirectlyToSource=True}"
              HorizontalAlignment="Left">Remove</Button>

These properties work as follows:

  • Command: Specifies the command to be sent to the target when the button is clicked

  • CommandParameter: Uses a binding expression to pass a reference to the current child object (the child object representing this one row of data in the ListBox) as a parameter

  • CommandTarget: Uses a relatively complex binding expression to direct the command to the CommandManager of the CslaDataProvider

As discussed in Chapter 10, the CslaDataProvider control understands several standard commands, including Save, Undo, AddNew, and Delete. Of these, the only one that requires a parameter is Delete, because the CslaDataProvider needs to know which object to remove from the collection.

Also, recall from Chapter 10 that commands can only be handled by visual elements, and that a data provider is not a visual element. To support commanding, the CslaDataProvider exposes a CommandManager property, which is an object that is a visual element and can accept commands. The binding expression sets the target to this CommandManager object.

The BindsDirectlyToSource property must be set to True, because the RoleList resource is a data provider. WPF data binding would normally see that the resource is a data provider and would route all bindings to its Data property. However, in this case, the binding needs to actually go to the data provider itself, not the business object it is providing. The BindsDirectlyToSource property causes data binding to work directly against the resource, not the Data property.

You'll see other examples of Button controls using commanding later in this form.

Form Content

The form's content consists of a TextBlock label, a ListBox with the items to edit, and some button controls.

<StackPanel>
    <TextBlock>Roles:</TextBlock>
    <ListBox Name="RolesListBox"
             ItemsSource="{Binding}"
             ItemTemplate="{Binding Source={StaticResource RoleListStatus},
                            Path=CanEditObject,
                            Converter={StaticResource ListTemplateConverter}}" />
    <StackPanel Orientation="Horizontal"
                Visibility="{Binding Source={StaticResource RoleListStatus},
                               Path=CanEditObject,
                               Converter={StaticResource VisibilityConverter}}">
<Button
            Command="ApplicationCommands.Save"
            CommandTarget="{Binding Source={StaticResource RoleList},
              Path=CommandManager, BindsDirectlyToSource=True}"
            HorizontalAlignment="Left" IsDefault="True">Save</Button>
      <Button
            Command="ApplicationCommands.Undo"
            CommandTarget="{Binding Source={StaticResource RoleList},
              Path=CommandManager, BindsDirectlyToSource=True}"
            HorizontalAlignment="Left" IsCancel="True">Cancel</Button>
      <Button Name="AddItemButton"
            Command="ApplicationCommands.New"
            CommandTarget="{Binding Source={StaticResource RoleList},
              Path=CommandManager, BindsDirectlyToSource=True}"
            HorizontalAlignment="Left" IsCancel="True">Add role</Button>
    </StackPanel>
    </StackPanel>

The core of the UI is the ListBox control, which displays the items from the Roles business object. It does this by specifying an ItemTemplate, which is a DataTemplate that defines how each item in the collection is to be displayed on the form.

As you've seen, two DataTemplate items are defined in the Grid.Resources element earlier in the XAML: one for a read-only display, and one for a read-write display. The ListTemplateConverter switches between those two templates automatically, based on the value of CanEditObject from the ObjectStatus control declared in the form's resources dictionary. That ObjectStatus control exposes the status properties from the business object being used by the form, so the result is that the user is shown the correct template (read-only or read-write) depending on whether she has permission to edit the data.

The various Button controls should also only be visible if the user is allowed to edit the data. Because these controls use WPF commanding to interact with the CslaDataProvider control, they'll automatically enable and disable based on whether the business object allows the user to save, cancel, or add a new item. Take the Save button, for example: the CslaDataProvider control uses the business object's IsSavable property to determine whether the object can be saved, so the button will only be enabled if the object's IsSavable property is true.

However, all the buttons are contained in a StackPanel control, and its Visibility property is bound to the CanEditObject property of the ObjectStatus control defined earlier in the form's resources. By using the VisibilityConverter control, the bool value of CanEditObject is converted to a Visibility value: either Visible or Collapsed.

<StackPanel Orientation="Horizontal"
                  Visibility="{Binding Source={StaticResource RoleListStatus},
                  Path=CanEditObject,
                  Converter={StaticResource VisibilityConverter}}">

The result is that the user will only see the contents of the StackPanel if she's authorized to edit the business object.

Going back to the Button controls, I mentioned that they use WPF commanding to interact with the CslaDataProvider control, just like the Remove button discussed earlier.

<Button
            Command="ApplicationCommands.Save"
            CommandTarget="{Binding Source={StaticResource RoleList},
            Path=CommandManager, BindsDirectlyToSource=True}"
              HorizontalAlignment="Left" IsDefault="True">Save</Button>

In this case, the Save command is sent to the CommandManager of the data provider control, causing any changes to the business object (the Roles collection) to be saved.

At this point, you've seen all the XAML that defines the form. Nearly all the behaviors required for an edit form are handled purely through XAML, with just one bit of code required behind the form.

The ApplyAuthorization Method

The only code required behind the form is an override of the ApplyAuthorization() method. MainForm calls this method when the user logs in or out of the application, so this is the location where any changes must be made based on the new user identity.

When the current .NET principal changes, all authorization rules must be rechecked. The XAML elements handle all the UI changes, but something needs to trigger them to recheck the authorization rules. The simplest way to do this is to rebind them to the business object; that's the purpose of the Rebind() method on the CslaDataProvider control.

protected override void ApplyAuthorization()
  {
    var dp = (Csla.Wpf.CslaDataProvider)this.FindResource("RoleList");
    dp.Rebind();
    if (!Csla.Security.AuthorizationRules.CanEditObject(dp.ObjectType))
      dp.Cancel();
  }

There's one other bit of functionality in this method: a call to the CslaDataProvider control's Cancel() method. The Cancel() method is called if the new user is not authorized to edit the business object.

The reason for doing this is that the user could have been in the middle of editing the data when he logged out. The business object and UI could have a lot of changed data, but now that the user has logged out, those changes can't be saved and shouldn't be displayed. Calling Cancel() causes the object to return to an unchanged state, and data binding updates the UI, ensuring that the user isn't viewing partially edited and effectively invalid data.

This concludes the RolesEdit form. I've covered most of the important concepts required for any WPF data entry form. In the rest of the chapter, I'll discuss a couple of other forms in the application, focusing on slight variations for different UI requirements.

The ResourceList Form

It is common to build forms that display data, perhaps allowing the user to select an item from a list. By using data binding, building such a form is easy. The ResourceList form displays a list of resources from the database, allowing the user to select an item for editing. As with most WPF forms, most of the work is in the XAML.

The form defines a data provider resource.

<csla:CslaDataProvider x:Key="ResourceList"
                           ObjectType="{x:Type PTracker:ResourceList}"
                           FactoryMethod="GetResourceList"
                             IsAsynchronous="True"/>

This is different from the one used in RolesEdit, because IsAsynchronous is True, and IsInitialLoadEnabled isn't set (so it defaults to True). The result is that the data retrieval is started as soon as the form is loaded, but the data retrieval occurs on a background thread. To the user, the form displays almost immediately, and the data appears when it has been retrieved.

In the content of the form, a BusyAnimation control is used to give the user a visual cue that some background task is executing.

<csla:BusyAnimation Height="20" Width="20" Margin="5"
        IsRunning="{Binding Source={StaticResource ResourceList},
                                 Path=IsBusy, BindsDirectlyToSource=True}" />

The IsRunning property is bound to the IsBusy property of the CslaDataProvider control, so, as shown in Figure 19-7, the animation turns on and off based on whether the data provider has an active query.

Busy animation running while data is loading

Figure 19.7. Busy animation running while data is loading

You can see part of the animation to the right of the Resources: label text.

The content of the form is displayed in a ListBox control, which has a simple DataTemplate to display each row. The ListBox control's MouseDoubleClick event is routed to an event handler, which is a method in the code behind the form.

<ListBox Name="listBox1"
                 ItemsSource="{Binding}"
                MouseDoubleClick="ShowResource"/>

Here's the ShowResource() method:

void ShowResource(object sender, EventArgs e)
  {
    ResourceInfo item =
      (ResourceInfo)this.listBox1.SelectedItem;

    if (item != null)
    {
      ResourceEdit frm = new ResourceEdit(item.Id);
      MainForm.ShowControl(frm);
    }
  }

This is the only code behind the form. It retrieves the selected item (if any) from the ListBox control, and it passes the item's Id property to a new instance of the ResourceEdit form. Then it uses the ShowControl() method on MainForm to change the user's display to the new form, allowing the user to view or edit the selected item.

The ProjectList Form

The ProjectList form is nearly identical to ResourceList, with one slight twist. The ProjectList form allows the user to enter some filter criteria into a TextBox control. This value is used as a parameter to the GetProjectList() factory method for the ProjectList business object.

This is worth discussing, because I'm using data binding to connect the TextBox control to the parameter value in the CslaDataProvider control. In other words, no code behind the form is required to accept the user's input and use it as a parameter to the data provider.

Here's the data provider declaration with the parameter default highlighted:

<csla:CslaDataProvider x:Key="ProjectList"
                             ObjectType="{x:Type PTracker:ProjectList}"
                             FactoryMethod="GetProjectList"
                             IsAsynchronous="True"
                             IsInitialLoadEnabled="False">
      <csla:CslaDataProvider.FactoryParameters>
        <system:String>&lt;enter name&gt;</system:String>
      </csla:CslaDataProvider.FactoryParameters>
      </csla:CslaDataProvider>

Figure 19-8 shows the form in the Visual Studio designer.

ProjectList form in the designer

Figure 19.8. ProjectList form in the designer

The IsInitialLoadEnabled property is False, ensuring that the data provider won't try to load data from the database using <enter name> as a filter parameter. This also means that the user must enter some filter value to get any results at all.

The interesting part is the declaration of the TextBox control, because it is the TextBox that binds its input value to the data provider's parameter.

<TextBox Name="NameTextBox" AutoWordSelection="True">
            <TextBox.Text>
            <Binding Source="{StaticResource ProjectList}"
              Path="FactoryParameters[0]"
              BindsDirectlyToSource="true"
              UpdateSourceTrigger="PropertyChanged">
            </Binding>
          </TextBox.Text>

I've highlighted the Binding element, because that's what is unusual in this case. The Text property has a binding that connects it to the ProjectList resource (the data provider), with its Path set to FactoryParameters[0], which is the first parameter in the list of parameters.

As with any binding expression that wants to interact with a data provider control, the BindsDirectlyToSource property is True. The UpdateSourceTrigger is set to PropertyChanged, which means the user doesn't need to tab out of the field for the update to occur. Figure 19-9 shows the result of a filtered query.

ProjectList form displaying filtered results

Figure 19.9. ProjectList form displaying filtered results

When the user double-clicks an item in the list, she's taken to the ProjectEdit form, using code just like that behind the ResourceList form.

The ProjectEdit Form

The final form I'll discuss in this chapter is the ProjectEdit form. This form allows the user to edit the details about a project and the list of resources assigned to the project. This form provides the opportunity to discuss both detail edit forms and master-detail relationships.

Figure 19-10 shows what the form looks like when the user is editing a project.

This form is constructed much like the others you've seen in this chapter. It uses a data provider control and data binding to enable viewing and editing of the data. It uses the ObjectStatus and PropertyStatus controls to enable visual cues for the user, and it uses commanding to implement the buttons.

ProjectEdit form

Figure 19.10. ProjectEdit form

Data Provider Controls and Data Retrieval

However, this form uses two data provider controls: one to populate the ComboBox controls in the resources list, and one to get the Project business object.

<csla:CslaDataProvider x:Key="RoleList"
                         ObjectType="{x:Type PTracker:RoleList}"
                         FactoryMethod="GetList"
                         IsAsynchronous="True" />
<csla:CslaDataProvider x:Key="Project"
                         ObjectType="{x:Type PTracker:Project}"
                         FactoryMethod="GetProject"
                         IsAsynchronous="False"
                         IsInitialLoadEnabled="False"
                         DataChanged="DataChanged"
                         ManageObjectLifetime="True"/>

The way the Project data provider is used is different from what you've seen so far. In this case, the Id value of the business object is passed into the form when it is created. That value is stored in a field and is then used in the form's Loaded event handler to retrieve the object. Here's the code with the relevant parts highlighted:

private Guid _projectId;
    private Csla.Wpf.CslaDataProvider _dp;

    public ProjectEdit()
    {
      InitializeComponent();
      this.Loaded += new RoutedEventHandler(ProjectEdit_Loaded);
      _dp = this.FindResource("Project") as Csla.Wpf.CslaDataProvider;
    }

  public ProjectEdit(Guid id)
      : this()
    {
    _projectId = id;
    }

    void ProjectEdit_Loaded(object sender, RoutedEventArgs e)
    {
    using (_dp.DeferRefresh())
    {
      _dp.FactoryParameters.Clear();
      if (_projectId.Equals(Guid.Empty))
      {
        _dp.FactoryMethod = "NewProject";
      }
      else
      {
        _dp.FactoryMethod = "GetProject";
        _dp.FactoryParameters.Add(_projectId);
      }
    }
      if (_dp.Data != null)
        SetTitle((Project)_dp.Data);
      else
        MainForm.ShowControl(null);
    }

The constructor puts the id parameter into a field, and that field is then used in the Loaded event as a parameter to the data provider control.

The fact that all code interacting with the data provider control is wrapped in a using block with a DeferRefresh() method is important. By default, each time a property of a data provider is changed, the data provider immediately refreshes its data from the database. If you need to set several properties at once, as in this case, you only want the control to go to the database once.

The using block with DeferRefresh() allows you to set many properties of the control, and only at the end of the using block will the control attempt to query the database.

Setting Up Data Binding

As with the other forms you've seen, this one uses data binding by setting a DataContext value for all the content of the form. This is done on the top-level Grid control, which contains all the other content.

<Grid Name="MainGrid"
      DataContext="{Binding Source={StaticResource Project}}"
        Margin="0 0 20 0">

However, this is a master-detail form. While the detail controls are bound to this DataContext (the Project object), items in the list of resources are bound to the ProjectResources collection of the project object.

In other words, the detail controls are bound like this:

<TextBox Name="NameTextBox" Grid.Column="0"
                 Text="{Binding Name,
                                Converter={StaticResource IdentityConverter}}" />

This kind of binding is used on the other forms in this chapter. However, the ListBox control that displays the ProjectResources collection is bound like this:

<ListBox ItemsSource="{Binding Resources}"
                 ItemTemplate="{Binding Source={StaticResource ProjectStatus},
                               Path=CanEditObject,
                               Converter={StaticResource ListTemplateConverter}}"/>

In the Project class, the ProjectResources collection is exposed through a Resources property. Since the Project object has a Resources property, you can use that as the target for a binding expression, making the ListBox control bind to that property.

Each row of data in the ListBox control is one item from the ProjectResources collection: a single ProjectResource object. A DataTemplate is defined in the form to display the values for read-only use, and another is defined for read-write scenarios. This is no different from the previous forms in this chapter.

The only interesting thing in the DataTemplates is the use of a ComboBox control. Remember that this form has two data provider controls, and one returns the list of roles in the RoleList business object. This is used to populate the ComboBox control.

<ComboBox
            ItemsSource="{Binding Source={StaticResource RoleList}}"
            DisplayMemberPath="Value"
            SelectedValuePath="Key"
            SelectedValue="{Binding Path=Role}"
            Width="150" />

As with ComboBox controls in Windows Forms or Web Forms, the control is really bound to two data sources at once. One data source is used to populate the items in the list by setting the ItemsSource, DisplayMemberPath, and SelectedValuePath properties. The control is also bound to a single property on the current DataContext by setting the SelectedValue property—in this case, to the Role property for the current ProjectResource business object.

Navigation Through Commanding

Each item in the resources list can be clicked, allowing the user to navigate directly to the ResourceEdit form for that resource. You could do this navigation with a simple event handler, like I did in the ResourceList and ProjectList forms. In this case, however, I'm using commanding to illustrate a different technique you may choose.

The FullName property from the business object is bound to a Button control that is styled to appear like a hyperlink so the user knows to click on it. When the user clicks the control, it sends an Open command with the ResourceId property as a parameter.

<Button Style="{StaticResource LinkButton}"
                  Margin="0" Width="200"
                  Command="ApplicationCommands.Open"
                  CommandParameter="{Binding Path=ResourceId}"
                  Content="{Binding Path=FullName}" Foreground="Blue" />

The ProjectEdit form itself handles the command, because a CommandBinding has been defined on the form for this command.

<UserControl.CommandBindings>
  <CommandBinding Command="ApplicationCommands.Open"
                  Executed="OpenCmdExecuted"
                  CanExecute="OpenCmdCanExecute" />
  </UserControl.CommandBindings>

A CommandBinding allows you to express, through XAML, that a command should be routed to a specific method. You also need to provide a CanExecute method that returns a Boolean through its CanExecuteRoutedEventArgs parameter, indicating whether the command can be executed at any point in time. If this returns false, the associated Button control is disabled automatically.

In the code behind the form, the two methods are implemented.

private void OpenCmdExecuted(object sender, ExecutedRoutedEventArgs e)
  {
    if (e.Parameter != null)
    {
      ResourceEdit frm = new ResourceEdit(Convert.ToInt32(e.Parameter));
      MainForm.ShowControl(frm);
    }
  }

  private void OpenCmdCanExecute(
    object sender, CanExecuteRoutedEventArgs e)
    { e.CanExecute = true; }

The CommandParameter value is available in the OpenCmdExecuted() method through e.Parameter. In the XAML, the ResourceId value was passed as a parameter, so that value is available in the method. That value is used to create a new instance of the ResourceEdit form, which is then displayed to the user by calling the ShowControl() method.

This approach doesn't save any code over handling a Click event directly, but it can provide more flexibility. A full discussion of WPF commanding is outside the scope of this book, but you should know that commands can be routed to different locations, making them more flexible than tightly coupled event handlers.

Using a combination of data binding and the controls in the Csla.Wpf namespace, you can implement in XAML almost all common behaviors required for building a data display or entry form. In many cases, very little code is required behind any form, which improves maintainability of the application.

Conclusion

This chapter has walked through the process of creating a basic WPF UI using the business objects from Chapters 17 and 18. Obviously, there are many ways to create a UI using WPF, so the goal of this chapter was to highlight how you can use data binding to easily create forms to view and edit object data.

The key point to take from this chapter is that when you create your business layer using business objects, the UI developer doesn't need to worry about validation or authorization rules, data access, or most other complex issues. The UI developer can focus on user interaction, the look and feel of the application, and so forth. The result is a high degree of separation between the UI layer and the business layer.

At the same time, because the objects use the data portal mechanism to retrieve and update data, the application is able to exploit the capabilities of mobile objects: running business logic on both the client workstation and an application server as appropriate. Better still, you can simply change the application configuration file to switch between various physical n-tier configurations to meet different performance, scalability, reliability, and security requirements.

Chapter 20 will cover the implementation of a Web Forms UI based on the same set of business objects. Although there are obvious differences between the WPF and Web Forms environments, we'll achieve total reuse of the business logic and data access code when moving from one UI type to the next.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset