Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 50 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
50
Dung lượng
751,23 KB
Nội dung
■Note If you’re calling a remote data portal, you must avoid object designs that require IDisposable. Alternatively, you can modify the SimpleDataPortal class to explicitly call Dispose() on your business objects on the server. Business Class Structure As you’ve seen, business objects follow the same sequence of events for creation, retrieval, and updates. Because of this, there’s a structure and a set of features that are common to all of them. Although the structure and features are common, however, the actual code will vary for each busi- ness object. Due to the consistency in structure, however, there’s great value in providing some foundations that make it easier for the business developer to know what needs to be done. Also, there are differences between editable and read-only objects, and between root and child objects. After discussing the features common to all business objects, I’ll create “templates” to illus- trate the structure of each type of business object that you can create based on CSLA .NET. Common Features There are some common features or conventions that should be followed when coding any busi- ness classes that will inherit from the CSLA .NET base classes. These are as follows: • [Serializable()] attribute • Common regions • private default constructor • Criteria class Let’s briefly discuss each of these requirements. The Serializable Attribute All business objects must be unanchored so that they can move across the network as needed. This means that they must be marked as serializable by using the [Serializable()] attribute, as shown here: [Serializable()] public class MyBusinessClass { } This is required for all business classes that inherit from any of the CSLA .NET base classes. It’s also required for any objects that are referenced by business objects. If a business object references an object that isn’t serializable, then you must be sure to mark its field with the [NonSerialized()] attribute to prevent the serialization process from attempting to serialize that object. If you don’t do this, the result will be a runtime exception from the .NET Framework. Common Regions When writing code in VS .NET, the #region directive can be used to place code into collapsible regions. This helps organize the code, and allows you to look only at the code pertaining to a specific type of functionality. CHAPTER 7 ■ USING THE CSLA .NET BASE CLASSES374 6323_c07_final.qxd 2/27/06 1:31 PM Page 374 All business collection classes will have a common set of regions, as follows: • Factory Methods • Data Access And so classes derived from BusinessListBase and ReadOnlyListBase will follow this basic structure: [Serializable()] public class MyCollectionClass : Csla. baseclass<MyCollectionClass, MyChildType> { #region Factory Methods #endregion #region Data Access #endregion } All non-collection (editable and read-only) classes will have the following set of regions: • Business Methods • Validation Rules • Authorization Rules • Factory Methods • Data Access This means that the skeletal structure of a business object, with these regions, is as follows: [Serializable()] public class MyBusinessClass : Csla. baseclass<MyBusinessClass> { #region Business Methods #endregion #region Validation Rules #endregion #region Authorization Rules #endregion #region Factory Methods #endregion #region Data Access #endregion } Command objects that inherit from CommandBase will have the following regions: • Authorization Rules • Client-side Code CHAPTER 7 ■ USING THE CSLA .NET BASE CLASSES 375 6323_c07_final.qxd 2/27/06 1:31 PM Page 375 • Factory Methods • Server-side Code [Serializable()] public class MyCommandClass : Csla.CommandBase { #region Authorization Rules #endregion #region Client-side Code #endregion #region Factory Methods #endregion #region Server-side Code #endregion } And name/value list objects that inherit from NameValueListBase will typically have the following regions: • Factory Methods • Data Access [Serializable()] public class MyListClass : Csla.NameValueListBase<KeyType, ValueType> { #region Factory Methods #endregion #region Data Access #endregion } The Business Methods region will contain the methods that are used by UI code (or other client code) to interact with the business object. This includes any properties that allow retrieval or chang- ing of values in the object, as well as methods that operate on the object’s data to perform business processing. The Validation Rules region will contain the AddBusinessRules() method, and any custom rule methods required by the object. The Authorization Rules region will contain the AddAuthorizationRules() method. It will also contain a standard set of static methods indicating whether the current user is authorized to get, add, save, or delete this type of business object. The Factory Methods region will contain the static factory methods to create or retrieve the object, along with the static delete method (if the object is an editable root object). It will also contain the default constructor for the class, which must be scoped as non- public (i.e., private or protected) to force the use of the factory methods when creating the business object. CHAPTER 7 ■ USING THE CSLA .NET BASE CLASSES376 6323_c07_final.qxd 2/27/06 1:31 PM Page 376 The Data Access region will contain the DataPortal_XYZ methods. It will also contain the Criteria class used to create, retrieve, or delete the object. Your business objects may require other code that doesn’t fit neatly into these regions, and you should feel free to add extra regions if needed. But these regions cover the vast majority of code required by typical business objects, and in most cases they’re all you’ll need. Private Default Constructor All business objects will be implemented to make use of the class-in-charge scheme discussed in Chapter 1. Factory methods are used in lieu of the new keyword, which means that it’s best to pre- vent the use of new, thereby forcing the UI developer to use the factory methods instead. The data portal mechanism, as implemented in Chapter 4, requires business classes to include a default constructor. As I reviewed the create, fetch, update, and delete processes for each type of object earlier in this chapter, each sequence diagram showed how the server-side data portal created an instance of the business object. This is done using a technique that requires a default constructor. By making the default constructor private or protected (and by not creating other public con- structors), you ensure that UI code must use the factory methods to get an instance of any object: // #region Factory Methods private MyBusinessClass() { // require use of factory methods } #endregion // This constructor both prevents the new keyword from being called by code outside this class and provides the data portal with the ability to create the object via reflection. Your classes might also include other constructors, but this one is required for all objects. Criteria Class Root objects must have a Criteria class. Also, any child object that loads its own default values from the database can have an optional Criteria class if needed. Criteria classes can be nested classes within the business class or they can inherit from Csla.CriteriaBase. In most cases, it is simplest to nest the Criteria class within the business class. The Csla.CriteriaBase approach is intended primarily for use with code-generation tools. The Criteria class simply contains the data that’s required to identify the specific object to be retrieved or the default data to be loaded. Since it’s passed by value to the data portal, this class must be marked as [Serializable()]. ■Tip Technically, the Criteria class can have any name, as long as it’s [Serializable()], and is either nested in the business class or inherits from CriteriaBase. Some objects may have more than one criteria class, each one defining a different set of criteria that can be used to retrieve the object. Since this class is no more than a way to ferry data to the data portal, it doesn’t need to be fancy. Typically, it’s implemented with a constructor to make it easier to create and populate the object all at once. For example, here’s a Criteria class that includes an EmployeeID field: CHAPTER 7 ■ USING THE CSLA .NET BASE CLASSES 377 6323_c07_final.qxd 2/27/06 1:31 PM Page 377 // #region Data Access [Serializable()] private class Criteria { private string _employeeId; public string EmployeId { get { return _employeeId; } } public Criteria(string employeeId) { _employeeId = employeeId; } } // An equivalent criteria class can be created by subclassing CriteriaBase (only the changed lines are in bold): [Serializable()] public class MyBusinessClass : Csla. baseclass<MyBusinessClass> { // #region Data Access [Serializable()] protected class Criteria : Csla.CriteriaBase { private string _employeeId; public string EmployeId { get { return _employeeId; } } public Criteria(string employeeId) : base(typeof(MyBusinessClass)) { _employeeId = employeeId; } } // All Criteria classes are constructed using one of these two schemes. Nested criteria classes are scoped as private because they are only needed within the context of the business class. The CriteriaBase class is typically used by code-generation tools, in which case the class is typically protected in scope so that it is available to subclasses as well. ■Note Code generation is outside the scope of this book. For good information on code generation, including the rationale behind CriteriaBase, please refer to Kathleen Dollard’s book, Code Generation in Microsoft .NET (Apress, 2004). Even though the Criteria object is passed through the data portal, it’s passed as a type object, so the DataPortal code doesn’t need access to the object’s code. This is ideal, because it means that UI developers, or other business object developers, won’t see the Criteria class, thus improving the business object’s overall encapsulation. CHAPTER 7 ■ USING THE CSLA .NET BASE CLASSES378 6323_c07_final.qxd 2/27/06 1:31 PM Page 378 The Criteria classes shown thus far include a constructor that accepts the criteria data value. This is done to simplify the code that will go into the static factory methods. Rather than forcing the business developer to create a Criteria object and then load its values, this constructor allows the Criteria object to be created and initialized in a single statement. In many cases, this means that a static factory method will contain just one line of code! For instance: public static Project GetProject(Guid id) { return DataPortal.Fetch<Project>(new Criteria(id)); } Many Criteria classes will contain a single value (as in the examples here), but they can be more complex, providing for more control over the selection of the object to be retrieved. If you have a root collection in which you’re directly retrieving a collection of child objects, the Criteria class may not define a single object, but rather act as a search filter that returns the collection populated with all matching child objects. In other cases, an object may have no criteria data at all. In that case, a Criteria class is still required, but it would be empty: [Serializable()] private class Criteria { } The factory methods can still create an instance of this Criteria class and pass it to the data portal. In this case, the Criteria object doesn’t provide any criteria data beyond the type of the business object to be retrieved. This is typically used when retrieving a root collection object for which you want all the child objects in the database returned at all times. I’ll use this technique to create the ProjectList and ResourceList collection classes in Chapter 8. Class Structures At this point in the chapter, I’ve walked through the life cycle of typical business objects, so you know the sequence of events that will occur as they are created, retrieved, updated, and deleted. I’ve also discussed the code concepts and structures that are common to all business classes. Now let’s dive in and look at the specific coding structure for each type of business class that you can create based on the CSLA .NET framework. These include the following: • Editable root • Editable child • Editable, “switchable” (i.e., root or child) object • Editable root collection • Editable child collection • Read-only object • Read-only collection • Command object • Name/value list For each of these object types, I’ll create the basic starting code that belongs in the class. In a sense, these are the templates from which business classes can be built. CHAPTER 7 ■ USING THE CSLA .NET BASE CLASSES 379 6323_c07_final.qxd 2/27/06 1:31 PM Page 379 ■Tip You can use this code to create either snippets or class templates for use in Visual Studio. The Csla\ Snippets subdirectory in the code download (available from www.apress.com) contains a set of sample snippets you may find valuable. Editable Root Business Objects The most common type of object will be the editable root business object, since any object-oriented system based on CSLA .NET will typically have at least one root business object or root collection. (Examples of this type of object include the Project and Resource objects discussed in Chapter 8.) These objects often contain collections of child objects, as well as their own object-specific data. As well as being common, an editable object that’s also a root object is the most complex object type, so its code template covers all the possible code regions. The basic structure for an editable root object, with example or template code in each region, is as follows: [Serializable()] class EditableRoot : BusinessBase<EditableRoot> { #region Business Methods // TODO: add your own fields, properties and methods private int _id; public int id { get { CanReadProperty(true); return _id; } set { CanWriteProperty(true); if (_id != value) { _id = value; PropertyHasChanged(); } } } protected override object GetIdValue() { return _id; } #endregion #region Validation Rules protected override void AddBusinessRules() { // TODO: add validation rules //ValidationRules.AddRule(null, ""); } CHAPTER 7 ■ USING THE CSLA .NET BASE CLASSES380 6323_c07_final.qxd 2/27/06 1:31 PM Page 380 #endregion #region Authorization Rules protected override void AddAuthorizationRules() { // TODO: add authorization rules //AuthorizationRules.AllowWrite("", ""); } public static bool CanAddObject() { // TODO: customize to check user role //return ApplicationContext.User.IsInRole(""); return true; } public static bool CanGetObject() { // TODO: customize to check user role //return ApplicationContext.User.IsInRole(""); return true; } public static bool CanEditObject() { // TODO: customize to check user role //return ApplicationContext.User.IsInRole(""); return true; } public static bool CanDeleteObject() { // TODO: customize to check user role //return ApplicationContext.User.IsInRole(""); return true; } #endregion #region Factory Methods public static EditableRoot NewEditableRoot() { return DataPortal.Create<EditableRoot>(); } public static EditableRoot GetEditableRoot(int id) { return DataPortal.Create<EditableRoot>(new Criteria(id)); } public static void DeleteEditableRoot(int id) { DataPortal.Delete(new Criteria(id)); } CHAPTER 7 ■ USING THE CSLA .NET BASE CLASSES 381 6323_c07_final.qxd 2/27/06 1:31 PM Page 381 private EditableRoot() { /* Require use of factory methods */ } #endregion #region Data Access [Serializable()] private class Criteria { private int _id; public int Id { get { return _id; } } public Criteria(int id) { _id = id; } } private void DataPortal_Create(Criteria criteria) { // TODO: load default values } private void DataPortal_Fetch(Criteria criteria) { // TODO: load values } protected override void DataPortal_Insert() { // TODO: insert values } protected override void DataPortal_Update() { // TODO: update values } protected override void DataPortal_DeleteSelf() { DataPortal_Delete(new Criteria(_id)); } private void DataPortal_Delete(Criteria criteria) { // TODO: delete values } #endregion } You must define the class, including making it serializable, giving it a name, and having it inherit from BusinessBase. CHAPTER 7 ■ USING THE CSLA .NET BASE CLASSES382 6323_c07_final.qxd 2/27/06 1:31 PM Page 382 The Business Methods region includes all member or instance field declarations, along with any business-specific properties and methods. These properties and methods typically interact with the instance fields, performing calculations and other manipulation of the data based on the business logic. Notice the GetIdValue() method, which is required when inheriting from BusinessBase. This method should return a unique identifying value for the object. The value is directly returned by the default ToString() method in BusinessBase, and is used in the implementation of the Equals() and GetHashCode() methods as well. For details, refer to Chapter 3. The Validation Rules region, at a minimum, overrides the AddBusinessRules() method. In this method, you call ValidationRules.AddRule() to associate rule methods with properties. This region may also include custom rule methods for rules that aren’t already available in Csla.Validation. CommonRules or in your own library of rule methods. The Authorization Rules region overrides the AddAuthorizationRules() method and implements a set of static authorization methods. The AddAuthorizationRules() method should include calls to methods on the AuthorizationRules object: AllowRead(), AllowWrite(), DenyRead(), and DenyWrite(). Each one associates a property with a list of roles that are to be allowed read and write access to that property. The static authorization methods are CanGetObject(), CanAddObject(), CanEditObject(), and CanDeleteObject(). These methods should check the current user’s roles to determine whether the user is in a role that allows or denies the particular operation. The purpose of these methods is so the UI developer can easily determine whether the current user can get, add, update, or delete this type of object. That way, the UI can enable, disable, or hide controls to provide appropriate visual cues to the end user. Since these are static methods, there’s no way to make them part of the BusinessBase class, and they must be directly declared and implemented in each business class. In the Factory Methods region, there are static factory methods to create, retrieve, and delete the object. Of course, these are just examples that must be changed as appropriate. The parameters accepted and Criteria object used must be tailored to match the identifying criteria for your partic- ular business object. Finally, the Data Access region includes the Criteria class and the DataPortal_XYZ methods. These methods must include the code to load defaults, retrieve object data, update object data, and delete object data, as appropriate. In most cases, this will be done through ADO.NET, but this code could just as easily be implemented to read or write to an XML file, call a web service, or use any other data store you can imagine. The [RunLocal()] attribute is for objects that do not load default values from the database when they are created. The use of the [RunLocal()] attribute on DataPortal_Create() is optional, and is used to force the data portal to always run the method locally. When this attribute is used, the DataPortal_Create() method should not access the database, because it may not be running in a physical location where the database is available. The [Transactional()] attributes on the methods that insert, update, or delete data specify that those methods should run within a System.Transactions transactional context. You may opt instead to use the TransactionTypes.EnterpriseServices setting to run within a COM+ distributed transaction, or TransactionTypes.Manual to handle your own transactions using ADO.NET. ■Tip Many organizations use an abstract, metadata-driven data access layer. In environments like this, the business objects don’t use ADO.NET directly. This works fine with CSLA .NET, since the data access code in the DataPortal_XYZ methods can interact with an abstract data access layer just as easily as it can interact with ADO.NET directly. CHAPTER 7 ■ USING THE CSLA .NET BASE CLASSES 383 6323_c07_final.qxd 2/27/06 1:31 PM Page 383 [...]... Root Collection At times, applications need to retrieve a collection of child objects directly To do this, you need to create a root collection object For instance, the application may have a Windows Forms UI consisting of a DataGridView control that displays a collection of Contact objects If the root object is a collection of child Contact objects, the UI developer can simply bind the collection... maximum value Each Project object contains a collection of ProjectResource child objects When a Project object is created, an empty child collection is also created by calling the appropriate factory method on the collection The NewProjectResources() method creates an empty collection, ensuring that child objects can be added as required The result is that the instance fields are declared and initialized... available at www .apress. com ProjectTracker Objects Chapter 6 covered the creation of an object model for the sample project tracking application This object model, shown in Figure 8-1, includes some editable root business objects (Project and Resource), some editable child objects (ProjectResource and ResourceAssignment), some collections of child objects (ProjectResources and ResourceAssignments),... will have some editable child objects, or even grandchild objects Examples of these include the ProjectResource and ResourceAssignment objects In many cases, the child objects are contained within a child collection object, which I’ll discuss later In other cases, the child object might be referenced directly by the parent object Either way, the basic structure of a child object is the same; in some... discussed in Chapter 1) • Data access methods (DataPortal_XYZ, as discussed in Chapter 4) The object model created in Chapter 6 includes editable objects and collections, parent-child collection relationships, read-only lists, a name/value list, and command objects It also makes use of custom authentication, requiring the creation of custom principal and identity objects The custom identity object... public static bool CanGetObject() { // TODO: customize to check user role //return ApplicationContext.User.IsInRole(""); return true; } public static bool CanEditObject() { // TODO: customize to check user role //return ApplicationContext.User.IsInRole(""); return true; } 391 6323 _c0 7_final.qxd 392 2/27/06 1:31 PM Page 392 CHAPTER 7 s USING THE CSLA NET BASE CLASSES public static bool CanDeleteObject()... private constructor, and having a nested Criteria class There are also specific structures or templates for each type of business object, including the following: • Editable root • Editable child • Switchable object • Editable root collection • Editable child collection 6323 _c0 7_final.qxd 2/27/06 1:31 PM Page 403 CHAPTER 7 s USING THE CSLA NET BASE CLASSES • Read-only object • Read-only collection • Command... item.Update(parent); RaiseListChangedEvents = true; } #endregion } As you can see, this code is very similar to a root collection in structure The differences start with the factory methods Since only a parent object can create or fetch an instance of this class, the static factory methods are scoped as internal The static method to create an object simply returns a new collection object As with the EditableChild template,... TODO: implement code to run on client // after server is called } #endregion #region Factory Methods public static bool Execute() { CommandObject cmd = new CommandObject(); cmd.BeforeServer(); cmd = DataPortal.Execute(cmd); cmd.AfterServer(); return cmd.Result; } private CommandObject() { /* require use of factory methods */ } #endregion #region Server-side Code protected override void... public static SwitchableObject GetSwitchableRoot(int id) { return DataPortal.Create( new RootCriteria(id)); } internal static SwitchableObject GetSwitchableChild( SqlDataReader dr) { return new SwitchableObject(dr); } Notice how the NewSwitchable() methods are each designed The public version (used to create a root object) uses the RootCriteria object, while the internal version (called . object include the Project and Resource objects discussed in Chapter 8.) These objects often contain collections of child objects, as well as their own object-specific data. As well as being common,. UI consisting of a DataGridView control that displays a collection of Contact objects. If the root object is a collection of child Contact objects, the UI developer can simply bind the collection. many cases, the child objects are con- tained within a child collection object, which I’ll discuss later. In other cases, the child object might be referenced directly by the parent object. Either