Professional Documents
Culture Documents
NET
Version 3.0
Rockford Lhotka
Editor:
Teresa Lhotka
Technical reviewers:
Brant Estes
Joe Fallon
The information in this book is distributed on an as is basis, without warranty. Although every
precaution has been taken in the preparation of this work, the author shall not have any liability
to any person or entity with respect to any loss or damage caused or alleged to be caused directly
or indirectly by the information contained in this work.
The source code for this book (CSLA .NET 3.0.2) is available at http://www.lhotka.net/cslanet.
Acknowledgements
Neither this book, nor CSLA .NET version 3.0,
would have been possible without
out support from
Magenic. Magenic is the premier .NET
development company in the US, and is a
Microsoft Gold Certified Partner.
You can reach Magenic at
http://www.magenic.com.
Contents
Introduction...........................................................................................................................................................7
Before Reading this Book..........................................................................................................................................7
Organization of the Book..........................................................................................................................................7
Breaking Changes from CSLA .NET Version 2.1.........................................................................................................7
Known Issues with CSLA .NET Version 3.0.................................................................................................................8
Summary of Changes and Enhancements.................................................................................................................9
Technical Requirements..........................................................................................................................................10
Building CSLA .NET for .NET 2.0 .........................................................................................................................10
Building CSLA .NET for .NET 3.0 .........................................................................................................................11
Chapter 1: Windows Communication Foundation................................................................................................ 12
WCF Data Portal Channel .......................................................................................................................................12
Setting up a WCF Application Server .................................................................................................................13
Adding an Empty Web Site............................................................................................................................13
Referencing Assemblies ................................................................................................................................14
Creating the svc File ......................................................................................................................................14
Configuring WCF............................................................................................................................................14
Configuring a WCF Client Application ................................................................................................................16
Using a Custom Endpoint Name ........................................................................................................................17
Supporting DataContract and DataMember ..........................................................................................................18
When to use DataContract.................................................................................................................................19
Serialization in CSLA .NET...................................................................................................................................19
Configuring CSLA .NET to use NetDataContractSerializer .............................................................................19
Creating a WCF Service using Business Objects ......................................................................................................20
Creating the WCF Service...................................................................................................................................21
Service Contract ............................................................................................................................................21
Data Contract ................................................................................................................................................22
Service Definition File....................................................................................................................................24
Service Implementation ................................................................................................................................24
Configuring the Service .................................................................................................................................26
Consuming the WCF Service ..............................................................................................................................27
Custom Authentication...........................................................................................................................................29
Acquiring an X.509 Certificate............................................................................................................................29
Creating a Test Certificate..................................................................................................................................30
Creating the Certificate .................................................................................................................................30
Granting Access to the Certificate to IIS........................................................................................................31
Trusting the Certificate..................................................................................................................................32
Configuring WCF to use Message Security.........................................................................................................33
Configuring the Service .................................................................................................................................33
Configuring the Client....................................................................................................................................34
Configuring a Service to use Username Credentials ..........................................................................................35
Using CSLA .NET 3.0
Copyright 2007 Rockford Lhotka
Page i
Modifying PTPrincipal....................................................................................................................................37
Custom UserNamePasswordValidator ..........................................................................................................39
Custom Authorization Policy .........................................................................................................................40
Server Configuration .....................................................................................................................................42
Providing a Username from the Client...............................................................................................................43
Client Implementation ..................................................................................................................................43
Client Configuration ......................................................................................................................................44
Chapter 2: Windows Presentation Foundation .................................................................................................... 46
Custom Authentication in WPF ...............................................................................................................................47
Managing the Principal Object...........................................................................................................................47
Storing the Principal Reference.....................................................................................................................47
Using the DataPortalInitInvoke Event ...........................................................................................................48
CslaDataProvider Control........................................................................................................................................49
Basic Use ............................................................................................................................................................51
Define the Csla.Wpf Namespace...................................................................................................................51
Define Your Business Namespace .................................................................................................................51
Define CslaDataProvider as a Resource ........................................................................................................51
Using the Data Provider Resource.................................................................................................................52
Passing Parameters to the Factory Method.......................................................................................................53
Setting Parameters in XAML..........................................................................................................................53
Setting Parameters Through Data Binding....................................................................................................54
Setting Parameters in the Code Behind ........................................................................................................55
Managing the Object Lifetime............................................................................................................................58
Enabling Lifetime Management....................................................................................................................59
Declaring a Save Button ................................................................................................................................59
Declaring a Cancel Button .............................................................................................................................60
Declaring an Add New Button .......................................................................................................................61
Handling Exceptions...........................................................................................................................................62
Binding a ComboBox to a Name/Value List .......................................................................................................62
Validator Control.....................................................................................................................................................64
Using the Validator Control................................................................................................................................65
Using a Custom Style for Controls in Error.........................................................................................................66
Authorizer Control...................................................................................................................................................69
Using the Authorizer Control .............................................................................................................................70
Forcing a Manual Refesh ...............................................................................................................................70
Read Authorization........................................................................................................................................71
Write Authorization ...........................................................................................................................................72
ObjectStatus Control...............................................................................................................................................72
Using the ObjectStatus Control..........................................................................................................................73
IdentityConverter Control .......................................................................................................................................75
Using the IdentityConverter Control..................................................................................................................76
Chapter 3: Workflow ........................................................................................................................................... 77
Building Activities using Objects .............................................................................................................................79
Implementing a Code Activity............................................................................................................................80
State and Performance..................................................................................................................................81
Implementing an External Activity .....................................................................................................................83
Implementing a SequentialActivity ...............................................................................................................84
Using CSLA .NET 3.0
Copyright 2007 Rockford Lhotka
Page ii
Page iii
Page iv
List of Tables
Table 1. Known issues with version 3.0
Table 2. Functional enhancements in version 3.0
Table 3. Properties defined in a svc file.
Table 4. Makecert.exe arguments.
Table 5. Certmgr.exe arguments.
Table 6. Properties of the serviceCertificate element.
Table 7. CslaDataSource control properties
Table 8. Options for the NotVisibleMode property.
Table 9. Status properties from BusinessBase<T>.
Table 10. Versioning issues when reloading a workflow.
Table 11. Methods provided by WorkflowManager.
Table 12. WorkflowManager Status property values.
Table 13. Summary of new validation features and enhancements.
Table 14. NullResultOptions values.
Table 15. Parameters for the rule methods in CommonRules.
Table 16. Properties of the RuleDescription object.
Table 17. Conditions when UndoException is thrown.
8
9
24
31
32
34
50
71
73
89
92
93
101
102
109
110
113
List of Figures
Figure 1. Setting the NET20 compiler symbol.
Figure 2. Adding a WCF Service web site.
Figure 3. Browser display when navigating to a WCF data portal host.
Figure 4. WCF services as an interface to your business objects.
Figure 5. Add Service Reference dialog.
Figure 6. Installing the certificate into the Other People store.
Figure 7. Authentication and authorization during WCF initialization.
Figure 8. Default display of a Windows Forms ErrorProvider control.
Figure 9. Default display of a CSLA .NET Validator control in WPF.
Figure 10. Validation error with custom styling.
Figure 11. Validation error style similar to Windows Forms ErrorProvider.
Figure 12. ObjectStatus data bound to CheckBox controls.
Figure 13. High level architecture using objects and workflows.
Figure 14. A workflow activity as a UI relative to the business layer.
Figure 15. Sequential workflow to close a project.
Figure 16. Revised workflow to close a project.
Figure 17. Using a code activity within a SequentialActivity.
Using CSLA .NET 3.0
Copyright 2007 Rockford Lhotka
10
14
16
20
28
33
36
64
65
68
69
74
78
79
80
82
84
Page v
85
86
87
87
88
97
114
Page vi
Introduction
Welcome to Using CSLA .NET 3.0. This book will show you how to use the new features,
enhancements and changes made to the CSLA .NET framework from version 2.1 to 3.0.
Page 7
Class
CslaDataSource
Summary
You cannot add a CslaDataSource to a page by choosing to add a
new data source from within the GridView or DetailsView controls.
Attempting to do this will result in an exception that prevents the
control from displaying properly. I have been unable to resolve this
issue, but there is a viable workaround.
You must manually add a CslaDataSource control to your page, either
using drag-and-drop from the Toolbox, or by typing the tag into the
page. At this point you can configure the assembly/type information
in the data source control. You can then choose this data source
control as the data source for your GridView or DetailsView control.
ReadOnlyListBase
There is a known issue where the Visual Studio debugger will lock
up (actually it will eventually crash due to a stack overflow) when
an exception is thrown in the DataPortal_Fetch() of a subclass of
ReadOnlyListBase.
The issue appears to be due to a bug in the Visual Studio debuggers
exception assistant.
The workaround is to click Tools|Options, then
Debugging|General and uncheck the Unwind the call
stack on unhandled exceptions box (You may need to
check the Show all settings box at the bottom of the dialog to
see this option).
Page 8
Enhancement
WCF
Summary
Add support for WCF as a data portal channel that is
interchangeable with the previous channels.
Provide global support for use of the DataContract and
DataMember attributes in CSLA .NET business objects.
WPF
Validation
Authorization
Data binding
Interfaces
Miscellaneous
Page 9
Technical Requirements
CSLA .NET 3.0 is designed to run on either Microsoft .NET 2.0 or Microsoft .NET 3.0. Obviously,
if you are using only the Microsoft .NET 2.0 runtime, then you cannot use any features specific to
Microsoft .NET 3.0, but the bulk of CSLA .NET 3.0s features remain at your disposal.
Note: In the process of writing this book some changes were made to the framework.
The exact version of the framework that corresponds to this book is version 3.0.2,
which you can download from
http://www.lhotka.net/cslanet/download.aspx.
PresentationCore
PresentationFramework
Page 10
System.Printing
System.ServiceModel
System.Windows.Forms
System.Workflow.Runtime
WindowsBase
You can safely remove these references from the project, thus eliminating the compiler warnings
youll otherwise receive (to notify you that these assemblies could not be located).
Page 11
Chapter 1:
Windows Communication Foundation
One of the pillars of Microsoft .NET 3.0 is the Windows Communication Foundation (WCF). WCF
is essentially the replacement for web services, Web Service Extensions (WSE) 3.0, .NET Remoting,
Enterprise Services and Microsoft Message Queue (MSMQ). It provides a common API for creating
service-oriented, client/server, n-tier or message-based applications.
In this chapter I assume you have a basic familiarity with WCF, including how to create and
consume WCF services in your applications.
WCF impacts CSLA .NET in three ways:
1. WCF is a new way for the data portal to communicate between client and server.
2. WCF includes a replacement for the Serializable attribute: DataContract, and this
directly impacts the way CSLA .NET can serialize objects.
3. WCF allows you to create a WCF service, similar to a Web service, that is implemented
using business objects behind the scenes.
If you have pre-existing CSLA .NET applications, you can use the new WCF data portal channel
by making some configuration changes to your application. No coding changes are required.
You may also choose to start using the new DataContract and DataMember attributes instead of
the Serializable attribute in your business classes.
WCF is also the next generation technology for creating web services, replacing the older asmx
and WSE (Web service extensions) technologies. You can create WCF services that accept and
return standard XML messages, but which use CSLA .NET style business objects to implement the
business logic within the service itself.
Lets look at each of these concepts.
Page 12
The details of exploiting all the power of WCF is outside the scope of this book. However, it is
important to realize that the WCF data portal channel is specifically designed to allow you to use
most WCF features by setting up the appropriate configuration on both the client and server.
Page 13
Referencing Assemblies
The simplest way to get Csla.dll , along with your business assembly, into the Bin directory of the
web site is to add a reference to your business assembly (or project if it is in the same solution).
Alternately, you can manually copy Csla.dll and your business assembly into the Bin directory of
the site on the web server.
The service name as specified in this file must correspond to the name of the service you define in
your web.config file. When a client request comes into your service, this name is used to locate the
configuration for the service in web.config.
Configuring WCF
Finally, open web.config in the editor. You need to add the following configuration section to the
file:
Page 14
<?xml version="1.0"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.serviceModel>
<services>
<service name="Csla.Server.Hosts.WcfPortal">
<endpoint contract="Csla.Server.Hosts.IWcfPortal" binding="wsHttpBinding"/>
</service>
</services>
</system.serviceModel>
This <system.serviceModel> element configures the WCF service that corresponds to the svc file
you just created. Notice that the name property exactly matches the Service name from the svc file.
WCF services are configured based on their address, binding and contract.
In this case, the address is mostly defined by IIS. This is because the address flows from your web
server name, the virtual root name and the name of the svc file (WcfHost). The result is the following
address:
http://serverName/virtualRoot/WcfHost.svc
The http:// part of the address actually corresponds to the binding. The binding specifies how
the client is allowed to communicate with the server. The most common binding is wsHttpBinding,
which uses the standard HTTP protocol. When hosting within IIS, you must use an HTTP binding.
Note: If you create your own host in a Windows service, or use WAS, you can use
other bindings that use protocols such as TCP, named pipes or others.
Finally, theres the contract, which is specified using the contract property. The contract is
defined by CSLA .NET, and must be Csla.Server.Hosts.IWcfPortal.
Notice that all configuration options for the address, binding and channel are controlled through
the standard <system.serviceModel> element used by all WCF services. This means that you have
complete flexibility in defining how a client is allowed to communicate with your data portal service
(as long as the binding is synchronous).
In most cases youll also need to include the standard CSLA .NET
setting:
appSettings authentication
<appSettings>
<add key="CslaAuthentication" value="Csla"/>
</appSettings>
Youll probably also need to set your connectionStrings, so your business objects can interact
with the database while running on your application server.
At this point, your WCF application server should be ready to use.
You can do a basic functionality test by navigating to your services address using a browser such
as Internet Explorer. You should see a result similar to Figure 3.
Page 15
This specifies that the data portal should use a WcfProxy object to communicate with the server.
The WcfProxy object relies on the configuration of WCF options to find the server, so you also need
to add a standard <system.serviceModel> element to your clients config file:
Page 16
<system.serviceModel>
<client>
<endpoint name="WcfDataPortal"
address="http://localhost:2961/WcfHost/WcfPortal.svc"
binding="wsHttpBinding"
contract="Csla.Server.Hosts.IWcfPortal" />
</client>
</system.serviceModel>
The name property is used by WcfProxy to locate the correct endpoint in the configuration, and this
value should usually not be changed. If you need to change it, Ill discuss using a custom endpoint
name later in this chapter.
The only value that you will normally need to change from this example is the address property.
The address value must correspond to the actual address of the service on your application server.
The binding property must correspond to the binding you configured on your application server.
In most cases wsHttpBinding is a good option. However, any synchronous binding can be used. The
important thing is that the binding on the client and server must match.
The contract property cannot be changed. The contract name is specified by WcfProxy in CSLA
.NET itself. Changing this value will prevent the data portal channel from functioning, and youll get
an exception at runtime.
You must then use this proxy class instead of the standard WcfProxy class in your configuration
of the data portal:
<add key="CslaDataPortalProxy"
value="MyApplication.CustomWcfProxy, MyApplication"/>
The data portal will then use your custom subclass of WcfProxy. This changes the endpoint name,
allowing you to change the endpoint name in your <system.serviceModel> element:
Page 17
<system.serviceModel>
<client>
<endpoint name="CustomEndpointName"
address="http://localhost:2961/WcfHost/WcfPortal.svc"
binding="wsHttpBinding"
contract="Csla.Server.Hosts.IWcfPortal" />
</client>
</system.serviceModel>
This is an advanced scenario and is not the sort of thing most people will need to worry about.
At this point, you should understand how to use the WCF data portal channel by configuring an
application server with WCF, and setting the appropriate values in the client applications
app.config or web.config file.
by
providing full fidelity for even complex .NET data types. Any Serializable or DataContract type
(or object graph of Serializable or DataContract types) can be serialized using this component.
Ill focus on the use of the NDCS in the rest of this section, because CSLA .NET uses the
only NDCS provides comparable functionality in WCF.
BinaryFormatter and
The DataContract attribute is somewhat like the Serializable attribute, in that it marks an object
as being eligible for serialization by one of the new serializers.
The Serializable attribute uses an opt-out model, where all fields are serialized unless you
explicitly mark the field with the NonSerialized attribute. DataContract, on the other hand, uses an
opt-in model; where fields (or properties) are only serialized if you explicitly mark them with the
DataMember attribute.
The BinaryFormatter, of course, only understands the Serializable attribute. If you only mark a
class with DataContract, the BinaryFormatter will throw an exception because it views the object as
not being serializable.
The NDCS understands both the DataContract and Serializable attributes. This means that you
can use NDCS to serialize objects that are Serializable, objects that are a DataContract and even
object graphs composed of both types of objects at once.
This makes sense, because all the core .NET types to date are Serializable, not DataContract.
They cant be changed without massive repercussions throughout the existing .NET framework itself.
Any serializer intended to replace the BinaryFormatter must do what the BinaryFormatter does, and
then do more.
Page 18
Page 19
This will cause both the clone and n-level undo implementations in CSLA .NET to use NDCS.
The result is that you can use the DataContract attribute instead of, or in combination with, the
Serializable attribute in your business classes.
Again, please remember that only the WCF data portal channel uses the NDCS, so if you use the
your business objects you cannot use any of the older data portal channels.
DataContract attribute in
Page 20
The binding specifies how the client communicates with the service. The binding defines the
protocol (such as HTTP, TCP sockets, named pipes and so forth). It also defines whether the data
should be encrypted during transfer and many other possible settings.
The contract is a two part concept. Theres the service contract that defines the methods exposed
by the service to its clients. There are also data contracts that define the shape of any data passed
into or out of those methods.
Service Contract
The first thing to do when creating a WCF service, is to create the service contract. A service
contract is just a normal .NET interface with some extra attributes on the interface and the methods.
The App_Code directory contains IPTService.cs:
[ServiceContract]
public interface IPTService
{
[OperationContract]
ProjectData[] GetProjectList();
}
The ServiceContract attribute specifies that this interface defines a service contract for a WCF
service.
Using CSLA .NET 3.0
Copyright 2007 Rockford Lhotka
Page 21
The OperationContract attribute specifies that the method is part of the public service interface.
It is important to realize that you can choose which methods are part of the service contract, and
which are just part of the .NET interface by applying OperationContract to some methods and not
others. You can use this technique to define service implementations that provide more functionality
to other code in the project than is provided to clients of the service.
Note: If you dont want to put your code in the App_Code directory of your service,
you can create a separate Class Library project to contain your service contract, data
contract and service implementation.
In this example, the GetProjectList() method is part of the service contract, and so the method can
be invoked by clients of the service.
Data Contract
Notice that the GetProjectList() method returns an array of type ProjectData. Any data passed
into or returned from a service method should be defined by a data contract.
A data contract is just a simple .NET class with public read-write properties. The DataContract
and DataMember attributes are used to define the shape of the public contract for use by WCF.
ProjectData is defined in the App_Code directory:
[DataContract]
public class ProjectData
{
private Guid _id;
private string _name;
private string _started;
private string _ended;
private string _description;
private List<ProjectResourceData>
_resources = new List<ProjectResourceData>();
[DataMember]
public Guid Id
{
get { return _id; }
set { _id = value; }
}
[DataMember]
public string Name
{
get { return _name; }
set { _name = value; }
}
[DataMember]
public string Started
{
get { return _started; }
set { _started = value; }
}
[DataMember]
public string Ended
{
get { return _ended; }
set { _ended = value; }
}
[DataMember]
Using CSLA .NET 3.0
Copyright 2007 Rockford Lhotka
Page 22
The DataContract attribute specifies that this class defines a data contract.
The DataMember attribute is applied to a property or field, and specifies that the property or field is
part of the contract. Youll typically apply DataMember to properties rather than fields, because the
serialized XML stream includes the name of the element, and property names (such as Name) are
typically more appropriate than field names (such as _name).
Notice that the AddResource() method doesnt have a DataMember attribute. The DataMember
attribute is only valid for properties and fields, and cant be applied to methods. Any methods
included in a data contract class are only available to code in the service, not to code in the client of
the service.
The ProjectResourceData class is another data contract, also in the App_Code directory. I wont
list it here, because it is just a simple class, also using the DataContract and DataMember attributes.
The ProjectData class does provide an interesting implementation of the ProjectResources
property:
[DataMember]
public ProjectResourceData[] ProjectResources
{
get
{
if (_resources.Count > 0)
return _resources.ToArray();
return null;
}
set
{
_resources = new List<ProjectResourceData>(value);
}
}
Page 23
This illustrates how you can use encapsulation to provide a friendly internal implementation,
while offering a simpler and more standard external interface.
Combined, the service contract and data contracts define how clients will see and interact with
your service.
Hosting in IIS means that http:// is the only possible protocol, and the web server defines the
myserver name. The myroot name is defined by the name of the virtual root on the server, so only the
name PTService.svc is under the control of the service itself.
The svc file contains just one line to define the service:
<%@ ServiceHost Language=C# Debug="true"
Service="PTService" CodeBehind="~/App_Code/PTService.cs" %>
Property
Language
Description
Defines the programming language used in the code behind file that
implements the service.
Debug
Service
CodeBehind
Service Implementation
The service implementation is in PTService.cs in the App_Code directory. The implementation class
is a normal .NET class that implements the interface defined as the service contract:
public class PTService : IPTService
{
// ...
}
Page 24
By implementing the IPTService interface, the class is required to implement the methods
defined by the interface. You can use implicit or explicit implementations for these methods. WCF
will invoke the methods through the interface.
These methods are implemented almost exactly the same as the methods youd implement for an
service. Heres the GetProjectList() method:
asmx Web
The highlighted lines of code show the implementation of the services behavior. Notice how this
code essentially acts as a bridge between the data contract objects exposed to the client of the service,
and the business objects used within the service.
I recommend that you never directly expose your business objects as the data contract, as that
would break the encapsulation provided by the service. Your business objects are your internal
implementation and should not be exposed to outside callers.
Similarly, I dont recommend having your business objects make direct use of the data contract
objects. The data contract objects are part of your services public contract, and they will be changed
based on how you version your service, not on how you might change or evolve your business
objects themselves.
The service implementation provides a clear buffer, an abstraction layer, between the external
service contract and the internal implementation.
Notice that the GetProjectList() method doesnt require any user credentials. Because all users
are allowed to view the list of projects, the current principal is simply set to an unauthenticated
custom principal in the UseAnonymous() method:
public static void UseAnonymous()
{
ProjectTracker.Library.Security.PTPrincipal.Logout();
}
If your method needs an authenticated principal object, youll need to use one of the many
techniques provided by WCF to get the users credentials from the client to your service. You can
Using CSLA .NET 3.0
Copyright 2007 Rockford Lhotka
Page 25
then use those credentials to set the current principal appropriately. This is not easy, and is discussed
later in this chapter in the section on custom authentication.
Theres a lot going on here, but the two highlighted lines of code are the most important. The first
defines the service itself, and the second defines the HTTP endpoint used by clients when calling the
service.
The configuration also includes a second <endpoint> element:
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
This endpoint can be used by Visual Studio or other tools to access your services metadata.
During development of a client application, youll want to expose this endpoint so the client
developer has access to the metadata for your service.
The use of the term mex refers to metadata exchange, which is the WSDL (web service definition
language) standard used to exchange metadata about a service.
Also, during development you may want access to more detailed server-side exception details.
This is the purpose of the behaviorConfiguration property of the <service> element:
<service behaviorConfiguration="PTServiceBehavior" name="PTService">
Page 26
The serviceMetadata tag indicates that clients can retrieve metadata from the service through an
HTTP GET.
The serviceDebug tag indicates that service faults returned to the client should include exception
details from the server. In production, doing this can be a major security issue, because you could
expose sensitive implementation details to unknown clients. But during development, this is a
powerful tool for debugging.
In production, you may choose to use a simpler configuration, such as:
<system.serviceModel>
<services>
<service name="PTService">
<endpoint address="" binding="wsHttpBinding" contract="IPTService" />
</service>
</services>
</system.serviceModel>
This simpler configuration is all that is technically needed to make your service available to
clients; assuming the clients dont need to access your services metadata to determine your service
contract, and assuming you dont want to return detailed server-side exception information to your
callers.
The WCF service should now be complete. It has an address, binding and contract. Behind the svc
file is an implementation that implements the service contract, consuming and returning data defined
by the data contract.
Page 27
Notice that the result is an array of type PTWcfService.ProjectData. This is not the same type as
on the server. It is important to remember that Visual Studio created a set of proxy types that have
the same shape as the data contract defined by your service, but they are not the same types. In other
Using CSLA .NET 3.0
Copyright 2007 Rockford Lhotka
Page 28
words, this ProjectData type doesnt use a List<T> internally like the version on the server. It
merely stores any child data in an array, because thats what the WSDL defined.
Finally, the service proxy is closed:
svc.Close();
Even though service proxies do implement IDisposable, they should be closed, not disposed,
when you are done using them. If you do dispose a proxy object, you will get runtime exceptions
from WCF.
At this point, you should understand how to use WCF with CSLA .NET through the WCF data
portal channel, the optional use of the DataContract and DataMember attributes on your business
classes and how to create a WCF service that is implemented using business objects behind the
service.
Custom Authentication
If you are using custom authentication in your business objects, youll typically want the client to
pass a username and password to the service. The service can then authenticate those credentials and
set up your custom principal object for use by the server code.
Authenticating the credentials and setting up the principal object is easily accomplished using a
CSLA .NET style custom principal object. For example, in ProjectTracker the PTPrincipal class
has a Login() method to do this:
PTPrincipal.Login(username, password);
Unfortunately, getting the username and password from a client into a WCF service is very
challenging. Remember that this is a function of security, and so a lot of infrastructure and
configuration work is required to get all the security pieces set up before you can safely pass a
username and password across the network.
WCF is designed to be secure, and so it wont allow you to pass a username and password without
first having a secure and encrypted binding. To do that, you first need a server certificate, either for
SSL or WCFs built-in message level security. Creating, installing and configuring a test certificate is
challenging. After that, some of the WCF configuration and code to use the certificate is also a bit
complex.
There are three processes involved:
1. Acquire or create an X.509 certificate.
2. Configure WCF to use message level security.
3. Configure WCF to use username credentials.
Lets walk through each of these steps.
Page 29
In a production Internet environment, these certificates are typically issued by a commercial firm
like VeriSign or RapidSSL. These commercial certificates are cryptographically linked to a trusted
root certificate, and all the major vendors have their root certificates installed automatically on
Windows, so all Windows clients automatically trust the certificates they sell to their customers.
In a production intranet environment, the certificates might come from your organization itself.
Many organizations have their own internal certificate authority and their own internally trusted root
certificate. Certificates issued by the organization are only trusted by computers within the
organization (because they install the organizations trusted root certificate), not by all computers in
the world.
In a development or test environment, the certificates often are created by the developer. A
developer might use a tool like makecert.exe that comes with the Microsoft .NET SDK. These
certificates arent linked to any trusted root certificate, and so no one trusts them unless the certificate
is explicitly trusted by the user on a specific machine.
Page 30
Argument
-n CN=localhost
Meaning
Defines the name of the certificate. If possible this should be the
network domain name of your server, but you can provide a name
unique to your service if desired.
localhost.cer
-sky exchange
-ss My
-sr LocalMachine
The result is the full path to the certificate file corresponding to CN=localhost in the
LocalHost/My certificate store.
Using CSLA .NET 3.0
Copyright 2007 Rockford Lhotka
Page 31
You can then use the cacls.exe tool to grant access to the IIS account. For example, youd enter
the following command (on one line):
cacls.exe "C:\Documents and Settings\All Users \Application
Data\Microsoft\Crypto\RSA\MachineKeys\8aeda5eb81555f14f8f9960745b5a40d_38f7de48-5ee9-452d-8a5a92789d7110b1" /E /G "NETWORK_SERVICE":R
In this example, you can see both the full path to the certificate file, as well as the account that is
granted access (NETWORK_SERVICE). This is because I am working on Windows Server 2003, and it
would be the same on Windows Vista. On Windows XP, however, youd grant access to the ASPNET
account.
The result is that your service, when hosted in IIS, will have access to the certificates private key,
so it can use that information to sign and encrypt messages.
Argument
-add
Meaning
Specifies that the certificate should be added to a certificate store.
-c
-n localhost
-r LocalMachine
-s My
-r CurrentUser
-s TrustedPeople
Page 32
Manual Installation
Another way you can install the certificate is by double-clicking on the .cer file. You need to use
this technique to install the certificate onto other client machines. This will bring up a dialog
allowing you to view details about the certificate.
Click the Install Certificate button to bring up the Certificate Import Wizard. Figure 6
shows the key panel of the wizard, where you must specify to install the certificate into the Other
People store, which corresponds to CurrentUser/TrustedPeople.
Page 33
behavior. As with everything in WCF, you can configure the behavior through code, but it is more
commonly done through the configuration file.
The PTWcfService projects configuration already uses the
changed that to include the certificate configuration as well:
PTServiceBehavior,
so Ive just
<behaviors>
<serviceBehaviors>
<behavior name="PTServiceBehavior">
<serviceCredentials>
<serviceCertificate
findValue="localhost"
storeLocation="LocalMachine"
storeName="My"
x509FindType="FindBySubjectName" />
</serviceCredentials>
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
The serviceCertificate element indicates how to find the certificate to be used to secure the
communication channel. Table 6 lists the properties of this element.
Property
findValue
Description
The name of the certificate to find. This name must correspond to
the name used earlier to name the certificate (CN=localhost).
storeLocation
storeName
X509FindType
Page 34
<client>
<endpoint
behaviorConfiguration="ServiceCertificate"
address="http://localhost/PTWcfServicecs/PTService.svc"
binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IPTService"
contract="PTWcfService.IPTService" name="WSHttpBinding_IPTService">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
</client>
Page 35
Unfortunately, the strong separation of these two concepts in WCF complicates matters a little.
After the credential validation step, WCF clears the principal object. Later in the process, the
principal can be set for the service, but at that point in time the credentials are no longer available.
This means that the credential validation step must somehow cache the results of the Login()
method in memory so that the principal object can be made current later in the process as shown in
Figure 7. This is the purpose behind the PrincipalCache object in Csla.Security.
Page 36
This would override the default, expanding the cache to keep the most recent 20 principal objects
in memory.
Without the PrincipalCache object, the authorization step would need to go back to the security
database to reload the principal object. This results in two hits to the security database for every
service request.
Even with the PrincipalCache object, a second hit to the security database may be required. If the
cache is configured to be too small, theres a chance that the principal object could be gone from the
cache in the fraction of a second it takes to get from authentication to authorization. To prevent this
unlikely occurrence from causing a failure, your authorization code should have the option of
reloading the principal from the database if needed, and thats the approach Ill illustrate in this
chapter.
Modifying PTPrincipal
Before I get into the WCF authorization and authentication code, it is necessary to modify
PTPrincipal and PTIdentity to accommodate the separation between authentication and
authorization.
LoadPrincipal Method
WCF validates the users credentials early in the process. Later in the process, WCF creates an
authorization policy object that defines the principal and identity objects to be used as the service is
executed. This authorization policy object is only provided with the username value, not the
password, and so PTPrincipal needs a way to load the principal and identity objects based purely on
the username.
This requirement isnt unique to WCF. When creating a totally stateless web application, the
custom principal cant be held in Session on the web server. Instead, it must be reloaded on each
page request, based purely on the username. While the password isnt maintained by ASP.NET forms
authentication, the username is provided on every page request. This LoadPrincipal() method can
also be used to reload the principal from the database on every page request.
The LoadPrincipal() method is very similar to the Login() method, except that it only requires a
username, not a password. In fact, Ive altered the Login() method to share some code:
public static bool Login(string username, string password)
{
return SetPrincipal(PTIdentity.GetIdentity(username, password));
}
public static void LoadPrincipal(string username)
{
SetPrincipal(PTIdentity.GetIdentity(username));
}
private static bool SetPrincipal(PTIdentity identity)
{
if (identity.IsAuthenticated)
{
PTPrincipal principal = new PTPrincipal(identity);
Csla.ApplicationContext.User = principal;
}
return identity.IsAuthenticated;
}
Page 37
The highlighted line of code invokes a new overload of the GetIdentity() factory method on
PTIdentity. This overload uses a different criteria object, that contains only the username, to call an
overload of DataPortal_Fetch() in PTIdentity:
private void DataPortal_Fetch(LoadOnlyCriteria criteria)
{
using (SqlConnection cn =
new SqlConnection(Database.SecurityConnection))
{
cn.Open();
using (SqlCommand cm = cn.CreateCommand())
{
cm.CommandText = "GetUser";
cm.CommandType = CommandType.StoredProcedure;
cm.Parameters.AddWithValue("@user", criteria.Username);
using (SqlDataReader dr = cm.ExecuteReader())
{
Fetch(dr);
}
}
}
}
Because the loading of the objects fields is the same for both the login and load process, Ive
consolidated that code into a Fetch() method that is called by both overloads of
DataPortal_Fetch():
private void Fetch(SqlDataReader dr)
{
if (dr.Read())
{
_name = dr.GetString(0);
_isAuthenticated = true;
if (dr.NextResult())
{
while (dr.Read())
{
_roles.Add(dr.GetString(0));
}
}
}
else
{
_name = string.Empty;
_isAuthenticated = false;
_roles.Clear();
}
}
The original login process is effectively unchanged. But this new load process calls a different
stored procedure that doesnt require or verify the password:
Page 38
The end result is that the UI can either call Login(username, password) or
and the result is the same. Of course, only the Login() method actually
validates the users credentials. Ill use the new LoadPrincipal() method in the implementation of
the custom WCF authorization behavior.
LoadPrincipal(username)
Custom UserNamePasswordValidator
In WCF, custom username authorization is handled by subclassing UserNamePasswordValidator
from the System.IdentityModel.Selectors namespace. The implementation of this class is not
complex. If the users credentials are valid, it returns without an exception. If the credentials are
invalid, the method must throw an exception. Only users with valid username/password
combinations get past this point in the process.
It is important to realize that you cannot set the current principal at this point. WCF makes no
guarantee that this code will run on the same thread as the actual service instance, and it explicitly
resets the principal object at a point after this method completes. However, using the approach shown
in Figure 7, it is possible to cache the principal in memory for use later by using the PrincipalCache
object.
You can find the CredentialValidator class in the PTWcfServiceAuth project in
ProjectTracker:
public class CredentialValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (userName != "anonymous")
{
PTPrincipal.Logout();
if (!PTPrincipal.Login(userName, password))
throw new FaultException("Unknown username or password");
// add current principal to rolling cache
Csla.Security.PrincipalCache.AddPrincipal(Csla.ApplicationContext.User);
}
}
}
Notice the call to the Logout() method. This is required because the data portal must have a valid,
even if unauthenticated, principal to work. Without the call to Logout() , the Login() call would
throw a data portal exception due to having an invalid principal on the thread.
If the Login() call succeeds, the resulting principal (which is temporarily the current principal
until this method completes) is stored in the PrincipalCache object. This makes the principal object
available for use later in the service initialization process.
Using CSLA .NET 3.0
Copyright 2007 Rockford Lhotka
Page 39
Finally, notice that special consideration is made for the username anonymous . Remember that
ProjectTracker allows unauthenticated users to access some information and so the application must
allow anonymous users to at least make some service calls. This special username will get extra
attention in the custom authorization policy class later.
Ill discuss how to configure WCF to use the CredentialValidator class later in the chapter. For
now, it is enough to know that this code will be invoked early in the process as WCF initializes itself
for every service call.
Page 40
identities[0] = principal.Identity;
return true;
}
}
The Evaluate() method is called to evaluate the current context and set the principal and identity.
The highlighted lines of code show how the principal is retrieved from the PrincipalCache object
based on the username value available from the identity object retrieved from the
context.Properties dictionary:
string username = identities[0].Name;
IPrincipal principal = Csla.Security.PrincipalCache.GetPrincipal(username);
Csla.ApplicationContext.User
Both the principal and identity must also be made available to WCF by setting values in
context.Properties.
Page 41
This object is then used as the primary identity for the ServiceSecurityContext provided by
WCF.
With these two classes defined in the
use them.
PTWcfServiceAuth
PTWcfService to
Server Configuration
WCF allows the use of custom username validation and authorization policy objects through
configuration. Before changing web.config however, it is important to reference the
PTWcfServiceAuth project so that assembly is available to the service.
The highlighted lines of code show the changes to web.config necessary to use these new classes:
<services>
<service behaviorConfiguration="PTServiceBehavior" name="PTService">
<endpoint address=""
binding="wsHttpBinding" bindingConfiguration="UserNameWS"
contract="IPTService" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
<bindings>
<wsHttpBinding>
<binding name="UserNameWS">
<security mode="Message">
<message clientCredentialType="UserName" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="PTServiceBehavior">
<serviceAuthorization principalPermissionMode="Custom">
<authorizationPolicies>
<add policyType="PTWcfServiceAuth.PrincipalPolicy, PTWcfServiceAuth"/>
</authorizationPolicies>
</serviceAuthorization>
<serviceCredentials>
<serviceCertificate
findValue="localhost"
storeLocation="LocalMachine"
storeName="My"
x509FindType="FindBySubjectName" />
<userNameAuthentication
userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType=
"PTWcfServiceAuth.CredentialValidator, PTWcfServiceAuth" />
</serviceCredentials>
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
Page 42
<bindings>
<wsHttpBinding>
<binding name="UserNameWS">
<security mode="Message">
<message clientCredentialType="UserName" />
</security>
</binding>
</wsHttpBinding>
</bindings>
It explicitly sets the security mode to Message, which is the default. More importantly, it specifies
that the clientCredentialType is UserName, so the service will require that callers provide a
username and password when calling the service.
Also, notice that the PTServiceBehavior behavior
serviceAuthorization element has been added:
<serviceAuthorization principalPermissionMode="Custom">
<authorizationPolicies>
<add policyType="PTWcfServiceAuth.PrincipalPolicy, PTWcfServiceAuth"/>
</authorizationPolicies>
</serviceAuthorization>
This specifies that the principalPermissionMode is Custom, and that means that a custom
authorization policy must be defined. It is set to use a policyType that references the
PrincipalPolicy class discussed earlier in the chapter.
The behaviors serviceCredentials element has also been enhanced to define the
customUserNamePasswordValidatorType that refers to the CredentialValidator class discussed
earlier in the chapter:
<userNameAuthentication
userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType=
"PTWcfServiceAuth.CredentialValidator, PTWcfServiceAuth" />
With these changes, WCF will now invoke the CredentialValidator to authenticate the callers
username and password. Assuming that method doesnt throw an exception, WCF will invoke the
PrincipalPolicy to set up the authorization policy, later in the process. This will includ the custom
principal and identity to be used by the service.
Client Implementation
In the clients code, setting the username and password values is relatively straightforward:
Page 43
Because the service supports the special anonymous username, this will also work (for methods
that dont require special roles):
PTWcfService.ProjectData[] list = null;
PTWcfService.PTServiceClient svc = new PTWcfClient.PTWcfService.PTServiceClient();
try
{
svc.ClientCredentials.UserName.UserName = "anonymous";
list = svc.GetProjectList();
}
finally
{
svc.Close();
}
this.projectDataBindingSource.DataSource = list;
You might also use a channel factory to create the service proxy. In that case, you set the
credentials on the factory:
PTWcfService.ProjectData[] list = null;
ChannelFactory<PTWcfService.IPTService> factory =
new ChannelFactory<PTWcfService.IPTService>("WSHttpBinding_IPTService");
try
{
factory.Credentials.UserName.UserName = "pm";
factory.Credentials.UserName.Password = "pm";
PTWcfService.IPTService proxy = factory.CreateChannel();
using (proxy as IDisposable)
{
list = proxy.GetProjectList();
}
}
finally
{
factory.Close();
}
this.projectDataBindingSource.DataSource = list;
In this case, you can dispose the proxy, but you should call Close() on the factory object.
Disposing the factory may result in an exception from WCF.
Either way, the supplied values are passed securely to the service where they are authenticated
and then used to load the custom principal and identity objects.
Client Configuration
There is one change required to the callers WCF configuration, and it is highlighted here:
Page 44
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_IPTService" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
bypassProxyOnLocal="false" transactionFlow="false"
hostNameComparisonMode="StrongWildcard"
maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
allowCookies="false">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true" inactivityTimeout="00:10:00"
enabled="false" />
<security mode="Message">
<transpor t clientCredentialType="Windows" proxyCredentialType="None"
realm="" />
<message clientCredentialType="UserName" negotiateServiceCredential="true"
algorithmSuite="Default" establishSecurityContext="true" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="ServiceCertificate">
<clientCredentials>
<serviceCertificate>
<authentication certificateValidationMode="PeerTrust"/>
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint
behaviorConfiguration="ServiceCertificate"
address="http://localhost/PTWcfServicecs/PTService.svc"
binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IPTService"
contract="PTWcfService.IPTService" name="WSHttpBinding_IPTService">
</endpoint>
</client>
If you hand-craft your client configuration, much of what you see here is optional. However, if
you allow Visual Studio to create the configuration for you, youll see something similar to this. The
highlighted lines of code merely specify that, at the message level, the service expects a UserName
type credential:
<message clientCredentialType="UserName" negotiateServiceCredential="true"
algorithmSuite="Default" establishSecurityContext="true" />
With this change, the client will now properly pass the credentials through to the service.
At this point, you should understand how to use WCF with CSLA .NET. You can use WCF
through the data portal as a replacement for previous network transports. You can configure CSLA
.NET to use WCF serialization with the NetDataContractSerializer. You can also create and
consume WCF services using CSLA .NET objects.
Page 45
Chapter 2:
Windows Presentation Foundation
Perhaps the part of Microsoft .NET 3.0 that will have the biggest long-term impact, is the Windows
Presentation Foundation (WPF). WPF is the next generation UI technology for the development of
Windows applications.
Note: In its Silverlight incarnation, WPF is also the next generation technology for
the development of Web applications.
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 very similar to both Windows Forms and
Web Forms development, and yet it is fundamentally different from both of those technologies. This
leads to a learning curve that must be overcome before you can be productive in the new
environment.
Like Windows Forms, WPF provides powerful data binding support. But it is important to
remember that WPF is a version 1.0 technology, and so there are features that a Windows Forms
developer would expect that simply dont exist (yet) in WPF.
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 now includes a
CslaDataProvider control for WPF.
A Windows Forms developer might expect to find an ErrorProvider control, but WPF does not
yet have such a control. CSLA .NET provides a Validator control to provide similar functionality.
CSLA .NET also provides an Authorizer control, which is roughly equivalent to the
ReadWriteAuthorization control CSLA .NET includes for Windows Forms development.
Like Windows Forms, WPF has an issue where data changed in a propertys set block wont be
immediately shown in the UI. CSLA .NET includes an IdentityConverter control that can be used
to address this issue.
Note: You may see some differences between the code in this chapter and the code in
the PTWpf project from the ProjectTracker download. The differences are because
PTWpf relies on a custom UI base class that Ill discuss in Chapter 7.
Before getting into the details of each control, it is important to discuss one higher-level issue
regarding custom authentication.
Page 46
Page 47
As the main form is loaded, a Logout() method is called. Then the current principal object is
stored in the static field.
This static reference must be updated if the current principal changes. In your UI code, after
youve logged a user in or out, refresh the field. For example:
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";
}
}
_principal = (ProjectTracker.Library.Security.PTPrincipal)
Csla.ApplicationContext.User;
// ...
}
This way the static field can always be used, on any thread, to get a reference to the current
principal.
Page 48
public MainForm()
{
InitializeComponent();
_mainForm = this;
this.Loaded += new RoutedEventHandler(MainForm_Loaded);
Csla.DataPortal.DataPortalInitInvoke +=
new Action<object>(DataPortal_DataPortalInitInvoke);
}
Then, in the event handler itself, all you need to do is make sure the current principal for the
thread matches the current principal from the static field:
void DataPortal_DataPortalInitInvoke(object obj)
{
if (!ReferenceEquals(Csla.ApplicationContext.User, _principal))
Csla.ApplicationContext.User = _principal;
}
When the application first starts up and WPF initializes the threads, it will use this code to set the
principal for those threads. Once the application is running, WPF tends to reuse the same threads and
so this code basically does nothing at that point.
You should now understand that WPF provides a slightly more complex environment, in some
ways, than Windows Forms or Web Forms. To use custom authentication, you need to implement
some code to deal with WPFs use of multiple threads.
CslaDataProvider Control
WPF has a data control concept similar to the data source control concept from ASP.NET 2.0. In
WPF these data controls are called data providers, and they allow declarative data access from your
XAML code, or your code-behind.
Data provider controls are powerful, because they abstract the concept of data access within a
WPF form, and they can support additional behaviors such as providing asynchronous access to data.
As with ASP.NET, WPF provides an ObjectDataProvider control that might, at first glance,
appear to be a good way to work with CSLA .NET style business objects. Unfortunately, the
ObjectDataProvider has some of the same limitations as the ASP.NET ObjectDataSource control:
It requires a public constructor
It has no way to call a static (or any other type of) factory method
CSLA .NET style business objects have non-public constructors and factory methods are used to
create or retrieve the object.
Additionally, CSLA .NET objects intrinsically support n-level undo and persistence, and the
has no knowledge of those capabilities either.
ObjectDataProvider
Whats needed is a data provider control that understands how to call static factory methods and
how to manage the objects lifetime: interacting with n-level undo and CSLA .NET style object
persistence.
The CslaDataProvider is a WPF data provider control that understands how to interact with
CSLA .NET business objects. This control cannot only create or retrieve a business object, but it can
Page 49
manage the objects entire lifetime through to saving (inserting, updating or deleting) the object into
the database, or cancelling any changes made to the object by the user.
Like many other data provider controls, CslaDataProvider supports asynchronous loading of the
object, which can be a very powerful feature in some cases.
Table 7 lists the properties of the CslaDataProvider control available for use in your XAML
code.
Property
x:Key
Description
A standard WPF property that defines the name of this instance of
CslaDataProvider .
ObjectType
FactoryMethod
ManageLifetime
True for
IsAsynchronous
IsInitialLoadEnabled
FactoryParameters
Page 50
Basic Use
The basic use of the CslaDataProvider control is relatively straightforward:
1. Define the Csla.Wpf namespace in XAML
2. Define your business library namespace in XAML
3. Define CslaDataProvider as a resource
4. Use the resource as a data source
You can now use the csla: prefix to use the controls from the Csla.Wpf namespace in your form.
You can now use the lib: prefix to access the types from your business library.
Page 51
<Page x:Class="PTWpf.ResourceList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:csla="clr-namespace:Csla.Wpf;assembly=Csla"
xmlns:lib="clr-namespace:ProjectTracker.Library;assembly=ProjectTracker.Library"
Title="Resource List">
<Page.Resources>
<csla:CslaDataProvider x:Key="ResourceList"
ObjectType="{x:Type lib:ResourceList}"
FactoryMethod="GetResourceList">
</csla:CslaDataProvider>
</Page.Resources>
Notice how the <csla:CslaDataProvider> tag is contained within a <Page.Resources> tag. You
can define CslaDataProvider as a resource anywhere you would use any other type of data provider
as a resource. However, data providers are typically declared at the top level of a form as shown here.
Note: Instead of Page, you can use Window or UserControl as well.
Remember that the x:Key, ObjectType and FactoryMethod properties are the minimum required
properties that must be set for the CslaDataProvider control to return a valid business object.
At this point, you have defined a data resource that you can use as a data source in your form.
</Grid>
ResourceList,
matches the
x:Key
Note: You may define many data provider resources for a form, as long as each one
has a unique x:Key so it can be referenced later in the XAML.
At this point, the Grid control will bind to the business object returned by the CslaDataProvider .
Thanks to the way WPF data binding works, all controls contained within the Grid will also default
to binding to this same business object.
Page 52
In this case, Im referencing the System namespace from the .NET mscorlib.dll assembly, and
so all the basic .NET types like String, Object and Int32 are now available for use in the XAML.
Note: While C# has language-specific keywords for most primitive .NET types, you
must use the .NET type name from the System namespace when using these types in
XAML.
At this point, you can specify factory parameter values directly in the XAML:
<Page x:Class="PTWpf.ProjectList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:csla="clr-namespace:Csla.Wpf;assembly=Csla"
xmlns:lib="clr-namespace:ProjectTracker.Library;assembly=ProjectTracker.Library"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Title="Project List">
<Page.Resources>
<csla:CslaDataProvider x:Key="ProjectList"
ObjectType="{x:Type lib:ProjectList}"
FactoryMethod="GetProjectList">
<csla:CslaDataProvider.FactoryParameters>
<system:String>Expert</system:String>
</csla:CslaDataProvider.FactoryParameters>
</csla:CslaDataProvider>
</Page.Resources>
The highlighted lines of XAML illustrate how to pass a single String parameter value to the
factory method. The value of the parameter is Expert, and that value will be
GetProjectList()
Page 53
passed as a parameter when GetProjectList() is called. In the case of the ProjectList business
object, this means the list will contain all projects with the word Expert in the project name.
Notice the use of the system: prefix when defining the <system:String> XAML element. You
can specify any type in the System namespace, as long as that type has a public constructor that can
accept the value provided in the element. This means you can specify most common types, including
numeric values, Boolean values, text values, GUID values and so forth.
The advantage of this approach is that it is simple, but obviously very limited since the parameter
value must be coded directly into the XAML.
Page 54
</StackPanel>
</Grid>
</Page>
Notice how the factory parameter is set to a temporary value. This value will be displayed to the
user, and must be non-empty. The reason it cant be empty is that the System.String type has no
public default constructor, which is exactly what WPF tries to invoke if the parameter value is empty.
However, the IsInitialLoadEnabled property of the CslaDataProvider control is set to False.
This prevents the control from attempting to create a business object when the control is first
initialized, so the temporary value in the factory parameter is essentially ignored. No business object
will actually be created until some property of the data provider control is changed, which will
trigger the control to refresh itself and to actually create a business object.
Later in the XAML, notice the highlighted TextBox control. This control is using data binding to
bind its Text property to the first factory parameter value of the ProjectList data provider control:
<TextBox.Text>
<Binding Source="{StaticResource ProjectList}"
Path="FactoryParameters[0]"
BindsDirectlyToSource="true"
UpdateSourceTrigger="PropertyChanged">
</Binding>
</TextBox.Text>
The Source property of the binding is set to the ProjectList resource: the data provider control
itself. The Path property of the binding is set to the first element of the FactoryParameters
collection.
The BindsDirectToSource property is required, and tells data binding to bind to the controls
properties rather than to the properties of the data returned by the control.
Finally, the UpdateSourceTrigger property indicates that the binding should be refreshed when
the property value is changed. Essentially, this means that the refresh will occur when the user tabs
off the TextBox control.
Using this technique, the value entered by the user is placed into the factory parameter by data
binding, which triggers a refresh of the data provider. The data provider then executes the factory
method, passing in the new parameter value and returning the resulting business object as the new
data source.
Page 55
<StackPanel FlowDirection="LeftToRight">
<StackPanel.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="3,5"/>
</Style>
<Style TargetType="{x:Type ListBox}">
<Setter Property="Margin" Value="3,5"/>
</Style>
</StackPanel.Resources>
<TextBlock>Projects:</TextBlock>
<ListBox Name="listBox1"
ItemsSource="{Binding}"
MouseDoubleClick="ShowProject"/>
</StackPanel>
</Grid>
</Page>
Notice that there is no data provider control, but the XAML is set up to use data binding. Behind
this form you can write code like this:
public partial class ProjectList : EditForm
{
public ProjectList()
{
InitializeComponent();
ProjectList list = ProjectList.GetList();
this.mainGrid.DataContext = list;
}
}
The highlighted lines of code show how to create a business object and set it as the data source for
the Grid control (and all controls it contains). The result of this code is basically the same as the
previous example where the business object was created by the CslaDataProvider control.
Since the result is the same, you might wonder why you would ever programmatically set the
factory parameters, or any other properties, on a data provider control.
I think the primary motivation to use the CslaDataProvider control in code is that the control has
an IsAsynchronous property that causes the business object to be created and loaded on a
background thread, and then provided to data binding once the object has been loaded.
Clearly, you can write similar code yourself, using the BackgroundWorker component, or
manually interacting with System.Threading objects. But why would you go to the work of writing
and debugging complex threading code when the work is already done for you by the data provider
control?
Notice that the XAML does not define any factory parameters at all. The code-behind will handle
that entirely on its own. However, since no parameters are provided, the IsInitialLoadEnabled
Using CSLA .NET 3.0
Copyright 2007 Rockford Lhotka
Page 56
property is set to False to prevent the control from trying to invoke the factory method without any
meaningful parameters.
The code-behind includes logic to determine whether a new object should be created, or an
existing object retrieved. Since creating an object and retrieving an object require calling two
different factory methods, the method name is set in the code as well:
void ProjectEdit_Loaded(object sender, RoutedEventArgs e)
{
Csla.Wpf.CslaDataProvider dp =
this.FindResource("Project") as Csla.Wpf.CslaDataProvider;
using (dp.DeferRefresh())
{
dp.FactoryParameters.Clear();
if (_projectId.Equals(Guid.Empty))
{
dp.FactoryMethod = "NewProject";
}
else
{
dp.FactoryMethod = "GetProject";
dp.FactoryParameters.Add(_projectId);
}
}
}
Because the control is a resource of the form, the FindResource() method can be used to retrieve
it by name. Notice that the name used here matches the x:Key property set on the control in the
XAML.
The rest of the code is contained in a using block:
using (dp.DeferRefresh())
{
// ...
}
Data provider controls expose a DeferRefresh() method, that returns an object used to control
when the data provider refreshes its data. By default, every time you change a property on a data
provider control, it will refresh its data. However, if you are setting a series of properties on the
control, you probably dont want it to refresh until you are done setting all of them.
The DeferRefresh() method takes care of this, blocking any refreshes from occurring until the
DeferRefresh object is disposed at the end of the using block. Only when this object is disposed
does the data provider control refresh itself to create a new business object.
Inside the using block, the code clears the data provider controls factory parameter list:
dp.FactoryParameters.Clear();
Page 57
ObservableCollection<T>. By clearing
the list to start with, the code can add appropriate items as
needed to create or retrieve the business object.
In this case, a _projectId value of Guid.Empty is used to indicate that a new business object
should be created, a process that requires no parameters be passed to the factory method:
if (_projectId.Equals(Guid.Empty))
{
dp.FactoryMethod = "NewProject";
}
The FactoryMethod property is set to the name of the factory method that should be invoked to
create a new business object. Since no parameters are required for this method, the
FactoryParameters collection is left empty. This is equivalent to the following XAML:
<csla:CslaDataProvider x:Key="Project"
ObjectType="{x:Type PTracker:Project}"
FactoryMethod="GetProject">
</csla:CslaDataProvider>
If _projectId is not an empty GUID value, then the code changes the factory method name and
sets a parameter to retrieve an existing business object:
else
{
dp.FactoryMethod = "GetProject";
dp.FactoryParameters.Add(_projectId);
}
Notice that not only is the FactoryMethod property set to a method name, but an item is added to
the FactoryParameters collection. That value is passed to the static factory method as a parameter.
If your factory method requires multiple parameters, you would add them to the collection as
well. They are simply passed as parameters to the factory method in the order they are found in the
collection. The collection stores values as type object, so it is up to you to make sure that each value
has the correct type as expected by the method.
At this point, you should understand how to set the properties of a CslaDataProvider control, so
you can dynamically change the factory method name and parameters using code behind the form.
Page 58
Page 59
This control will automatically enable and disable based on the business objects IsSavable
property value. In other words, the Button control will only be enabled if the business object
provided by the CslaDataProvider control is both valid and dirty (changed).
When the Button control is clicked, it triggers the ApplicationCommands.Save command. The
target of that command is specified by the CommandTarget property, which is defined by a Binding
expression.
The CommandTarget propertys Binding expression specifies that the command should be routed to
the Project resource (our CslaDataProvider control), and to the CommandManager property of the
data provider. The BindsDirectlyToSource property indicates that the binding should connect to the
data provider control itself, rather than to the data object returned by the control.
When this command is received by the data provider control, the control does the following:
1. Call ApplyEdit() on the business object to commit any changes to the object in memory
2. Call the business objects Save() method to save the objects data to the database
3. Call BeginEdit() on the new business object returned from the
Save() method
4. Refresh data binding to use the new business object returned from the Save() method
In short, any changes to the business object are safely saved to the database, and data binding
automatically reflects any changes made to the object during the save process.
This control will automatically enable and disable based on the business objects IsDirty
property value. In other words, the Button control will only be enabled if the business object
provided by the CslaDataProvider control is dirty (changed).
In this case, it is an ApplicationCommands.Undo command that is routed to the Project data
provider controls CommandManager property.
When this command is received by the data provider control, the control does the following:
1. Call CancelEdit() on the business object to roll back any changes to the object
2. Call BeginEdit() on the business object to prepare for future editing of the objects
properties
Any changes to the business object are undone, in memory, and the object remains ready for
editing.
Page 60
The highlighted lines of code show the AddNewCore() method override and the setting of the
property to true .
AllowNew
With a collection that implements IBindingList , where AllowNew returns true , you can define an
Add new button like this:
<Button Name="AddItemButton"
Command="ApplicationCommands.New"
CommandTarget="{Binding Source={StaticResource RoleList},
Path=CommandManager,
BindsDirectlyToSource=True}"
HorizontalAlignment="Left">Add new</Button>
This control will automatically enable and disable based on whether the business object
implements IBindingList and whether the AddNew property returns true . The Button control will
only be enabled if the business object provided by the CslaDataProvider control supports adding
new items.
The XAML declaration is no different from the Save or Cancel buttons, except that the
command is ApplicationCommands.New .
When this command is received by CslaDataProvider, the control does the following:
1. Call IBindingList.AddNew() on the business object
This adds a new item to the collection, which is immediately displayed in the UI through data
binding thanks to the automatic ListChanged event raised when a new item is added.
Page 61
Handling Exceptions
Using declarative XAML programming is a powerful tool, but it is also abstract. The
CslaDataProvider control abstracts the creation, retrieval, canceling and saving of business objects
into compact XAML notation.
However, any of those operations could potentially throw an exception. This is especially true for
retrieval of existing data, and saving of data into the database. But if you arent in control of the
retrieval or saving of the object, how can you get access to any exception information?
There are two parts to the answer. First is the DataChanged event raised by the data provider
whenever the data object is created, retrieved or saved. Second is the Error property exposed by the
data provider.
In your code behind a form, you can declare a DataChanged event handler:
protected virtual void DataChanged(object sender, EventArgs e)
{
Csla.Wpf.CslaDataProvider dp = sender as Csla.Wpf.CslaDataProvider;
if (dp.Error != null)
MessageBox.Show(dp.Error.ToString(),
"Data error",
MessageBoxButton.OK,
MessageBoxImage.Exclamation);
}
The highlighted lines of code show the use of the Error property. This property will return null if
the last data provider operation was successful, otherwise it will return the Exception object
representing the failure.
In this example, I am simply displaying the exception detail to the end user. You may choose to
log the exception or take other steps as appropriate for your particular application.
Of course, you must hook the DataChanged event so it is handled by your DataChanged method as
shown by the highlighted lines of code:
public ProjectEdit()
{
InitializeComponent();
Csla.Wpf.CslaDataProvider dp =
this.FindResource("Project") as Csla.Wpf.CslaDataProvider;
dp.DataChanged += new EventHandler(DataChanged);
}
I add this code to the constructors of my forms, ensuring that any DataChanged events are properly
handled.
Page 62
First, it means that you must define two data provider resources. One provides the data to populate
the ComboBox controls list of items, the other typically provides the business object that has a
property corresponding to that list of options.
In the ProjectTracker sample application, the resources assigned to a project have a role. So
there is a list of roles in an object called RoleList, and the ProjectResource object has a Role
property to specify which role that particular resource fills on the project.
The data providers are declared like this:
<csla:CslaDataProvider x:Key="RoleList"
ObjectType="{x:Type PTracker:RoleList}"
FactoryMethod="GetList"
IsAsynchronous="False" />
<csla:CslaDataProvider x:Key="Project"
ObjectType="{x:Type PTracker:Project}"
FactoryMethod="GetProject"
IsAsynchronous="False"
IsInitialLoadEnabled="False"
ManageObjectLifetime="True" />
The RoleList data provider returns a RoleList business object containing a name/value list of the
roles defined in the application. The Project data provider returns a Project business object that has
a child list of ProjectResource objects representing the resources assigned to the project.
The data template controlling the display of that collection of ProjectResource objects looks like
this:
<DataTemplate x:Key="lbTemplate">
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=FullName}" Width="200" />
<TextBlock Text="{Binding Path=Assigned}" Width="100" />
<ComboBox
ItemsSource="{Binding Source={StaticResource RoleList}}"
DisplayMemberPath="Value"
SelectedValuePath="Key"
SelectedValue="{Binding Path=Role}"
Width="150" />
<Button Click="Unassign"
HorizontalAlignment="Left"
Tag="{Binding Path=ResourceId}">Unassign</Button>
</StackPanel>
</Grid>
</DataTemplate>
Page 63
The SelectedValuePath property tells the ComboBox control to privately keep track of the Key
property value for each item in the RolesList collection. The Key value is automatically matched
against the SelectedValue property to control which item is selected in the ComboBox.
The SelectedValue property indicates that the
property from the ProjectResource object.
ComboBox should
What happens is that the ProjectResource objects Role property value is indexed into the Key
property of the items in RoleList to find the matching item. The Value property of that matching
item is displayed to the user in the ComboBox as the currently selected item.
If the user opens the ComboBox, theyll see all the Value values from the RoleList items. When
they click on an item, the Key value from the RoleList for that selected item is used to set the Role
property of the ProjectResource object.
You should now understand how to use the CslaDataProvider control to create, retrieve, cancel
and save business objects through XAML and code behind. Generally speaking, this control can be
used like any other data provider control; and you can use all the normal data binding techniques
available in WPF when interacting with the properties of your business objects.
Validator Control
Windows Forms provides an ErrorProvider control that interacts with data binding to automatically
display validation error information to the user on a per-property basis. This control drives off the
System.ComponentModel.IDataErrorInfo interface, and is very easy to use.
Web Forms in ASP.NET provides a set of validation controls, but they dont drive off the
business object at all. Instead, they end up replicating the validation business logic into the UI and
Presentation layers of the application. In my view, this is an unfortunate step backward.
WPF has a validation scheme somewhat similar to Web Forms, in that you can create validation
components that run within the UI, sitting between the UI controls and the data source and
automatically invoked by data binding. This model is not ideal, because it shifts the validation
business logic into the UI, meaning that it must be a duplicate of the logic in your business objects.
Fortunately, WPF has the infrastructure support needed to create something similar to the
Windows Forms ErrorProvider control. The Validator control in CSLA .NET provides this
functionality, interacting with the object through the same IDataErrorInfo interface, and providing a
standard, stylable visual effect to the UI.
For comparison purposes, Figure 8 shows the default Windows Forms ErrorProvider control,
with a tool tip that displays a human-readable reason the field is in error.
Validator
Page 64
The Validator control is a decorator, which means that it alters the visual appearance of the
control it contains. It can contain only one control, but that control can be a panel, allowing
Validator to effectively contain many controls.
The Validator control not only interacts with the control it contains, but it also interacts with its
data context: the business object to which the controls are bound. This means that the DataContext
must be set on the Validator control itself, or on a control that contains the Validator.
For example:
<csla:Validator DataContext="{Binding Source={StaticResource Project}}">
<StackPanel FlowDirection="LeftToRight">
<TextBlock>Id:</TextBlock>
<TextBlock Text="{Binding Id, Mode=OneWay}"></TextBlock>
<TextBlock>Name:</TextBlock>
<TextBox Name="NameTextBox"
Text="{Binding Name,
Converter={StaticResource IdentityConverter}}"></TextBox>
<TextBlock>Started:</TextBlock>
<TextBox Text="{Binding Started,
Converter={StaticResource IdentityConverter}}"></TextBox>
<TextBlock>Ended:</TextBlock>
<TextBox Text="{Binding Ended,
Converter={StaticResource IdentityConverter}}"></TextBox>
<TextBlock>Description:</TextBlock>
<TextBox Text="{Binding Description,
Converter={StaticResource IdentityConverter}}"
TextWrapping="Wrap"></TextBox>
</StackPanel>
</csla:Validator>
Notice that the DataContext is set directly on the Validator , which makes the business object the
data source for the Validator and all the controls it contains. More commonly, youll define the data
context at a higher level:
Page 65
In this case the Validator will automatically use the data context provided by its container, the
Grid.
Simply wrapping a panel or other control with a Validator automatically causes the Validator to
change the visual style of any data bound control when the underlying business object reports a
validation error through the IDataErrorInfo interface. The CSLA .NET BusinessBase<T> class
implements this interface, and it is integrated with the validation rules concept, so no extra work is
required.
It is important to remember that the Validator control applies to the current data context. In the
example above, it applies to the Project business object to which the controls are data bound.
However, you may also use Validator in a template so it applies to each individual item in a
ListBox or other list control. For example:
...
<Grid.Resources>
<DataTemplate x:Key="lbTemplate">
<csla:Validator>
<StackPanel Orientation="Horizontal">
<TextBox
Text="{Binding FirstName,
Converter={StaticResource IdentityConverter}}"></TextBox>
<TextBox
Text="{Binding LastName,
Converter={StaticResource IdentityConverter}}"></TextBox>
</StackPanel>
</csla:Validator>
</DataTemplate>
...
Later in the XAML you would use this data template in a list control:
<ListBox DataContext="{Binding Source={StaticResource WorkGroup}}"
ItemsSource="{Binding Employees}"
ItemTemplate="{StaticResource lbTemplate}">
</ListBox>
Here, the DataContext is set explicitly on the ListBox, but it could come from a parent control.
The important thing to note is that the ItemsSource is set to a property representing a collection of
items from the WorkGroup object.
Each item in that Employees collection will be displayed according to the data template defined by
That means the data context each time the template is applied is a single child item from
the collection.
lbTemplate.
In other words, each item displayed in the ListBox has its own data context, and so the Validator
control operates against that child object.
Page 66
To provide your own visual effect for a control with a validation error, you need to create your
own error template and set the controls Validation.ErrorTemplate property. In some cases, youll
also interact with the Validation.HasErrors property to implement a trigger to display a tooltip with
the validation error message.
Like virtually everything in WPF, this can be done through either XAML or code, but is typically
implemented purely in XAML.
Note: The ErrorTemplate is set on the visual controls, like TextBox, not on the
Validator control itself.
You can get information about the ErrorTemplate and the HasError properties from this MSDN
article:
http://msdn2.microsoft.com/en-us/library/system.windows.controls.validation.errortemplate.aspx.
There are several ways you can set the ErrorTemplate property, but perhaps the best approach is
to use an application-level style. To do this, open your projects Application.xaml file and add a
custom style within the <Application.Resources> element. For example:
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel>
<Border BorderBrush="Red" BorderThickness="3">
<AdornedElementPlaceholder />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
This style affects all TextBox controls, as noted by the TargetType property on the
The style sets the controls ErrorTemplate property:
Style element.
<Setter Property="Validation.ErrorTemplate">
Page 67
The AdornedElementPlaceHolder element represents the original control within the template (in
this case the TextBox). It is important to realize that the template doesnt redisplay the original
control, but the placeholder allows the template to manipulate the space around the original control,
by adding a red border in this example.
The style also has a trigger, which displays a tool tip with the validation error text:
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static Relative Source.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
ErrorProvider
Page 68
</Ellipse.Fill>
</Ellipse>
<TextBlock Text="!"
Foreground="White"
FontSize="16"
FontWeight="Bold"
HorizontalAlignment="Center"
ToolTip="{Binding
Path=AdornedElement.ToolTip,
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type Adorner}}}"/>
</Grid>
<AdornedElementPlaceholder />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
Authorizer Control
The ReadWriteAuthorization control from the Csla.Windows namespace helps Windows Forms
developers build interfaces where the controls on the form alter their appearance based on whether
the user is authorized to read or write to the underlying business object property.
The Authorizer control in the Csla.Wpf namespace provides similar functionality for WPF. Like
the Validator control, Authorizer is a decorator, and it affects the appearance of the control it
contains. If you want it to affect the appearance of multiple controls, you can nest those controls
within a panel or other container control, inside the Authorizer control itself.
uses the Csla.Security.IAuthorizeReadWrite interface to interact with the business
object. It uses this interface to determine whether the user is authorized to read or write to each
business object property that is data bound to a control contained within the Authorizer control.
Authorizer
Page 69
You can then use Authorizer to control read and write access to the detail controls on your form.
The control is used to wrap, or decorate, another control:
<csla:Authorizer Name="AuthPanel">
<csla:Validator>
<StackPanel FlowDirection="LeftToRight" Margin="0 0 30 0">
<TextBlock>First name:</TextBlock>
<TextBox Text="{Binding FirstName,
Converter={StaticResource IdentityConverter}}"></TextBox>
<TextBlock>Last name:</TextBlock>
<TextBox Text="{Binding LastName,
Converter={StaticResource IdentityConverter}}"></TextBox>
</StackPanel>
</csla:Validator>
</csla:Authorizer>
In this case, it decorates a Validator control. The Validator control ,in turn, decorates a
contains the UI controls actually displayed to the user.
StackPanel that
Like the Validator control, the Authorizer control can be used at a form level, or within a
template that is applied to each item in a ListBox or other list control.
The Authorizer control automatically refreshes authorization rules when the form loads or when
its data context changes. However, you may need to manually force a refresh at times, most notably
when the currently logged in user changes while the form is open.
I typically implement a method called ApplyAuthorization() in each form, that looks similar to
this:
private void ApplyAuthorization()
{
this.AuthPanel.Refresh();
if (Resource.CanEditObject())
{
this.ProjectListBox.ItemTemplate =
(DataTemplate)this.MainGrid.Resources["lbTemplate"];
this.AssignButton.IsEnabled = true;
}
else
{
this.ProjectListBox.ItemTemplate =
(DataTemplate)this.MainGrid.Resources["lbroTemplate"];
((Csla.Wpf.CslaDataProvider)this.FindResource("Resource")).Cancel();
this.AssignButton.IsEnabled = false;
}
Using CSLA .NET 3.0
Copyright 2007 Rockford Lhotka
Page 70
The Authorizer is refreshed, which refreshes the display of all detail controls in the form.
The code also checks to the see if the current user is authorized to edit the business object, and if
so, it changes some other aspects of the UI. It does this by using an editable template for a ListBox
control and enabling a button.
If the user is not authorized to edit the business object, the code changes the ListBox to use a
read-only template for display and disables a button. But more importantly, as indicated by the
highlighted line of code, the CslaDataProvider controls Cancel() method is invoked to force a
CancelEdit() on the business object. This causes the business object to undo any changes made
while the user was able to edit, so the user now sees the original object in its unedited state.
Read Authorization
If the user is not authorized to read the property bound to a UI control, the UI control is collapsed,
hidden or left unchanged. The NotVisibleMode attached dependency property is used to determine
what happens, but the default is to collapse the UI control. Table 8 lists the options for
NotVisibleMode.
Option
Collapsed
Description
If the user is not authorized to read a property bound to this control,
the control is collapsed. This means the control is not visible, and
also consumes no space in the UI.
Hidden
Ignore
Page 71
The first TextBox control is changed so it is hidden if the user is not authorized to read the value.
The second TextBox control will be ignored, so no change will be made to its appearance regardless
of whether the user is authorized to read the property or not. Remember that the default is to collapse
the control, both hiding it and making it consume no space in the UI layout.
It is important to remember that the way the CanReadProperty() method is used in Expert C#
2005 Business Objects, when implementing a property get block ,will throw an exception if the user
is not authorized to read the property. Unfortunately, there is no way to prevent WPF data binding
from attempting to read the property value from the object, even if the UI control is collapsed or
hidden. Due to this, you should implement your property get block to return a dummy value if read
access is not authorized:
public string FirstName
{
get
{
if (CanReadProperty("FirstName"))
return _firstName;
else
return "n/a";
}
}
This way, no exception is thrown. If you are using the Authorizer control the user wont actually
see the dummy value, because the control will be collapsed or hidden.
Write Authorization
If the user is not authorized to write to a property that is data bound to a UI control, the UI control is
set to read-only mode, or entirely disabled.
If the control has an IsReadOnly property then that property is set to true . If the control has no
then IsEnabled is set to false; which always works because the base control
type in WPF implements IsEnabled , so it is available on all UI controls.
IsReadOnly property,
The end result is that the user will be unable to enter data into the UI control if they arent
authorized to write to the underlying business object property.
You should now understand how to use the Authorizer control to automatically alter the
appearance of UI controls based on whether the user is authorized to read or write to the business
object property to which each UI control is data bound.
ObjectStatus Control
Editable CSLA .NET business objects that subclass BusinessBase<T> have a set of valuable status
properties. These properties are not available for data binding because they are marked with the
[Browsable(false)] attribute, and because they dont raise the PropertyChanged event when they
change.
Page 72
Property
IsNew
Description
Indicates whether the object corresponds to an existing primary key
value in the database or not.
IsDirty
IsDeleted
IsValid
IsSavable
Then you can use the control in your form. The ObjectStatus control is a decorator, and so it
contains another control. For example:
<csla:ObjectStatus>
<StackPanel>
...
</StackPanel>
</csla:ObjectStatus>
Remember that the ObjectStatus control requires a DataContext to work. You can set the
DataContext directly on the ObjectStatus control, or on a control that contains the ObjectStatus
control. This is no different than how you use Validator or Authorizer.
Controls inside the ObjectStatus control can use the properties it provides. These are the same
properties as listed in Table 9. Here is an example of using these properties to display the status of
the business object. You can see the resulting output near the bottom of the form shown in Figure 12:
Page 73
<csla:ObjectStatus>
<StackPanel Orientation="Horizontal">
<CheckBox IsEnabled="False"
IsChecked="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType=csla:ObjectStatus, AncestorLevel=1},
Path=IsSavable}">IsSavable</CheckBox>
<CheckBox IsEnabled="False"
IsChecked="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType=csla:ObjectStatus, AncestorLevel=1},
Path=IsValid}">IsValid</CheckBox>
<CheckBox IsEnabled="False"
IsChecked="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType=csla:ObjectStatus, AncestorLevel=1},
Path=IsDirty}">IsDirty</CheckBox>
<CheckBox IsEnabled="False"
IsChecked="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType=csla:ObjectStatus, AncestorLevel=1},
Path=IsNew}">IsNew</CheckBox>
<CheckBox IsEnabled="False"
IsChecked="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType=csla:ObjectStatus, AncestorLevel=1},
Path=IsDeleted}">IsDeleted</CheckBox>
</StackPanel>
</csla:ObjectStatus>
This is a RelativeSource binding, where the binding looks for an ancestor of the current control
(a control that ultimately contains the current control). The ancestor must be of type
csla:ObjectStatus, and it will stop at the first instance of ObjectStatus it finds.
Once data binding has found the first instance of an ObjectStatus control, it binds to the
IsDeleted property, based on the Path property value.
You can use this technique to bind the properties to any other controls Boolean property.
Page 74
IdentityConverter Control
WPF data binding has one obscure issue that can cause trouble in certain cases. If your property set
block alters the value of the inbound data, any changes you make will not be shown in the UI until
some other property is changed. This can lead to some very inconsistent UI behaviors that can
confuse your users.
For example, consider this property set block:
public string ProductNumber
{
get
{
if (CanReadProperty("ProductNumber"))
return _productNumber;
else
return string.Empty;
}
set
{
CanWriteProperty("ProductNumber", true);
if (string.IsNullOrEmpty(value)) value = string.Empty;
if (_productNumber != value)
{
_productNumber = value.ToUpper();
PropertyHasChanged("ProductNumber");
}
}
}
The highlighted line of code is important, because it changes the incoming value to be all upper
case. This is entirely valid code, but it can cause problems with WPF data binding.
When the user types a value into a data bound control, such as a TextBox, their new value is
placed into the business objects property. The property, in this example, changes the value and
keeps the changed value. It also raises the PropertyChanged event (due to the PropertyHasChanged()
method call). This event tells data binding that the property has changed.
However, as an optimization, data binding does not refresh the current control. It refreshes all
other controls on the form, but not the current control. The erroneous assumption is that property set
blocks never change values.
Fortunately, theres a solution to this problem.
WPF has a concept called a value converter that can be used to format values as data binding
moves them from the data source to the UI control and from the UI control to the data source. Value
converters are very useful, especially when dealing with date values, because they allow the UI
developer to control how the value appears to the user, or to parse complex user input into something
the business object can understand.
It turns out that when a value converter is associated with a UI control, data binding always
refreshes the display. It has to refresh the display, because data binding has no way of knowing what
the value converter might do to the value as it moves from the data source to the UI control.
In other words, as long as you attached a value converter to your UI control, the control will
refresh its display as part of the data binding process. In most cases, however, you dont actually
want to change the value from the business object property, you just want to refresh the display.
Page 75
The IdentityConverter value converter from the Csla.Wpf namespace can be used to force UI
controls to update their display without changing the value from the business object.
Note: The word identity in this case comes from standard arithmetic where 0 is the
identity for addition (anything plus 0 is the original number), and 1 is the identity for
multiplication (anything times 1 is the original number).
You must also define the value converter as a resource in your form:
<Page.Resources>
<csla:IdentityConverter x:Key="IdentityConverter" />
<Page.Resources>
Then you can use the IdentityConverter value converter in your XAML. For example:
<csla:ObjectStatus>
<csla:Authorizer Name="AuthPanel">
<csla:Validator>
<StackPanel FlowDirection="LeftToRight" Margin="0 0 30 0">
<TextBlock>First name:</TextBlock>
<TextBox csla:Authorizer.NotVisibleMode=Hidden
Text="{Binding FirstName,
Converter={StaticResource IdentityConverter}}"></TextBox>
</StackPanel>
</csla:Validator>
</csla:Authorizer>
</csla:ObjectStatus>
The Converter property is used in a Binding expression to specify the value converter that should
be used by data binding as the data value moves between the UI control and the data source. The
highlighted line of code specifies that the IdentityConverter should be used.
The result is that the value is not changed as it moves between the UI control and the data source,
but data binding will always refresh the UI controls contents when the underlying business object
property changes.
If you do have a value converter that changes the value, thats fine. You would just use that value
converter instead of IdentityConverter. The IdentityConverter control is intended to be used in
cases where you dont otherwise need a value converter, but want the UI controls to refresh their
display as users would expect.
Note: If you know that a property set wont change the value, there is no need to use
an IdentityConverter when binding to that property.
At this point, you should have a good understanding of how CSLA .NET supports WPF
development, through the CslaDataProvider , Validator , Authorizer, ObjectStatus and
IdentityConverter controls.
Using CSLA .NET 3.0
Copyright 2007 Rockford Lhotka
Page 76
Chapter 3:
Workflow
Another pillar of Microsoft .NET 3.0 is the Windows Workflow Foundation (WF). WF is a basic
workflow engine, but it also comes with a designer experience that integrates into Visual Studio.
In this chapter, I assume you have a basic understanding of WF, and know how to use the
workflow designer in Visual Studio to lay out a workflow. I also assume you are familiar with the
concept of creating a workflow in an external assembly that may also include custom activities.
It is important to realize that workflow, in general, is a procedural programming model. You
might think that theres little need for object-oriented concepts in the workflow world, since
procedural programming and object-oriented programming are quite different. That isnt entirely the
case.
Certainly, the workflow itself is procedural: workflows are basically anima ted flowcharts after all.
But an object-oriented application can invoke a workflow, and workflow activities can be
constructed using objects behind the scenes.
A workflow activity is, by definition, a self-contained unit of functionality with defined inputs and
outputs. This is also the basic definition of a use case. As a result, you can view a workflow activity
as a use case! This is exciting, because creating workflow activities using object-oriented design
concepts is as close as most of us will ever get to doing pure object design, without all the
complexities of user interaction and so forth.
Figure 13 illustrates, at a high level, one architectural view of using both objects and workflows.
Page 77
Page 78
Page 79
properties and events) and the business objects. You should think of the code in the activity as an
encapsulation layer that hides the business objects used to implement the activity.
Notice how the highlighted lines of code are simply using the Project class from the
ProjectTracker.Library project. This is very similar to the code youd expect to find in a WCF
Page 80
service to close a project, reinforcing how a workflow activity is really just another type of interface
to your business layer.
Again, each activity should be viewed as an atomic task, and so it should have little or no
dependencies on prior activities, and should leave no state behind that could be used or misused by
subsequent activities.
The input for this activity is the ProjectId dependency property, which is global to the workflow:
private static DependencyProperty ProjectIdProperty = DependencyProperty.Register(
"ProjectId", typeof(Guid), typeof(ProjectWorkflow), null);
public Guid ProjectId
{
get { return (Guid)base.GetValue(ProjectIdProperty); }
set { base.SetValue(ProjectIdProperty, value); }
}
As youll see later in this chapter, the ProjectId value is set by the code that invokes the
workflow itself, so it is considered a required input to the workflow.
Due to the use of the dependency property, the closeProject implementation has no requirements
on any prior activities, and leaves no state behind for subsequent activities. The result is that this
activity can be used anywhere in the workflow, as long as ProjectId contains a valid value.
Page 81
Notice that _project is a private field of the workflow itself, so when the object is loaded it is
available to subsequent activities within the workflow.
You could choose, instead, to create a dependency property. This makes the Project object
available for data binding to activity dependency properties through the designer. In that case, the
code looks like this:
private static DependencyProperty ProjectProperty = DependencyProperty.Register(
"Project", typeof(Project), typeof(ProjectWorkflow), null);
public Project Project
{
get { return (Project)base.GetValue(ProjectProperty); }
Using CSLA .NET 3.0
Copyright 2007 Rockford Lhotka
Page 82
The closeProject and other activities can then make use of this pre-loaded object. Heres the
new closeProject activity code:
private void closeProject_ExecuteCode(object sender, EventArgs e)
{
this.Project.Ended = DateTime.Today.ToString();
this.Project = this.Project.Save();
}
A private field is simpler and incurs less overhead, and is perfectly adequate when all your
activities are code activities implemented directly in the workflow. However, if you use activities
from external assemblies, you may want the more advanced data binding features enabled by storing
the value in a dependency property.
Pay special attention to the Save() method call:
this.Project = this.Project.Save();
Notice how the result of this method is used to replace the value of the workflow-level Project
value. Remember that the CSLA .NET Save() method often returns an updated version of the object,
and since this object may be used by later activities in the workflow, it is important to update the
global value.
Page 83
to both the CSLA .NET framework and the business objects common to all ProjectTracker
applications.
Implementing a SequentialActivity
By default, when you add an activity to a workflow project or a workflow activity library, youll get
a SequentialActivity. This type of activity is like a mini-workflow, and you can use the normal
workflow designer to add sub-activities within the activity to create its implementation.
In other words, the activity designer in Visual Studio helps you implement activities using
procedural design. And sometimes thats fine, but you may want to implement your activity using
business objects instead. The simplest way to do this is to drag a single Code Activity onto the
designer as shown in Figure 17.
The activity defines a dependency property, Project, so the workflow can provide the activity
with an instance of a Project object to close. The doClose code activitys ExecuteCode() method
then uses this value to do its work:
this.Project.Ended = DateTime.Today.ToString();
this.Project = this.Project.Save();
Page 84
This is the same code as in the earlier implementation, but now it is encapsulated in an activity
that is implemented entirely outside the workflow. The result is that this activity can be used by any
workflow that can set the Project dependency property to a valid value.
Implementing an Activity
Another way you can implement an external activity is by adding a SequentialActivity and then
changing the base class from which it inherits. You must change the base class before doing anything
else to the activity or you can confuse the Visual Studio activity designer:
public partial class GetProject: Activity
Figure 18 shows how the designer looks once youve changed this base class.
As with the CloseProject activity, this activity also implements dependency properties:
private static DependencyProperty ProjectIdProperty = DependencyProperty.Register(
"ProjectId", typeof(Guid), typeof(GetProject), null);
public Guid ProjectId
{
get { return (Guid)base.GetValue(ProjectIdProperty); }
set { base.SetValue(ProjectIdProperty, value); }
}
private static DependencyProperty ProjectProperty = DependencyProperty.Register(
"Project", typeof(Project), typeof(GetProject), null);
public Project Project
{
get { return (Project)base.GetValue(ProjectProperty); }
set { base.SetValue(ProjectProperty, value); }
}
The ProjectId property is an input, and provides the activity with the Guid value that identifies
the project to get. The Project property exposes the resulting Project object to the workflow and
any other activities within the workflow.
Again, notice how the actual implementation is the same as before:
Using CSLA .NET 3.0
Copyright 2007 Rockford Lhotka
Page 85
this.Project = Project.GetProject(this.ProjectId);
But the activitys implementation is now encapsulated into a separate location from the workflow.
Any workflow can use this activity to load a Project, as long as it can provide a valid ProjectId .
Page 86
Page 87
As shown in Figure 22, the Project property is bound to getProject1.Project for the
closeProject1 activity.
Directly using the properties of activities simplifies the code in the workflow itself.
You should now understand how to use business objects when implementing simple code
activities, SequentialActivity objects or Activity objects. You should also understand how to
declare and use dependency properties to pass values and objects into and out of activities, through
data binding or directly through code.
Page 88
There are two issues you need to consider. First, the WF engine uses the BinaryFormatter to
serialize and deserialize the data. This means that you cant use the DataContract attribute on your
business objects if you intend on exposing them through dependency properties. Instead, you must
continue to use the Serializable attribute.
Second, and more importantly, what you need to worry about here is versioning. If you update
your business assembly (DLL) while a workflow is unloaded, and you change the fields in the
business objects that were serialized (adding or removing fields), you may run into some versioning
issues when the data is deserialized.
The WF engine uses the BinaryFormatter in a mode where it is not version sensitive. This means
that the deserialization will not fail due to a version mismatch even if the business assembly has been
updated since the object was serialized. However, Table 10 lists some specific issues you need to
consider.
Issue
Add new field to
business object
Description
If you add a new field to a business object, the serialized byte stream
clearly has no value corresponding to that field. When the byte stream
is deserialized, that field is left uninitialized.
Remove field
from business
object
Mark a field as
NotSerialized
Page 89
What youll find, though, is that you dont need to use this attribute to avoid getting an exception
when the WF engine reloads a workflow instance. In other words, the OptionalField attribute is
optional.
However, you can use the OptionalField attribute in conjunction with the
attribute to set default values for optional fields:
OnDeserializing
[OptionalField]
private int _newField;
[OnDeserializing]
private void OnDeserializingHandler(StreamingContext context)
{
_newField = 123; // default value
}
The method marked with OnDeserializing is invoked by the BinaryFormatter as the objects
fields are deserialized from the byte stream, and it is intended to be used as shown here: loading
default values into optional fields.
You can also specify the version of your object at which the field was added:
[OptionalField(VersionAdded = 3)]
private int _newField;
[OnDeserializing]
private void OnDeserializingHandler(StreamingContext context)
{
_newField = 123; // default value
}
The BinaryFormatter then knows to ignore the value when deserializing any byte streams from
older versions of the object.
These techniques allow you to construct your business objects in a way that is safe for versioning,
even if the business object is serialized and deserialized as part of a workflows state.
Page 90
object for that workflow, which implies that the business layer would have a reference to the
workflow project. This sort of circular reference between assemblies is not allowed.
If you look back at Figure 14, the workflow is architecturally treated as just another interface to
the business layer. And thats a good way to think about the workflow. That means that having the
workflow reference the assembly containing the business objects is a good thing, so I dont
recommend changing that.
To avoid a circular reference, the business layer must not reference the workflow assembly. You
can avoid the circular reference by dynamically loading the workflow type. This is done with code
like this:
Type workflowType = Type.GetType("Namespace.WorkflowClass, Assembly");
WorkflowInstance instance =
workflowRuntime.CreateWorkflow(workflowType);
The Namespace.WorkflowClass part of the text is the fully qualified type name of the workflow
class, including namespace. The Assembly part of the text is the name of the assembly containing the
workflow class. Notice how the CreateWorkflow() method then accepts the Type object. This allows
the workflow runtime to properly create an instance of the workflow, without this code needing a
reference to the assembly that contains the workflow.
WorkflowManager Class
CSLA .NET provides a WorkflowManager class in the Csla.Workflow namespace to help abstract the
process of executing a workflow. The primary purpose of this class is to help manage the use of
thread synchronization, while maintaining the flexibility provided by the workflow runtime model.
Table 11 lists the methods provided by the
Method
ExecuteWorkflow
WorkflowManager
object.
Description
Synchronously executes a workflow, blocking the calling
thread until the workflow stops.
BeginWorkflow
WaitForEnd
ResumeWorkflow
BeginResumeWorkflow
InitializeRuntime
Page 91
In this case, the ExecuteWorkflow() method creates a Type object for the workflow based on the
assembly qualified name of the workflow, such as PTWorkflow.ProjectWorkflow, PTWorkflow.
Alternately, you can just pass a Type object as a parameter, rather than the type name:
WorkflowManager mgr = new WorkflowManager();
mgr.ExecuteWorkflow(typeof(ProjectWorkflow));
Either way, this synchronously executes the workflow. When ExecuteWorkflow() returns, the
workflow is complete or terminated, and the workflow runtime has been disposed.
You can examine mgr.Status to determine the final state of the workflow. The possible values are
listed in Table 12.
Page 92
Status
Initializing
Description
The workflow is being initialized and has not yet started
executing.
Executing
Completed
Terminated
Suspended
Idled
Aborted
ExecuteWorkflow() method
Starting a Workflow
This code shows the basic structure required to execute a workflow that might be idled or suspended,
and which will be persisted to a database in both those cases:
Csla.Workflow.WorkflowManager mgr = new Csla.Workflow.WorkflowManager();
mgr.InitializeRuntime();
// associate your persistence service with mgr.RuntimeInstance here
mgr.ExecuteWorkflow("WorkflowApp.Workflow1, WorkflowApp", false);
if (mgr.Status == Csla.Workflow.WorkflowStatus.Suspended)
{
Guid instanceId = mgr.WorkflowInstance.InstanceId;
mgr.WorkflowInstance.Unload();
// store instanceId so you can resume the workflow later
}
mgr.DisposeRuntime();
If you expect your workflow might become idled or suspended, and you want to store that
workflow instance in a database to resume later, you need to provide a persistence service to the
workflow runtime.
Page 93
This line of code adds the service to the workflow runtime instance being used by the
WorkflowManager.
Once this is done, the DisposeRuntime() method of the WorkflowManager is called to dispose the
workflow runtime.
Page 94
Make special note of the comment in the code above. If you unload a workflow, you need the
workflows InstanceId value, a Guid , to reload and resume the workflow later. Without this value
the workflow cant be reloaded.
The ResumeWorkflow() method uses the instanceId value to load the workflow instance from the
persistence service and then resumes the workflow execution.
At this point, you should understand how to execute, unload and resume a workflow that could
become idle or suspended as it runs.
Notice that no persistence service is required, because the workflow is never unloaded. However,
the ExecuteWorkflow() method is still called such that the workflow runtime is not disposed when it
completes.
The Status property is then checked, and if the workflow was suspended then the application
needs to do any work that is required before the workflow is resumed. This is represented by the first
highlighted line of code.
The second highlighted line of code shows how to resume the current workflow. Since the
workflow wasnt unloaded, it is still in memory, just suspended. The ResumeWorkflow() method call
resumes the workflow and allows it to run to completion.
Page 95
Execution of a workflow should fit naturally into your object model. Remember that the UI layer
always interacts with your business objects, never with the database or any other services directly. If
your application makes use of a workflow to perform some back-end processing, that processing
should be represented somehow within your object model.
The code in DataPortal_Execute() is highlighted, as this is the code that executes the workflow.
Notice the use of a Dictionary to pass in name/value parameters to the workflow. The parameter
sets the ProjectId dependency property of the ProjectWorkflow.
It is also important to note that ExecuteWorkflow() is called using the type name of
reference PTWorkflow,
When ExecuteWorkflow() completes, the Status property is checked to see if the workflow
terminated abnormally. The exception from the workflow is then thrown, indicating to the original
caller of the object that the command failed.
With ProjectCloser implemented like this, any code using the business layer can close a project
like this:
ProjectCloser.CloseProject(projectId);
Page 96
The calling code doesnt need to worry about workflows, threading or any of the details.
Everything is abstracted behind the business object.
Better yet, ProjectCloser is a standard CSLA .NET business object, which means that its call to
DataPortal.Execute() uses the data portal. If the application is configured properly, this means that
the workflow would run on the application server, or if the application is configured to use a local
data portal the workflow would run on the client machine.
Page 97
The workflow now includes a check to see if the project is closed, and if it is closed then the
notification processes are invoked. If the project isnt closed nothing happens. However, the
workflow can be changed independently from the rest of the application, and so theres a great deal
of flexibility offered because additional processing can be added without affecting the business or UI
layers.
What you cant see from the designer surface is that this new version of ProjectWorkflow has a
dependency property called Project, and the ProjectId property has been removed:
private static DependencyProperty ProjectProperty = DependencyProperty.Register(
"Project", typeof(Project), typeof(ProjectWorkflow), null);
public Project Project
{
get { return (Project)base.GetValue(ProjectProperty); }
set { base.SetValue(ProjectProperty, value); }
}
The reason for this change is that workflow is designed to be invoked as the Project object
inserts or updates itself. Executing the workflow is handled in a new method in the Project class:
private void ExecuteWorkflow()
{
Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("Project", this);
Csla.Workflow.WorkflowManager mgr = new Csla.Workflow.WorkflowManager();
mgr.ExecuteWorkflow("PTWorkflow.ProjectWorkflow, PTWorkflow", parameters);
}
Notice how the actual instance of the Project object, this , is passed into the workflow as a
parameter, so it can be used by the activities in the workflow.
Note: Because the PTWorkflow assembly is being loaded dynamically by name
without being referenced by ProjectTracker.Library, you must ensure that the
PTWorkflow assembly is physically located in the same directory as your applications
EXE or in your data portal servers \bin directory.
The easiest way to do that is typically to reference the PTWorkflow project from your
main UI project, such as PTWpf or PTWin.
The final step is to call ExecuteWorkflow() from the DataPortal_Insert() and
DataPortal_Update() methods. For example, heres the call in DataPortal_Update():
[Transactional(TransactionalTypes.TransactionScope)]
protected override void DataPortal_Update()
{
if (base.IsDirty)
{
using (SqlConnection cn = new SqlConnection(Database.PTrackerConnection))
{
cn.Open();
using (SqlCommand cm = cn.CreateCommand())
{
cm.CommandText = "updateProject";
cm.Parameters.AddWithValue("@lastChanged", _timestamp);
DoInsertUpdate(cm);
}
}
Using CSLA .NET 3.0
Copyright 2007 Rockford Lhotka
Page 98
}
// update child objects
_resources.Update(this);
MarkOld();
ExecuteWorkflow();
}
Notice how the workflow is executed after both the Project object and all its child objects have
been updated. The Project object in memory should exactly match the data in the database.
Also notice the explicit call to MarkOld() , to mark the object as both not new and not changed.
The data portal will make this call too, but after DataPortal_Update() completes, and it is best if the
call happens before the workflow executes so the workflow activities are working against accurate
data.
Remember that this code is still running within a TransactionScope due to the Transactional
attribute on the method. If the workflow terminates abnormally, the exception from the workflow
will be thrown, which will automatically roll back the transaction.
If you want the workflow to execute outside of the transaction, youll need to switch to manual
transactions and create your own TransactionScope. Heres what that code would look like:
protected override void DataPortal_Update()
{
using (TransactionScope tr = new TransactionScope())
{
if (base.IsDirty)
{
using (SqlConnection cn = new SqlConnection(Database.PTrackerConnection))
{
cn.Open();
using (SqlCommand cm = cn.CreateCommand())
{
cm.CommandText = "updateProject";
cm.Parameters.AddWithValue("@lastChanged", _timestamp);
DoInsertUpdate(cm);
}
}
}
// update child objects
_resources.Update(this);
tr.Complete();
}
ExecuteWorkflow();
}
The highlighted lines of code are the extra lines needed to implement the transactional behavior
manually. The Transactional attribute has been removed from the method, which means that the
data portal wont wrap this method in a transaction automatically. Manual transactions are the default
behavior by the data portal.
The ExecuteWorkflow() method is called after the transaction is complete, meaning that it will
execute outside your transaction. Now if the workflow terminates abnormally, the exception from the
workflow will be thrown, but wont roll back the database transaction.
You can use either technique, depending on whether you want a workflow exception to roll back
the data operation or not.
Page 99
At this point, you should understand how to use WF and business objects together. You can build
workflow activities using business objects within the activity, and you can execute workflows from
within your business objects.
Page 100
Chapter 4:
Validation
CSLA .NET includes framework support for implementing validation logic within each of your
business objects. This functionality was introduced in version 2.0 and is described in Expert C# 2005
Business Objects. CSLA .NET version 2.1 introduced several major enhancements, and they are
described in the CSLA .NET Version 2.1 Handbook.
CSLA .NET version 3.0 includes more enhancements to the validation rule functionality. Some of
these enhancements are relatively straightforward, but a couple are more complex and are designed
either for advanced scenarios or to simplify code generation of business objects. Table 13 provides a
summary of the new features.
Feature
StringMinLength
rule
Description
This is a new rule method in
Csla.Validation.CommonRules.
rule null
handling
Format mask
DecoratedRuleArgs
Page 101
StringMinLength Rule
CSLA .NET 2.0 included the StringRequired and StringMaxLength rule methods. The
StringMinLength method completes the set, making it possible to mandate a minimum length for a
string value. You use the rule as follows:
ValidationRules.AddRule(
Csla.Validation.CommonRules.StringMinLength,
new Csla.Validation.CommonRules.MinLengthRuleArgs("Name", 5));
This example ensures that the Name property has a string with length 5 or greater, or the object will
be considered invalid.
RegExMatch Rule
The RegExMatch rule method returns true if the property value matches the supplied regular
expression. However, in version 2.0 there were no explicit options for dealing with null values, and
that shortcoming has been addressed in version 3.0 with the addition of the NullResultOptions
enumerated list. The possible values are listed in Table 14.
Value
ConvertToEmptyString
Description
Converts any null value to an empty string before
checking the regular expression.
ReturnFalse
ReturnTrue
The highlighted line of code specifies that the rule should convert null values to an empty string
before checking the regular expression.
Page 102
but sometimes the property name isnt what youd like to display to the user. For example, a property
called ProjectName might be better represented by the text Project name.
CSLA .NET 3.0 includes support for friendly names for properties. If a friendly name is
supplied, it is used in the rule description text, otherwise the property name is used. The friendly
name is passed into the rule method as part of the RuleArgs parameter.
RuleArgs Property
The RuleArgs class has been enhanced to include a PropertyFriendlyName property. You can set this
property directly, or through an overloaded constructor. For example:
Csla.Validation.RuleArgs args = new Csla.Validation.RuleArgs("Name");
args.PropertyFriendlyName = "Project name";
ValidationRules.AddRule(
Csla.Validation.CommonRules.StringRequired, args);
Or:
ValidationRules.AddRule(
Csla.Validation.CommonRules.StringRequired,
new Csla.Validation.RuleArgs("Name", "Project name"));
If you set the PropertyFriendlyName property, this value is provided to the rule method
implementation, which can use this value to generate the broken rule description.
GetPropertyName Method
When you implement a custom rule method you should honor any value provided through
PropertyFriendlyName, and if no value is provided then you should use the PropertyName value:
string name;
if (string.IsEmptyOrNull(e.PropertyFriendlyName))
name = e.PropertyName;
else
name = e.PropertyFriendlyName;
Writing that same code in each rule method is redundant however, and so the RuleArgs class
includes a helper method to do the work for you:
string name = Csla.Validation.RuleArgs.GetPropertyName(e);
You can use this GetPropertyName() method to generate your description text. For example, a
Customer class might include a rule like this:
private static bool CreditLimit<T>(
T target, Csla.Validation.RuleArgs e) where T : Customer
{
if (target._creditLimit > Customer.MaxCreditAllowed)
{
e.Description = Csla.Validation.RuleArgs.GetPropertyName(e) + " limit exceeded;
return false;
}
else
return true;
}
Page 103
The rule methods in Csla.Validation.CommonRules make use of this pattern and you can use
them as examples for using the new functionality.
StringMinLength
StringMaxLength
MaxValue
MinValue
IntegerMaxValue
IntegerMinValue
RegExMatch
format parameter
are:
The constructors for the RuleArgs subclasses for each of these rules include a format parameter
allowing you to specify the value.
Page 104
In CSLA .NET 2.0 there was a bug where the StopProcessing property was never reset to false,
and so rule processing always short-circuited for a property after the rules were first checked. In
CSLA .NET 3.0, StopProcessing is set back to false after the rule has been processed.
CSLA .NET 3.0 also supports the use of the StopProcessing property even if the rule method
returns true :
private static bool AlwaysStop(object target, RuleArgs e)
{
e.StopProcessing = true;
return true;
}
This variation allows you to stop processing subsequent rule methods for the property, without
making the property or business object invalid. This technique can be useful if there are some rules
that you only want to process under specific circumstances, such as when the object is new. For
example:
private static bool StopIfNotNew<T>(T target, RuleArgs e) where T : IEditableBusinessObject
{
if (!target.IsNew)
e.StopProcessing = true;
return true;
}
By using priorities you can have some rules that run at all times, and other rules that only run for
new objects:
protected override void AddBusinessRules()
{
ValidationRules.ProcessThroughPriority = 2;
ValidationRules.AddRule(
Csla.Validation.CommonRules.StringRequired, "Name");
ValidationRules.AddRule<Customer>(
StopIfNotNew<Customer>, "Name", 1);
ValidationRules.AddRule(
SomeOtherRule, "Name", 2);
}
Page 105
For example:
protected override void AddBusinessRules()
{
ValidationRules.AddRule(
Csla.Validation.CommonRules.StringRequired,
new Csla.Validation.RuleArgs("Name", "Project name"));
ValidationRules.AddRule(
Csla.Validation.CommonRules.StringMinLength,
new Csla.Validation.CommonRules.MinLengthRuleArgs("Name", 5));
ValidationRules.AddRule(
Csla.Validation.CommonRules.StringMaxLength,
new Csla.Validation.CommonRules.MaxLengthRuleArgs("Name", 50, "d"));
ValidationRules.AddRule(
Csla.Validation.CommonRules.MaxValue<int>,
new Csla.Validation.CommonRules.MaxValueRuleArgs<int>(
"Price", "Item price", 200, "$#,##0.00"));
}
Notice how each argument object is a different type, and accepts different parameters in its
constructor. While it is possible to create code generation to handle this, the generator or template
ends up being complex and fragile.
DecoratedRuleArgs
The DecoratedRuleArgs is a new subclass of RuleArgs in the Csla.Validation namespace. This
class builds on RuleArgs by using the Decorator design pattern; which is a fancy way of saying that
it provides a Dictionary of name/value pairs that are passed into the rule method.
The previous AddBusinessRules() implementation can be written this way instead:
protected override void AddBusinessRules()
{
Csla.Validation.DecoratedRuleArgs args;
args = new Csla.Validation.DecoratedRuleArgs("Name", "Project name");
ValidationRules.AddRule(
Csla.Validation.CommonRules.StringRequired, args);
args = new Csla.Validation.DecoratedRuleArgs("Name");
args["MinLength"] = 50;
args["Format"] = "d";
ValidationRules.AddRule(
Csla.Validation.CommonRules.StringMinLength, args);
args = new Csla.Validation.DecoratedRuleArgs("Name");
args["MaxLength"] = 50;
args["Format"] = "d";
ValidationRules.AddRule(
Csla.Validation.CommonRules.StringMaxLength, args);
args = new Csla.Validation.DecoratedRuleArgs("Price", "Item price");
args["MaxValue"] = 200;
args["Format"] = "$#,##0.00";
ValidationRules.AddRule(
Csla.Validation.CommonRules.MaxValue<int>, args);
}
The result is more code, but the code is more consistent. Every call to AddRule() is the same, and
theres no need to know about custom argument types or different parameters to the constructor.
Since the parameters are added as name/value pairs, they can be easily generated.
This is possible, because all the custom RuleArgs subclasses used by the rule methods in
actually subclass from DecoratedRuleArgs, and the rule
methods themselves use the Dictionary to get all parameter values.
Csla.Validation.CommonRules now
Page 106
Note: This technique can only be used with rule methods that accept
DecoratedRuleArgs or a subclass of DecoratedRuleArgs as a parameter. You must
change any existing rule methods before using this technique!
The only remaining variation that a code generator has to deal with is the use of generics. Some
rule methods are generic, others are not. But the use of generics (or not) when creating rule methods
is under your control, and so you can either make your code generator accommodate their use, or you
can mandate that all (or none) of your rule methods use generics to standardize the generated code as
you choose.
Because the delegate signature for a rule method requires a RuleArgs parameter, the parameter
must be cast to DecoratedRuleArgs:
Csla.Validation.DecoratedRuleArgs args = (Csla.Validation.DecoratedRuleArgs)e;
Once thats done, the args field is a Dictionary containing the parameter values, so they can be
used in the rule implementation:
if (target._shipDate > (DateTime)args["MaxDate"])
This rule can now be associated with a property in the AddBusinessRules() method:
Page 107
Again, the call to AddRule() is very standardized and can easily be generated by a tool or code
generation template.
One subtle issue to note about this example code, is that AddBusinessRules() is run exactly once
during the lifetime of your application. It is run when the first instance of this business object is
created, and that is the point at which the MaxDate parameter is set:
args["MaxDate"] = DateTime.Now;
If the application is not closed until the next day, this date could become invalid. If that is a
concern, youd need to use a different technique to set the MaxDate value so it could be changed as
the application continues to run.
CommonRules Parameters
The one primary downside to using DecoratedRuleArgs is that the parameters used by your rule
method are no longer strongly typed, and so Intellisense is not available. This means that the author
of a rule method must clearly document the arguments expected.
The rules in Csla.Validation.CommonRules still have their strongly typed RuleArgs subclasses,
and so Intellisense still works if you use those subclasses. However, as youve seen, these rule
methods also accept a simple DecoratedRuleArgs parameter, in which case the parameter names and
meanings must be documented. Table 15 lists the values for each rule method.
Page 108
Rule
Parameter
MaxLength
StringMaxLength
StringMinLength
MaxValue
MinValue
IntegerMaxValue
IntegerMinValue
RegExMatch
Description
Maximum length allowed
Format
MinLength
Format
MaxValue
Format
MinValue
Format
MaxValue
Format
MinValue
Format
RegEx
Regular expression
NullOption
RuleDescription
CSLA .NET 2.1 introduced the use of a URI to describe the rules associated with the properties of an
object. A rule URI looks like this:
rule://methodName/propertyName?arg1=value&arg2=value
Page 109
For a complete discussion of the rule:// URI format please refer to the CSLA .NET Version 2.1
Handbook.
CSLA .NET 3.0 includes a new RuleDescription class that understands how to parse a rule://
URI for easier use. Rather than manually writing code to parse the URI, the RuleDescription class
can be used to easily get at the parts of the URI:
Csla.Validation.RuleDescription desc = new Csla.Validation.RuleDescription(
"rule://methodName/propertyName?arg1=value&arg2=value");
string scheme = desc.Scheme;
string methodName = desc.MethodName;
string propertyName = desc.PropertyName;
List<string> args = new List<string>();
foreach (System.Collections.Generic.KeyValuePair<string, string> item in desc.Arguments)
args.Add(item.Key + ", " + item.Value);
Property
Scheme
RuleDescription
object.
Description
Returns the URI scheme, which will always be rule://.
MethodName
PropertyName
Returns the name of the business object property with which this
rule is associated.
Arguments
rule:// URI.
You should now have a good understanding of the new features and capabilities available for
creating and using rule methods in CSLA .NET 3.0.
Page 110
Chapter 5:
Authorization
CSLA .NET 2.0 introduced support for property level authorization in business objects. By calling
CanReadProperty() in your property get code and CanWriteProperty() in your property set code,
you can ensure that only authorized users are able to read and write each property value.
By overriding the AddAuthorizationRules() method, you can specify the roles that are allowed
or denied read and write access to all the properties in an object.
CSLA .NET 3.0 extends this capability by adding a CanExecuteMethod() method, and
corresponding AllowExecute() and DenyExecute() methods for use in AddAuthorizationRules() .
CanExecuteMethod Method
The CanExecuteMethod() method is similar to CanReadProperty() and CanWriteProperty(). You
can use it at the top of a method in your business object to determine whether the current user is
authorized to execute that method.
The CanExecuteMethod() method has several overloads you can use. You may choose to
explicitly provide the method name:
public void DoSomething()
{
CanExecuteMethod("DoSomething", true);
// do something
}
Notice that you must use the NoInlining attribute to tell the .NET just-in-time compiler not to
apply the inlining optimization to this method. The inlining optimization literally copies your code
from inside the method directly into the code that is calling your method. The result is that your
method doesnt really exist in the code at runtime, and so CanExecuteMethod() is then unable to
dynamically determine the name of your method.
This second option allows for more maintainable code, because you dont have a string literal in
your code. However, dynamically determining the method name does incur a performance penalty.
You can decide which overload to use based on whether maintainability or performance is the
highest priority for your application.
In both the previous examples, a Boolean parameter was passed into the method. This parameter
specifies whether CanExecuteMethod() should throw an exception if the user is not authorized to
execute the method. Passing true indicates that an exception should be thrown. You can also pass
false:
Using CSLA .NET 3.0
Copyright 2007 Rockford Lhotka
Page 111
The method executes whether the user is authorized or not, but if the user isnt authorized, the
method does no work. You can use this overload to throw your own exception, perform no action or
perform an alternate action if the user is not authorized.
Page 112
Chapter 6:
Data Binding
Data binding is an important technology that provides a clear, abstract interface between business
objects and the user interface. Whether the interface is Web Forms, Windows Forms or WPF, data
binding is very useful.
Windows Forms data binding is far richer and more demanding of the business objects than Web
Forms. At the present time Windows Forms data binding is more demanding than WPF as well,
though I expect that WPF will rapidly mature to be comparable to Windows Forms.
Because of the demanding nature of Windows Forms data binding, I am constantly on the lookout
for bugs and issues with the implementation of the data binding support in CSLA .NET. For business
objects to fully support data binding, they must implement some relatively complex and very
nuanced interfaces defined by Microsoft. You can find a full discussion of these interfaces and their
original implementations in Expert C# 2005 Business Objects.
In this chapter Ill discuss the bug fixes and changes made in CSLA .NET 3.0. These changes
address issues that have been discovered and addressed between CSLA .NET version 2.1 and 3.0.
Method
CopyState()
Condition
If the new EditLevel would exceed the parents
EditLevel
UndoChanges()
AcceptChanges()
//
//
//
//
parent and
child 0 at
child 0 at
parent and
all children at EL 1
EL 2
EL 1
all children at EL 0
You can cause the exception using the following invalid code:
Page 113
parent.BeginEdit();
parent.children[0].BeginEdit();
parent.children[0].CancelEdit();
parent.children[0].CancelEdit();
parent.ApplyEdit();
//
//
//
//
//
Notice the duplicate CancelEdit() call on the child object that puts its EditLevel value to 0.
The highlighted line of code will throw an UndoException , because the parent will attempt to
cascade the ApplyEdit() call to its children through their AcceptChanges() method. At that point,
the children[0] objects new EditLevel value of -1 will be lower than the parents new value of 0.
The behavior of edit levels in n-level undo can be relatively complex, but when you call
a parent object, that object and all its child objects should end up at
exactly the same edit level. If that is not the case, then the code calling BeginEdit(), CancelEdit()
and ApplyEdit() is incorrect.
CancelEdit() or ApplyEdit() on
If you are using Windows Forms data binding, it is the data binding infrastructure that calls these
methods. So if the edit levels get out of sync in that environment, the resulting exception indicates a
flaw in the UI code or in how the UI is using data binding.
If you are using WPF, youll either be allowing CslaDataProvider to make the n-level undo calls
on your behalf, or youll be calling the methods manually. Either way, if the edit levels get out of
sync it is an indication of a flaw in the UI code.
Typically, n-level undo is not used in Web Forms. If you do use it youll need to make the method
calls manually. Again, an exception indicates a flaw in the UI code that calls those methods.
The most common area where youll encounter these exceptions is in Windows Forms with data
binding. The next section discusses the correct way to implement Apply, Save, Cancel and
Close buttons in that environment.
Page 114
The BindingSource control sits between the UI and the business layer. Code in the UI should
interact with the BindingSource, not with the business objects. The BindingSource assumes that it
has sole control over the interaction with the business layer, and it can get very confused if the UI
code directly interacts with the business layer. This is especially true when it comes to n-level undo.
What this means for a Windows Forms developer, is that they should never call BeginEdit(),
CancelEdit() or ApplyEdit() on a business object when that object is connected to a BindingSource
control. For most applications this means the UI code will not call these methods on the business
object at all.
Instead, the UI code should call the comparable methods on the BindingSource control. That way
the control can make appropriate calls to the underlying business object.
If you dislike this behavior, you can tell CSLA .NET to ignore data bindings calls to invoke nlevel undo. Ill discuss this option later in the chapter.
Binding to an Object
When binding a Windows Forms UI to a business object, the binding is done through a
BindingSource control. If you drag and drop the object onto the Windows Forms design surface,
Visual Studio will create and configure the BindingSource control for you. For example, if you drag
and drop a Project business class onto a form, the result is a projectBindingSource control.
You can then write code behind the form, often as the form is created or loaded, to set the
of projectBindingSource:
DataSource property
projectBindingSource.DataSource = Project.NewProject();
This line of code not only provides the business object as a data source for the form, but it causes
the BindingSource control to immediately call BeginEdit() on the object.
Working with a list of objects is slightly more complex:
projectListBindingSource.DataSource = ProjectList.GetList();
When the data source is a list, the concept of currency becomes important. Only one item in the
list is the current item, and this is tracked by the BindingSource control. As soon as the DataSource
property is set, the current items BeginEdit() method is called.
Lets assume that this list is displayed in a grid control. If the user moves to another row in the
grid, the BindingSource automatically calls EndEdit() (which calls ApplyEdit()) on the old child
object, and calls BeginEdit() on the new object.
If the user presses the ESC key while in a grid, the BindingSource calls CancelEdit() on the
current child object, and immediately calls BeginEdit() on that same child object.
What you should observe from this is that the current object (whether a single object, or the
current object in a list) is always in edit mode. That is to say that the current object has always had
BeginEdit() called on it as long as the object is bound to a BindingSource control.
Page 115
projectBindingSource.DataSource = null;
Unfortunately thats not the case. The reason it is more complex, is that setting the DataSource to
null doesnt cause the BindingSource to call either EndEdit() or CancelEdit() on the object. This
means that the current object is left in edit mode. The following helper method addresses this issue:
protected void UnbindBindingSource(BindingSource source, bool apply, bool isRoot)
{
System.ComponentModel.IEditableObject current =
source.Current as System.ComponentModel.IEditableObject;
if (isRoot)
source.DataSource = null;
if (current != null)
if (apply)
current.EndEdit();
else
current.CancelEdit();
}
You can find this method in the WinPart class in the PTWin project.
The source parameter is the BindingSource to unbind. The cancel parameter indicates whether to
accept or cancel edits to the current object, and the isRoot parameter indicates whether this is a root
or child BindingSource control.
The UnbindBindingSource() method finds the current object and calls CancelEdit() or
EndEdit() as appropriate. The result is that the objects edit level is reduced by one as it is unbound
from the BindingSource control.
Only a root BindingSource should be set back to null. If the BindingSource is a child
BindingSource, then its DataSource property must be reset to point to its parent BindingSource
control. This comes into play if your UI uses parent-child displays of data.
In the ProjectEdit form, for example, projectBindingSource is a root control, while
a child control, because it gets its data from the projectBindingSource.
To unbind these controls, use code like the following:
resourcesBindingSource is
The first two lines unbind the objects from the BindingSource controls by calling the
UnbindBindingSource() helper method.
Note: You must unbind the child first, then the parent.
The third line finishes the unbinding process by resetting the child
now empty, parent BindingSource .
BindingSource
to point to the,
The result of this process is that the objects are no longer connected to the BindingSource
controls, and thanks to the code in UnbindBindingSource(), the edit levels of the objects have been
set to appropriate values as well.
Page 116
Then the objects are unbound from the UI, committing any edits made to the current objects:
UnbindBindingSource(this.resourcesBindingSource, true, false);
UnbindBindingSource(this.projectBindingSource, true, true);
Page 117
Once the object has been saved, the new object returned from the Save() call is then bound to the
UI. This is actually a two-step process, the first step being to restore the BindingSource controls to
their original state:
this.projectBindingSource.DataSource = null;
this.resourcesBindingSource.DataSource = this.projectBindingSource;
Finally, event processing is turned back on, and a data refresh is forced so the UI displays the data
from the newly saved object:
this.projectBindingSource.RaiseListChangedEvents = true;
this.resourcesBindingSource.RaiseListChangedEvents = true;
this.projectBindingSource.ResetBindings(false);
this.resourcesBindingSource.ResetBindings(false);
The user is then left on the same form, able to edit the object further if they choose.
Page 118
As you can see, the event processing is disabled and the objects are unbound from the
BindingSource controls. Then the object is saved and the form is closed. So this is the same basic
process as the Apply button, but without the work involved for rebinding the UI.
First, the event processing is turned off so the UI doesnt try to refresh during the rest of the
process:
this.projectBindingSource.RaiseListChangedEvents = false;
this.resourcesBindingSource.RaiseListChangedEvents = false;
Again, it is critically important that the child BindingSource be unbound before the parent or root
unbound, the child BindingSource should be reset to point to
Notice that the second parameter to UnbindBindingSource() method is true, indicating that
CancelEdit() should be called to undo any changes to the objects. At this point the objects have not
only been unbound, but theyve been restored to their original states.
Now the objects can be rebound to the UI:
this.projectBindingSource.DataSource = _project;
Finally, event handling can be turned back on and a data refresh is forced to ensure that the UI
displays the data from the objects:
Using CSLA .NET 3.0
Copyright 2007 Rockford Lhotka
Page 119
// restore events
this.projectBindingSource.RaiseListChangedEvents = true;
this.resourcesBindingSource.RaiseListChangedEvents = true;
// refresh the UI
this.projectBindingSource.ResetBindings(false);
this.resourcesBindingSource.ResetBindings(false);
The result is that the users changes are undone, and the user is left on the same form, ready to
continue to edit the objects data.
The code here is the same as the start of the Cancel button. The event handling is turned off and
the objects are unbound from the BindingSource controls. The UnbindBindingSource() method calls
CancelEdit() on the current objects, ensuring that any changes to those objects are undone.
The result is that all edits to the objects are undone, and the form is closed.
You might consider just directly closing the form:
private void CloseButton()
{
this.Close();
}
If you take this approach, remember that the current business object for each BindingSource
control will still be at edit level 1. If your business object is only used by this one form you can get
away with this simpler technique, because the business object cant be used by any other code.
However, if any other code in your application has a reference to the business object, simply
closing the form will leave the business objects edit levels in a confusing state and the result will be
a bug that is very difficult to track down and fix.
Disabling IEditableObject
Data binding relies on the IEditableObject interface from System.ComponentModel to do much of
the n-level undo work. Some people want to use elements of data binding, but dont want data
binding to automatically call the n-level undo methods.
Page 120
BusinessBase implements IEditableObject, and in CSLA .NET 3.0 it now includes a protected
property called DisableIEditableObject that you can set to true to disable that interface. If this
property is set to true , all method calls through this interface are simply ignored. For example:
private Project()
{
this.DisableIEditableObject = true;
}
If you disable IEditableObject, your users will not get the expected behaviors from in-place
editing in a grid control. Due to this, I strongly recommend against using any grid controls if you
disable IEditableObject in your business objects.
There may be other undesired side-effects from disabling IEditableObject, depending on exactly
how you use data binding. If you think you want to use this property, I strongly urge extensive
testing in your scenarios to ensure that you dont encounter such issues. It is important to realize that
data binding expects the interface to work, and so disabling it means you are entering uncharted and
unsupported territory.
Page 121
Chapter 7:
Miscellaneous
CSLA .NET 3.0 includes a number of smaller, miscellaneous changes and enhancements. The
majority of these changes are due to suggestions made on the CSLA .NET forum at
http://forums.lhotka.net, and were listed on the wish list at www.lhotka.net/cslanet/wishlist.aspx.
SmartDate
The SmartDate type now has a TryParse() method to better match the behavior of DateTime.
The new TryParse() method works like other Microsoft .NET TryParse() methods, allowing
code to attempt to parse a value without an exception if the parse fails. The following code illustrates
how to use this method:
SmartDate dt;
if (SmartDate.TryParse("1/1/2008", ref dt))
// dt is now a valid SmartDate
else
// parse failed - dt does not contain a parsed value
Using TryParse() can be more efficient than calling Parse(), because a parse failure doesnt
result in an exception.
SortedBindingList
The SortedBindingList class now includes a property to expose a reference to the original
underlying list object. It also includes a fix to a bug when sorting null key values.
SourceList
property
The SourceList property returns a value of IList<T> , where T is the type of object contained in
the list. You can cast this value to your original list type, because it really is just a reference to the
original list:
List<string> sourceList = (List<string>)sortedList.SourceList;
The SourceList property is useful, because UI code may not retain an explicit reference to the
source list, but may still need that reference later in the application. This property allows the UI code
to maintain only a reference to the SortedBindingList, and to still be able to get back to that source
list object.
Page 122
FilteredBindingList
The FilteredBindingList class now includes a property to expose a reference to the original
underlying list object. A FilteredBindingList is just a filtered view over an original list object. The
new SourceList property provides access to that underlying list object:
List<string> original = new List<string>();
FilteredBindingList<string> filteredList = new FilteredBindingList<string>(original);
IList<string> source = filteredList.SourceList;
The SourceList property returns a value of IList<T> , where T is the type of object contained in
the list. You can cast this value to your original list type, because it really is just a reference to the
original list:
List<string> sourceList = (List<string>)filteredList.SourceList;
The SourceList property is useful, because UI code may not retain an explicit reference to the
source list, but may still need that reference later in the application. This property allows the UI code
to maintain only a reference to the FilteredBindingList, and to still be able to get back to that
source list object.
NameValueListBase
The NameValueListBase class now includes GetKey() and GetValue() methods to make it easier to
translate keys to values and vice versa:
RoleList _roles = RoleList.GetList();
int key = _roles.GetKey("Project manager");
string text = _roles.GetValue(1);
The GetKey() method accepts a value and returns the key corresponding to the first matching
value in the list. If there are duplicate matches in the list, only the first one is returned.
The GetValue() method accepts a key and returns the value corresponding to that key.
Data Portal
The client-side DataPortal class now includes a ReleaseProxy() method. This method immediately
causes the data portal to de-reference any cached data portal proxy. It also now supports a new
CslaAutoCloneOnUpdate configuration setting that causes the data portal to clone business objects
before performing an Update() operation, so the cloning process doesnt need to be done by hand in
the UI code.
Page 123
ReleaseProxy Method
The client-side DataPortal caches the data portal proxy reference as a performance optimization.
This means that after the first data portal call made by an application, the appropriate data portal
proxy object (based on the config file settings) is loaded and maintained in memory.
Normally this is desirable, since the same proxy can be reused throughout the life of the
application. However, if the applications config settings are changed while the application is
running, the proxy must be released and recreated to use the new settings.
It is important to realize that there is no elegant way to change the applications settings at
runtime. The System.Configuration implementation provided in .NET automatically caches the
settings values in memory, and doesnt reload them even if the underlying config file is changed.
Note: In a web application, when web.config is changed the settings are not reloaded
into the existing AppDomain. Instead, ASP.NET shuts down the old AppDomain and
starts an entirely new one, effectively restarting the application from scratch. Due to
this, you probably dont need to call ReleaseProxy() in a web environment, because
all objects are destroyed and recreated automatically.
However, if you develop your own scheme by which application settings can be changed or
reloaded as the application is running (a topic outside the scope of this book), you can use
ReleaseProxy() to force the client-side DataPortal to reload the data portal proxy object using the
new settings:
DataPortal.ReleaseProxy();
Once this method has been called, the next data portal call will cause a new proxy object to be
created and cached.
The reason for cloning the object is that the original object instance is unchanged during the save,
because the clone is being saved. If theres an exception part way through the save process, the field
(_project in this example) is unchanged. Effectively, an exception means that the object in memory
is reset to the state it was in before the save operation was attempted.
This only matters when you are using a local data portal configuration. If you are using a remote
data portal configuration then the business object is cloned across the network and the save occurs
Page 124
on the application server. But with a local data portal configuration the save occurs on the client
workstation, and it is the actual object instance that is saved.
So the complete best practice is to clone the object only when using a local data portal and not
when using a remote data portal.
Making the UI developer worry about these details is against the philosophy of CSLA .NET. A
primary goal of CSLA .NET is to minimize the code and complexity of the UI.
To help resolve this issue, CSLA .NET now supports a new configuration setting,
This setting is used in the clients app.config or web.config:
CslaAutoCloneOnUpdate.
<appSettings>
<add key="CslaAutoCloneOnUpdate" value="True" />
</appSettings>
This setting defaults to False for backward compatibility. If it is False, the UI developer must
manage the cloning manually.
If the setting is True however, the data portal will automatically clone the object before doing any
update operation, but only if the data portal is configured to be local.
Note: In a future version of CSLA .NET the default for this setting will probably
change to True .
So if the setting is True, then the UI code is simpler:
try
{
_project = _project.Save();
}
catch (Exception ex)
{
// handle exception
}
// use _project field
The object will be cloned only if necessary, and in all cases an exception during the Save()
method will result in _project still pointing to the original, unchanged, object as it was before
Save() was called.
CSLA .NET version 3.0 provides enhancements to version 2.1.4, and important new features for
support of Microsoft .NET 3.0. The primary focus is on supporting both WCF and WPF. However, a
number of other enhancements have been made that support some important scenarios that many
people encounter when using CSLA .NET to build applications.
Page 125
Index
A
AddAuthorizationRules() method..........................111
AddBusinessRules() method..................................107
AddNewCore() method............................................61
AdornedElementPlaceHolder element....................68
AllowNew property..................................................61
ApplicationCommands.New command ...................59
ApplicationCommands.Save command.............59, 60
ApplicationCommands.Undo command............59, 60
Authorizer control .................................46, 69, 70, 72
B
BinaryFormatter ....................................18, 19, 89, 90
Binding expression.................................52, 60, 74, 76
BindingSource control ... 114, 115, 116, 118, 119, 120
BindsDirectToSource property ................................55
BusinessListBase class............................................121
C
CanExecuteMethod() method ...............................111
code activities ..............................................80, 83, 88
ComboBox control.......................................62, 63, 64
CommandTarget property.......................................60
Converter property..................................................76
Csla.Wpf namespace ................. 51, 65, 69, 70, 73, 76
CslaAutoCloneOnUpdate...............................123, 125
CslaDataProvider control.... 46, 50, 51, 52, 54, 55, 56,
58, 59, 60, 61, 62, 64, 71
D
DataChanged event .................................................62
DataContract attribute ....................18, 19, 20, 23, 89
DataPortal class .....................................................123
DataSource property .....................................115, 116
DeferRefresh() method............................................57
dependency event...................................................79
dependency events..................................................79
dependency properties 73, 79, 80, 82, 83, 85, 87, 88,
89
dependency property ... 71, 81, 82, 83, 84, 85, 87, 88,
96, 98
DisableIEditableObject ..........................................121
DisplayMemberPath property .................................63
Path property.....................................................55, 74
ProcessThroughPriority property ..................104, 105
PropertyChanged event...............................72, 73, 75
Page 126
PropertyDescriptor ................................................121
PropertyFriendlyName property ...........................103
R
ReadWriteAuthorization control .......................46, 69
RelativeSource binding............................................74
ReleaseProxy() method .........................................123
RuleArgs class ........................................................103
RuleDescription class.............................................110
S
SelectedValue property...........................................64
SelectedValuePath property....................................64
sequential workflow ..........................................80, 83
Serializable attribute ....................... 12, 18, 19, 20, 89
short-circuiting ......................................101, 104, 105
SmartDate type......................................................122
SortedBindingList class ..........................................122
Source property.......................................................55
SourceList property .......................................122, 123
StopProcessing property .......................................105
StringMinLength method.......................................102
System namespace ............................................53, 54
T
TryParse() method.................................................122
U
UnbindBindingSource() method............116, 119, 120
unloadOnIdle parameter.........................................94
UpdateSourceTrigger property................................55
use case ...................................................................77
V
Validator control............ 46, 64, 65, 66, 67, 68, 69, 70
value converter..................................................75, 76
Value property...................................................63, 64
W
Windows Communication Foundation....................12
Windows Presentation Foundation.........................46
Windows Workflow Foundation........................19, 77
workflow activity ...................................77, 79, 81, 84
workflow instance .......................................90, 93, 95
workflow runtime instance................................90, 94
WorkflowManager object........................................91
X
x
Text property...........................................................55
TextBox control............................................55, 67, 72
Page 127