input argument values and asserting that the corresponding output argument values are as expected.
hostinG the workflow desiGner
One of the benefits in having a declarative configurable workflow is that it can be reconfigured at
will to support changing business requirements without the application needing to be recompiled.
This means (in theory) that an end user given the right tools (that is, the WF designer) should be
able to modify the workflow without requiring a developer to be involved (creating custom activities
is a different story, however). Of course it’s probably asking too much to have a casual end user use
the WF designer and modify a workflow without training — it really is a tool designed to be used
by developers. That said, with a little training, IT-savvy users (such as business analysts and so on)
could successfully take on this task.
If this is the case, it is quite easy to host the WF designer in your own application and expose it to
the end user for modification. The WF designer is a WPF component that you can host in your own
WPF applications, making it available to the users to modify a workflow as required. You can also
host the WF designer in Windows Forms using the WPF interoperability described in Chapter 18.
This chapter, however, will focus on hosting it natively in a WPF application.
The coverage of this topic assumes you have some experience working with WPF
and XAML. See Chapter 18 for more information on these topics.
720 .
chaPter 32 WindoWS WorkFloW FoundATion (WF)
Create a new WPF project, called WFDesignerHost. Add the following assembly references to the project:
.
System.Activities.dll
.
System.Activities.Core.Presentation.dll
.
System.Activities.Presentation.dll
You will also need to add a reference to any assemblies that contain custom activities that you want
to be used in the workflows through your application.
The designer has three main (separate) components: the Toolbox, the Properties window, and the
designer surface. Let’s create a user interface that instantiates and displays the three of these.
Open up the MainWindow.xaml file and set the name of the Grid control to WFLayoutGrid. Also
add three columns to this Grid (you will no doubt want to define some appropriate widths for these
columns at a later point in time). Host the Toolbox in the first column, the designer surface in the
second, and the Properties window in the third. The Toolbox can be created either declaratively in
XAML or in code, but the designer surface and Properties window can only be created in code. For
the purpose of this example, you’ll create all three of these controls in code.
Open up the code behind the MainWindow.xaml file. Import the following namespaces:
Vb
Imports System.Activities
Imports System.Activities.Core.Presentation
Imports System.Activities.Presentation
Imports System.Activities.Presentation.Toolbox
Imports System.Activities.Statements
Imports System.Linq
Imports System.Reflection
Imports System.Windows
Imports System.Windows.Controls
c#
using System;
using System.Activities;
using System.Activities.Core.Presentation;
using System.Activities.Presentation;
using System.Activities.Presentation.Toolbox;
using System.Activities.Statements;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
First, you need to register the designer metadata:
Vb
Private Sub RegisterMetadata()
Dim metaData As New DesignerMetadata()
metaData.Register()
End Sub
Hosting the Workflow Designer .
721
c#
private void RegisterMetadata()
{
DesignerMetadata metaData = new DesignerMetadata();
metaData.Register();
}
Now add the Toolbox to the page. You will find that the Toolbox is not automatically populated
with activities — instead you need to populate it yourself with the activities you want to make
available to the user. The following code handles this by creating an instance of the Toolbox and
adding all the activities in the same assembly as the Sequence activity to it.
Vb
Private Sub AddToolboxControl(ByVal parent As Grid, ByVal row As Integer,
ByVal column As Integer)
Dim toolbox As New ToolboxControl()
Dim category As New ToolboxCategory("Activities")
toolbox.Categories.Add(category)
Dim query = From type In Assembly.GetAssembly(GetType(Sequence)).GetTypes()
Where type.IsPublic AndAlso
Not type.IsNested AndAlso
Not type.IsAbstract AndAlso
Not type.ContainsGenericParameters AndAlso
(GetType(Activity).IsAssignableFrom(type) OrElse
GetType(IActivityTemplateFactory).IsAssignableFrom(type))
Order By type.Name
Select New ToolboxItemWrapper(type)
query.ToList().ForEach(Function(item)
category.Add(item)
Return Nothing
End Function)
Grid.SetRow(toolbox, row)
Grid.SetColumn(toolbox, column)
parent.Children.Add(toolbox)
End Sub
c#
private void AddToolboxControl(Grid parent, int row, int column)
{
ToolboxControl toolbox = new ToolboxControl();
ToolboxCategory category = new ToolboxCategory("Activities");
toolbox.Categories.Add(category);
var query = from type in Assembly.GetAssembly(typeof(Sequence)).GetTypes()
where type.IsPublic &&
722 .
chaPter 32 WindoWS WorkFloW FoundATion (WF)
!type.IsNested &&
!type.IsAbstract &&
!type.ContainsGenericParameters &&
(typeof(Activity).IsAssignableFrom(type) ||
typeof(IActivityTemplateFactory).IsAssignableFrom(type))
orderby type.Name
select new ToolboxItemWrapper(type);
query.ToList().ForEach(item => category.Add(item));
Grid.SetRow(toolbox, row);
Grid.SetColumn(toolbox, column);
parent.Children.Add(toolbox);
}
Now you add the designer and the Properties window (both are controls returned from instantiating
the WorkflowDesigner class):
Vb
Private Sub AddDesigner(ByVal parent As Grid,
ByVal designerRow As Integer,
ByVal designerColumn As Integer,
ByVal propertiesRow As Integer,
ByVal propertiesColumn As Integer)
Dim designer As New WorkflowDesigner()
designer.Load(New Sequence())
Grid.SetRow(designer.View, designerRow)
Grid.SetColumn(designer.View, designerColumn)
parent.Children.Add(designer.View)
Grid.SetRow(designer.PropertyInspectorView, propertiesRow)
Grid.SetColumn(designer.PropertyInspectorView, propertiesColumn)
parent.Children.Add(designer.PropertyInspectorView)
End Sub
c#
private void AddDesigner(Grid parent, int designerRow, int designerColumn,
int propertiesRow, int propertiesColumn)
{
WorkflowDesigner designer = new WorkflowDesigner();
designer.Load(new Sequence());
Grid.SetRow(designer.View, designerRow);
Grid.SetColumn(designer.View, designerColumn);
parent.Children.Add(designer.View);
Grid.SetRow(designer.PropertyInspectorView, propertiesRow);
Grid.SetColumn(designer.PropertyInspectorView, propertiesColumn);
parent.Children.Add(designer.PropertyInspectorView);
}
Now call these three functions from the window’s New method/constructor, like so:
summary .
723
Vb
Public Sub New()
InitializeComponent()
RegisterMetadata()
AddToolboxControl(WFLayoutGrid, 0, 0)
AddDesigner(WFLayoutGrid, 0, 1, 0, 2)
End Sub
c#
public MainWindow()
{
InitializeComponent();
RegisterMetadata();
AddToolboxControl(WFLayoutGrid, 0, 0);
AddDesigner(WFLayoutGrid, 0, 1, 0, 2);
}
Now you can run the project and test it. Your final user interface should look something like
Figure 32-14 (which can, of course, be improved upon by spending some time styling the page).
fiGure 32-14
suMMary
In this chapter, you learned that Windows Workflow is a means of defining a business process,
which is especially useful to use when you have a business process that changes frequently or is
a long running process. You also learned how to create and run a basic workflow, and how to
host the workflow designer in your own application. Windows Workflow is quickly becoming the
standard for implementing workflows on the Microsoft platform, enabling you to reuse the skills
you have gained here to also build workflows in the various products that support it.
33
Client application services
what’s in this chaPter?
.
Accessing client application services
.
Managing application roles
.
Persisting user settings
.
Specifying a custom login dialog
A generation of applications built around services and the separation of user experience
from backend data stores has seen requirements for occasionally connected applications
emerge. Occasionally connected applications are those that continue to operate regardless of
network availability. In Chapter 34 you will learn how data can be synchronized to a local
store to allow the user to continue to work when the application is offline. However, this
scenario leads to discussions (often heated) about security. Because security (that is, user
authentication and role authorization) is often managed centrally, it is difficult to extend so
that it incorporates occasionally connected applications.
In this chapter you become familiar with the client application services that extend ASP.NET
Application Services for use in client applications. ASP.NET Application Services is a provider-
based model for performing user authentication, role authorization, and profile management
that has in the past been limited to web services and web sites. In Visual Studio 2010, you can
configure your rich client application, either Windows Forms or WPF, to make use of these
services throughout your application to validate users, limit functionality based on what roles
users have been assigned, and save personal settings to a central location.
client serVices
Over the course of this chapter you are introduced to the different application services via a simple
WPF application. In this case it is an application called ClientServices, which you can create by
selecting the (C# or VB) WPF Application template from the File . New . Project menu item.
726 .
chaPter 33 clienT ApplicATion SerViceS
To begin using the client application services, you need to enable the checkbox on the Services tab
of the project properties designer, as shown in Figure 33-1. The default authentication mode is to
use Windows authentication. This is ideal if you are building your application to work within the
confines of a single organization and you can assume that everyone has domain credentials. Selecting
this option ensures that those domain credentials are used to access the roles and settings services.
Alternatively, you can elect to use Forms authentication, in which case you have full control over the
mechanism that is used to authenticate users. We return to this topic later in the chapter.
fiGure 33-1
You can also add the client application services to existing applications via the
Visual Studio 2010 project properties designer in the same way as for a new
application.
You will notice that when you enabled the client application services, an app.config file was added
to your application if one did not already exist. Of particular interest is the < system.web > section,
which should look similar to the following snippet:
< system.web >
< membership defaultProvider="ClientAuthenticationMembershipProvider" >
< providers >
< add name="ClientAuthenticationMembershipProvider" type=
"System.Web.ClientServices.Providers.ClientWindowsAuthentication
Client services .
727
MembershipProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35" serviceUri="" connectionStringName="Default
Connection" credentialsProvider=""/>
</providers>
</membership>
<roleManager defaultProvider="ClientRoleProvider" enabled="true">
<providers>
<add name="ClientRoleProvider"
type="System.Web.ClientServices.Providers.ClientRoleProvider, System.Web.Ext
ensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
serviceUri="" cacheTimeout="86400" connectionStringName="DefaultConnection"/>
</providers>
</roleManager>
</system.web>
Code snippet app.config
Here you can see that providers have been defined for membership and role management.
You can extend the client application services framework by building your own providers
that can talk directly to a database or to some other remote credential store such as Active
Directory. Essentially, all the project properties designer does is modify the app.config file
to use the providers that ship with the .NET Framework and define associated properties. To
implement your own providers, you need to create concrete classes that implement the abstract
methods defined in the System.Web.Security.RoleProvider, System.Web.Security
.MembershipProvider, or System.Configuration.SettingsProvider classes (depending on
which provider you are implementing).
After you define the default role and membership providers, you use the client application services
to validate the application user. To do this, you need to invoke the ValidateUser method on the
System.Web.Security.Membership class, as shown in the following snippet:
c#
using System.Web.Security;
public partial class MainWindow : Window{
public MainWindow(){
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e){
if (Membership.ValidateUser(null, null)){
MessageBox.Show("User is valid");
}
else{
MessageBox.Show("Unable to verify user, application exiting");
this.Close();
return;
}
}
}