Apress Expert C sharp 2005 (Phần 5) pdf

50 328 0
Apress Expert C sharp 2005 (Phần 5) pdf

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

In either case, it is the DataPortal.Update() call that ultimately triggers the data portal infra- structure to move the object to the application server so it can interact with the database. It is important to notice that the Save() method returns an instance of the business object. Recall that .NET doesn’t actually move objects across the network; rather, it makes copies of the objects. The DataPortal.Update() call causes .NET to copy this object to the server so the copy can update itself into the database. That process could change the state of the object (especially if you are using primary keys assigned by the database or timestamps for concurrency). The resulting object is then copied back to the client and returned as a result of the Save() method. ■Note It is critical that the UI update all its references to use the new object returned by Save(). Failure to do this means that the UI will be displaying and editing old data from the old version of the object. Data Portal Methods As noted earlier, the data portal places certain constraints on business objects. Specifically, it needs to know what methods it can invoke on the server. The data portal will invoke the methods listed in Table 4-10, though not all framework base classes need to implement all the methods. Collectively, I’ll refer to these methods as the DataPortal_XYZ methods. Table 4-10. Business Object Methods Invoked by the Data Portal Method Purpose DataPortal_Create() An editable business object implements this method to load itself with default values required for a new object. DataPortal_Fetch() An editable or read-only business object implements this method to load itself with existing data from the database. DataPortal_Insert() An editable business object implements this method to insert its data into the database. DataPortal_Update() An editable business object implements this method to update its data in the database. DataPortal_DeleteSelf() An editable business object implements this method to delete its data from the database. DataPortal_Delete() An editable business object implements this method to delete its data from the database based on its pri- mary key values only. DataPortal_Execute() A command object (see Chapter 5) implements this method to execute arbitrary code on the application server. DataPortal_OnDataPortalInvoke() This method is invoked on all objects before one of the preceding methods is invoked. DataPortal_OnDataPortalInvokeComplete() This method is invoked on all objects after any of the preceding methods have been invoked. DataPortal_OnDataPortalException() This method is invoked on an object if an exception occurs on the server; in this case, DataPortal_OnDataPortalInvokeComplete would not typically be invoked. CHAPTER 4 ■ DATA ACCESS AND SECURITY174 6323_c04_final.qxd 2/27/06 1:25 PM Page 174 There are several ways the framework might ensure that the appropriate methods are imple- mented on each business object. A formal interface or abstract base class could be used to force business developers to implement each method. Alternatively, a base class could implement virtual methods with default behaviors that could optionally be overridden by a business developer. Finally, it is possible to use reflection to dynamically invoke the methods. Since not all objects will implement all the methods listed in Table 4-10, the idea of an interface or base class with abstract methods isn’t ideal. Another negative side-effect of those approaches is that the methods end up being publicly available, so a UI developer could call them. Of course, that would be problematic, since these methods will be designed to be called only by the data portal infrastructure. Finally, defining the methods at such an abstract level prevents the use of strong typ- ing. Since the data types of the parameters being passed to the server by the client are defined by the business application, there’s no way the framework can anticipate all the types—meaning that the parameters must be passed as type object or other very generic base type. Implementing default virtual methods is an attractive option because it doesn’t force the business developer to implement methods that will never be called. This is the approach I used in CSLA .NET 1.0, and will use in this chapter as well. However, this approach suffers from the same lack of strong typing as the interface or abstract base class approach. Which brings us to the use of reflection. Reflection is much maligned as being slow, and it is in fact slower than making a native method call. However, it offers substantial benefits as well, most notably the ability to implement strongly typed data access methods on the business objects. The purpose behind reflection is to allow dynamic loading of types and then to allow dynamic invocation of methods on those types. And that’s exactly what the data portal does. ■Note The performance cost of reflection is typically negligible within the data portal. This is because the over- head of network communication and data access is so high that any overhead due to reflection usually becomes inconsequential. Remember that the message router pattern implies that CSLA .NET has no reference to any business assembly. Business assemblies are loaded dynamically based on the request coming from the client. Reflection is used to dynamically load the assemblies and to create instances of business objects based on the classes built by the business developer. Using reflection to also invoke the DataPortal_XYZ methods on the objects means that the business developer can write strongly typed versions of those methods. After all, the business devel- oper knows the exact type of the parameters she is sending from the client to the server, and can write data access methods to accept those types. For instance, a DataPortal_Fetch() method may look like this: private void DataPortal_Fetch(MyCriteria criteria) { // load data into object from database } If this method were defined by CSLA .NET, it couldn’t use the MyCriteria type because that type is specific to the business application. Instead, the framework would have to define the method using object as the parameter type, as I did in CSLA .NET 1.0. In that case, a business developer must write code like this: protected override void DataPortal_Fetch(object criteria) { MyCriteria crit = (MyCriteria)criteria; // load data into object from database } CHAPTER 4 ■ DATA ACCESS AND SECURITY 175 6323_c04_final.qxd 2/27/06 1:25 PM Page 175 For the purposes of backward compatibility, the implementation in this chapter will support both the old and new strongly typed models. To support the old model, the base classes in the framework need to include protected virtual methods with default behaviors for the key DataPortal_XYZ methods that a business developer might override. For those methods that aren’t appropriate for a given base class, private methods are imple- mented in the base class that throw an exception. For example, Csla.Core.BusinessBase includes the following code: protected virtual void DataPortal_Create(object criteria) { throw new NotSupportedException(Resources.CreateNotSupportedException); } This provides a base method definition that a business class can override. The Visual Studio 2005 IDE makes it very easy to override methods by simply typing the keyword override into the editor and getting an IntelliSense list of the virtual methods in the base class. Notice that the default implementation throws an exception. If the business developer doesn’t override this method (or provide a strongly typed equivalent), but does implement a factory method that calls DataPortal.Create(), this exception will be the result. ■Tip Notice the use of a string resource rather than a literal string for the exception’s message. This is done to enable localization. Since the text value comes from the resource (resx) file for the project, it will automatically attempt to use the resources for the current culture on the executing thread. The same thing is done for DataPortal_Fetch(), DataPortal_Insert(), DataPortal_Update(), DataPortal_DeleteSelf(), and DataPortal_Delete(). Since a subclass of BusinessBase is an editable object, all the data portal operations are valid. Likewise, the same default methods are implemented in BusinessListBase. Again, it is the base for editable collections, and so all operations are valid. Csla.ReadOnlyBase and Csla.ReadOnlyListBase are used to create read-only objects. As such, only the DataPortal.Fetch() operation is valid. This means that only DataPortal_Fetch() is imple- mented as a protected virtual default. All the other DataPortal_XYZ methods are implemented with private scope, and they all throw exceptions if they are called. This ensures that read-only objects can only be retrieved, but never inserted, updated, or deleted. This completes the enhancements to the business object base classes that are required for the data portal to function. Chapter 5 will implement a couple more base classes, and they too will have comparable features. Now let’s move on and implement the data portal itself, feature by feature. The data portal is designed to provide a set of core features, including • Implementing a channel adapter • Supporting distributed transactional technologies • Implementing a message router • Transferring context and providing location transparency The remainder of the chapter will walk through each functional area in turn, discussing the implemen- tation of the classes supporting the concept. Though the data portal support for custom authentication and impersonation will be covered in this chapter, the Csla.Security.BusinessPrincipalBase class will be covered in Chapter 5. CHAPTER 4 ■ DATA ACCESS AND SECURITY176 6323_c04_final.qxd 2/27/06 1:25 PM Page 176 Channel Adapter The data portal is exposed to the business developer through the Csla.DataPortal class. This class implements a set of static methods to make it as easy as possible for the business developer to create, retrieve, update, or delete objects. All the channel adapter behaviors are hidden behind the Csla.DataPortal class. The Csla.DataPortal class makes use of methods from the Csla.MethodCaller class. Csla.MethodCaller Class In fact, MethodCaller is used by many other classes in the data portal infrastructure, as it wraps the use of reflection in several ways. Csla.DataPortal, Csla.Server.DataPortal, and Csla.Server. SimpleDataPortal in particular all need to retrieve information about methods on the business class, and SimpleDataPortal needs to invoke those methods. The MethodCaller class contains methods to provide all these behaviors. GetMethod Chief among the behaviors is the GetMethod() method. This method is used to locate a specific method on the business class or object. Once the method has been located, other code can retrieve the attributes for that method, or the method can be invoked. This method is somewhat complex. Recall that the data portal will call strongly typed methods based on the type of criteria object provided by the business object’s factory method. This means that GetMethod() must locate the matching method on the business class—not only by method name, but by checking the parameter types as well. The process flow is illustrated in Figure 4-8. CHAPTER 4 ■ DATA ACCESS AND SECURITY 177 6323_c04_final.qxd 2/27/06 1:25 PM Page 177 Here’s the method in its entirety: public static MethodInfo GetMethod( Type objectType, string method, params object[] parameters) { BindingFlags flags = BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; MethodInfo result = null; // try to find a strongly typed match if (parameters.Length > 0) { // put all param types into a list of Type bool paramsAllNothing = true; List<Type> types = new List<Type>(); foreach (object item in parameters) CHAPTER 4 ■ DATA ACCESS AND SECURITY178 Figure 4-8. Process flow implemented by GetMethod() 6323_c04_final.qxd 2/27/06 1:25 PM Page 178 { if (item == null) types.Add(typeof(object)); else { types.Add(item.GetType()); paramsAllNothing = false; } } if (paramsAllNothing) { // all params are null so we have // no type info to go on BindingFlags oneLevelFlags = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; Type[] typesArray = types.ToArray(); // walk up the inheritance hierarchy looking // for a method with the right number of // parameters Type currentType = objectType; do { MethodInfo info = currentType.GetMethod(method, oneLevelFlags); if (info != null) { if (info.GetParameters().Length == parameters.Length) { // got a match so use it result = info; break; } } currentType = currentType.BaseType; } while (currentType != null); } else { // at least one param has a real value // so search for a strongly typed match result = objectType.GetMethod(method, flags, null, CallingConventions.Any, types.ToArray(), null); } } // no strongly typed match found, get default if (result == null) { try { result = objectType.GetMethod(method, flags); } catch (AmbiguousMatchException) CHAPTER 4 ■ DATA ACCESS AND SECURITY 179 6323_c04_final.qxd 2/27/06 1:25 PM Page 179 { MethodInfo[] methods = objectType.GetMethods(); foreach (MethodInfo m in methods) if (m.Name == method && m.GetParameters().Length == parameters.Length) { result = m; break; } if (result == null) throw; } } return result; } Let’s walk through the key parts of the process. First, assuming parameters were passed in for the method, the parameter types are put into a list: // put all param types into a list of Type bool paramsAllNothing = true; List<Type> types = new List<Type>(); foreach (object item in parameters) { if (item == null) types.Add(typeof(object)); else { types.Add(item.GetType()); paramsAllNothing = false; } } The reason for doing this is twofold. First, if there is at least one parameter that is not null, then this list is needed for a call to reflection to get the matching method. Second, the loop deter- mines whether there actually are any non- null parameters. If not, the search for a matching method can only by done by parameter count, not data type. ■Note In the general case, this could be problematic, because a null value along with some non-null values could result in an ambiguous match. For the purposes of the data portal, however, this is not an issue because the parameters involved are very clearly defined. If all the parameter values are null, then the search is done based on parameter count rather than parameter type. This is complicated, however, by the fact that preference is given to methods lower on the inheritance hierarchy. In other words, if both a base class and subclass have methods of the same name and number of parameters, preference is given to the subclass. To accomplish this, the code loops through the specific class types, starting with the outermost class and working up through the inheritance chain—ultimately to System.Object: Type currentType = objectType; do { MethodInfo info = currentType.GetMethod(method, oneLevelFlags); if (info != null) { if (info.GetParameters().Length == parameters.Length) CHAPTER 4 ■ DATA ACCESS AND SECURITY180 6323_c04_final.qxd 2/27/06 1:25 PM Page 180 { // got a match so use it result = info; break; } } currentType = currentType.BaseType; } while (currentType != null); As soon as a match is found, the loop is terminated and the result is used. The other case occurs when at least one parameter is not null. In such a case, reflection can be used in a simpler manner to locate a method with matching parameter types: // at least one param has a real value // so search for a strongly typed match result = objectType.GetMethod(method, flags, null, CallingConventions.Any, types.ToArray(), null); One way or the other, the result is typically a MethodInfo object for the correct method. How- ever, it is possible that no match was found. In that case, as in the case in which no parameters were passed at all, a search is done based purely on the method’s name: result = objectType.GetMethod(method, flags); Finally, it is possible for this check to find multiple matches—an ambiguous result. When that happens, an exception is thrown. In such a case, as a last-ditch effort, all methods on the business class are scanned to see if there’s a match based on method name and parameter count: MethodInfo[] methods = objectType.GetMethods(); foreach (MethodInfo m in methods) if (m.Name == method && m.GetParameters().Length == parameters.Length) { result = m; break; } If even that fails, then the AmbiguousMatchException is thrown so the business developer knows that something is seriously wrong with the data access methods in their business class. The end result of GetMethod() is a MethodInfo object describing the method on the business class. This MethodInfo object is used by other methods in MethodCaller and in other data portal code. CallMethod The Csla.Server.SimpleDataPortal object (discussed later in the chapter) will ultimately invoke methods on business objects based on the MethodInfo object returned from GetMethod(). To sup- port this, MethodCaller implements two different CallMethod() overloads: public static object CallMethod( object obj, string method, params object[] parameters) { MethodInfo info = GetMethod(obj.GetType(), method, parameters); if (info == null) throw new NotImplementedException( method + " " + Resources.MethodNotImplemented); return CallMethod(obj, info, parameters); } CHAPTER 4 ■ DATA ACCESS AND SECURITY 181 6323_c04_final.qxd 2/27/06 1:25 PM Page 181 public static object CallMethod( object obj, MethodInfo info, params object[] parameters) { // call a private method on the object object result; try { result = info.Invoke(obj, parameters); } catch (Exception e) { throw new Csla.Server.CallMethodException( info.Name + " " + Resources.MethodCallFailed, e.InnerException); } return result; } The first version accepts the method name as a string value, while the second accepts a MethodInfo object. In the first case, GetMethod() is called to retrieve a matching MethodInfo object. If one isn’t found, an exception is thrown; otherwise, the second version of CallMethod() is invoked. The second version of CallMethod() actually invokes the method by using the MethodInfo object. The interesting bit here is the way exceptions are handled. Since reflection is being used to invoke the business method, any exceptions that occur in the business code end up being wrapped within a reflection exception. To business developers, the exception from reflection isn’t very useful. They want the actual exception that occurred within their business method. To resolve this, when an exception is thrown as the business method is invoked, it is caught, and the InnerException of the reflection exception is wrapped within a new Csla.Server.CallMethodException. Effectively, the reflection exception is stripped off and discarded, leaving only the original excep- tion thrown within the business code. That exception is then wrapped within a CSLA .NET exception so the name of the failed business method can be returned as well. CallMethodIfImplemented The CallMethodIfImplemented() method is similar to the CallMethod() methods mentioned previ- ously, but it doesn’t throw an exception if the method doesn’t exist on the business class. public static object CallMethodIfImplemented( object obj, string method, params object[] parameters) { MethodInfo info = GetMethod(obj.GetType(), method, parameters); if (info != null) return CallMethod(obj, info, parameters); else return null; } This is the same basic code as the first CallMethod() implementation, except that it doesn’t throw an exception if the method isn’t found. Instead, it simply returns a null value. CallMethodIfImplemented() is used by Csla.Server.SimpleDataPortal to invoke optional methods on the business class—methods that should be invoked if implemented by the business developer, but which shouldn’t cause failure if they aren’t implemented at all. An example is DataPortal_OnData➥ PortalInvoke(), which is purely optional, but should be called if it has been implemented by the busi- ness developer. CHAPTER 4 ■ DATA ACCESS AND SECURITY182 6323_c04_final.qxd 2/27/06 1:25 PM Page 182 GetObjectType The final method in MethodCaller is used by both Csla.DataPortal and Csla.Server.DataPortal to determine the type of business object involved in the data portal request. It uses the criteria object supplied by the factory method in the business class to find the type of the business object itself. This method supports the two options discussed earlier: where the criteria class is nested within the business class and where the criteria object inherits from Csla.CriteriaBase: public static Type GetObjectType(object criteria) { if (criteria.GetType().IsSubclassOf(typeof(CriteriaBase))) { // get the type of the actual business object // from CriteriaBase return ((CriteriaBase)criteria).ObjectType; } else { // get the type of the actual business object // based on the nested class scheme in the book return criteria.GetType().DeclaringType; } } If the criteria object is a subclass of Csla.CriteriaBase, then the code simply casts the object to type CriteriaBase and retrieves the business object type by calling the ObjectType property. With a nested criteria class, the code gets the type of the criteria object and then returns the DeclaringType value from the Type object. The DeclaringType property returns the type of the class within which the criteria class is nested. Csla.Server.CallMethodException The MethodCaller class throws a custom Csla.Server.CallMethodException in the case that an exception occurs while calling a method on the business object. The purpose behind throwing this exception is to supply the name of the business method that generated the exception, and to pro- vide the original exception details as an InnerException. More importantly, it preserves the stack trace from the original exception. The original stack trace shows the details about where the exception occurred, and is very useful for debugging. With- out a bit of extra work, this information is lost as the method call comes back through reflection. Remember that MethodCaller.CallMethod() uses reflection to invoke the business method. When an exception occurs in the business method, a reflection exception is thrown—with the origi- nal business exception nested inside. CallMethod() strips off the reflection exception and provides the original business exception as a parameter during the creation of the CallMethodException object. In the constructor of CallMethodException, the stack trace details from that original exception are stored for later use: public CallMethodException(string message, Exception ex) : base(message, ex) { _innerStackTrace = ex.StackTrace; } Then in the StackTrace property of CallMethodException, the stack trace for the CallMethod➥ Exception itself is combined with the stack trace from the original exception: CHAPTER 4 ■ DATA ACCESS AND SECURITY 183 6323_c04_final.qxd 2/27/06 1:25 PM Page 183 [...]... object criteria, DataPortalContext context) { return _portal.Create(objectType, criteria, context); } 6323 _c0 4_final.qxd 2/27/06 1:25 PM Page 195 CHAPTER 4 s DATA ACCESS AND SECURITY public DataPortalResult Fetch(object criteria, DataPortalContext context) { return _portal.Fetch(criteria, context); } public DataPortalResult Update(object obj, DataPortalContext context) { return _portal.Update(obj, context);... network communication were involved An exception could occur while calling the server The most likely cause of such an exception is that an exception occurred in the business logic running on the server, though exceptions can also occur due to network issues or similar problems When an exception does occur in business code on the server, it will be reflected here as a Csla.Server.DataPortalException, which... The Activator.GetObject() call doesn’t actually create an instance of a server-side object It merely creates an instance of a client-side proxy for the server object The server configuration controls how server-side objects are created, and in this case, one will be created for each method call from a client The only other interesting bit of code is the static constructor, in which NET Remoting is configured... MethodCaller.GetObjectType() method and the determination of the type of business object to be created To make the criteria object optional, Create() takes a slightly different approach The public methods look like this: public static T Create(object criteria) { return (T)Create(typeof(T), criteria); } public static T Create() { return (T)Create(typeof(T), null); } public static object Create(object... however, because the COM references are used To make this work nicely, EnterpriseServicesProxy is actually a base class that a client application can use to easily create an Enterprise Services client proxy Similarly, the corresponding server-side EnterpriseServicesPortal class is a base class the application can use to easily create a server-side object to host in COM+ This way, the client application can... Server.Hosts.EnterpriseServicesPortal GetServerObject(); Each of the data methods simply delegates the call to this server-side object: public virtual Server.DataPortalResult Fetch( object criteria, Server.DataPortalContext context) { Server.Hosts.EnterpriseServicesPortal svc = GetServerObject(); try { return svc.Fetch(criteria, context); } finally { if (svc != null) svc.Dispose(); } } Notice the try…catch block, which ensures... 6323 _c0 4_final.qxd 2/27/06 1:25 PM Page 189 CHAPTER 4 s DATA ACCESS AND SECURITY Fetch There are two Fetch() methods, a generic one to provide a strongly typed result and the actual implementation: public static T Fetch(object criteria) { return (T)Fetch(criteria); } public static object Fetch(object criteria) { Server.DataPortalResult result; MethodInfo method = MethodCaller.GetMethod( MethodCaller.GetObjectType(criteria),... 1:25 PM Page 194 CHAPTER 4 s DATA ACCESS AND SECURITY The IDataPortalServer interface defines the methods common across the entire process: public interface IDataPortalServer { DataPortalResult Create(Type objectType, object criteria, DataPortalContext context); DataPortalResult Fetch(object criteria, DataPortalContext context); DataPortalResult Update(object obj, DataPortalContext context); DataPortalResult... objects: 6323 _c0 4_final.qxd 2/27/06 1:25 PM Page 207 CHAPTER 4 s DATA ACCESS AND SECURITY public Server.DataPortalResult Fetch( object criteria, Server.DataPortalContext context) { object result; Server.Hosts.WebServicePortal.FetchRequest request = new Server.Hosts.WebServicePortal.FetchRequest(); request.Criteria = criteria; request.Context = context; using (WebServiceHost.WebServicePortal wsvc =... (Server.IDataPortalServer)Activator.GetObject( typeof(Server.Hosts.RemotingPortal), ApplicationContext.DataPortalUrl.ToString()); return _portal; } } public Server.DataPortalResult Create( Type objectType, object criteria, Server.DataPortalContext context) { return Portal.Create(objectType, criteria, context); } public Server.DataPortalResult Fetch( object criteria, Server.DataPortalContext context) { return Portal.Fetch(criteria, . the class within which the criteria class is nested. Csla.Server.CallMethodException The MethodCaller class throws a custom Csla.Server.CallMethodException in the case that an exception occurs. ex) { _innerStackTrace = ex.StackTrace; } Then in the StackTrace property of CallMethodException, the stack trace for the CallMethod➥ Exception itself is combined with the stack trace from the original exception: CHAPTER. authentication and impersonation will be covered in this chapter, the Csla.Security.BusinessPrincipalBase class will be covered in Chapter 5. CHAPTER 4 ■ DATA ACCESS AND SECURITY176 6323 _c0 4_final.qxd

Ngày đăng: 06/07/2014, 00:20

Từ khóa liên quan

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan