|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Introduction
The .Net Services (classes derived from the [assembly: AssemblyKeyFileAttribute(@"..\..\ComponentQC.snk")] [assembly: ApplicationName("QCLogFile")] [assembly: ApplicationActivation(ActivationOption.Server)] [assembly: ApplicationQueuing(Enabled=true, QueueListenerEnabled=true)] [assembly: ApplicationAccessControl(Value = false, Authentication = AuthenticationOption.None)] namespace ComponentQC { [Guid("BD294B3E-E9AF-47f9-9E12-1983DD42E1BB")] [InterfaceQueuing] public interface IQLogFile { void Write(string str); } [Guid("254E1CE3-B0EC-4aaa-A0D9-34733ECB68F3")] [Transaction] [ObjectPooling(Enabled=true, MinPoolSize=2, MaxPoolSize=5)] [EventTrackingEnabled] public class QLogFile : ServicedComponent, IQLogFile { public QLogFile() {} public void Write(string str) {} public override bool CanBePooled() { return true; } public override void Activate() {} public override void Deactivate() {} } }
Everything is going straightforward until the situation when your design pattern needs to
use different class inheritance (for instances: Concept and Design.The concept of this solution is based on the following steps:
The first step is a design issue related with your application. For instance, the previously example using the COM+ interface contracts might look like the following: [assembly: AssemblyKeyFileAttribute(@"..\..\ComponentQC.snk")] [assembly: ApplicationName("QCLogFile")] [assembly: ApplicationActivation(ActivationOption.Server)] [assembly: ApplicationQueuing(Enabled=true, QueueListenerEnabled=true)] [assembly: ApplicationAccessControl(Value = false, Authentication = AuthenticationOption.None)] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("51372aec-cae7-11cf-be81-00aa00a2fa25")] public interface IObjectControl { void Activate(); void Deactivate(); bool CanBePooled(); } namespace ComponentQC { [Guid("BD294B3E-E9AF-47f9-9E12-1983DD42E1BB")] [InterfaceQueuing] public interface IQLogFile { void Write(string str); } [Guid("254E1CE3-B0EC-4aaa-A0D9-34733ECB68F3")] [Transaction] [ObjectPooling(Enabled=true, MinPoolSize=2, MaxPoolSize=5)] [EventTrackingEnabled] public class QLogFile : IQLogFile, IObjectControl { public QLogFile() {} public void Write(string str) {} public override bool CanBePooled() { return true; } public override void Activate() {} public override void Deactivate() {} } }
The changes are minor. The class instead of deriving from the rem unregister gacutil -u AttributeTester regasm /u AttributeTester.dll /tlb:AttributeTester.tlb rem register/update regasm AttributeTester.dll /tlb:AttributeTester.tlb regasm2 AttributeTester.dll gacutil -i AttributeTester.dll
Note that is very important to unregister your assembly and typelib. The otherwise your
system will accumulate all versions of those registry (based on the GUID). The Also, it's a good practice to incorporate your post-build sequences into the build script and automate all process, it's important especially when you are dealing with COM+ Services. Regasm2 Internals.The regasm2 is a command line program with responsibilities to install and configure your application and its components into the COM+ Catalog. The program requires in prior access to the assembly and typelib files. Note that both files have to be located on the same path. The main process of the regasm2 is shown in the following snippet code: static int Main(string[] args) { int retval = 0; try { // display Logo Console.WriteLine(); Console.WriteLine("The .Net/COM+ Installation Tool, Version 1.0"); Console.WriteLine("(C) Copyright 2001, Roman Kiss, rkiss@pathcom.com"); // validate the entry argument if(args.Length == 0 || File.Exists(args[0]) == false) { throw new Exception("Missing the Assembly file."); } // Load the assembly Assembly assembly = Assembly.LoadFrom(args[0]); Console.WriteLine("Assembly file: ' {0} '", assembly.FullName); // Typelib file string tlbName = Path.ChangeExtension(args[0], "tlb"); if(File.Exists(tlbName) == false) { throw new Exception("Missing the typelib file."); } Console.WriteLine("Typelib file: ' {0} ' ", tlbName); Console.WriteLine(); // Install to the COM+ Catalog Console.WriteLine("Scanning Assembly for attributes ..."); Install(assembly, tlbName); // finish Console.WriteLine("Done"); retval = 1; } catch (Exception ex) { Console.WriteLine("ErrorMessage: {0}", ex.Message); Console.WriteLine("StackTrace: {0}", ex.StackTrace); } return retval; }
The key task of the main function is invoking the static void Install(Assembly ass, string typelib) { ... COMAdminCatalog cat = new COMAdminCatalog(); ICatalogCollection colA = cat.GetCollection("Applications") as ICatalogCollection; colA.Populate(); ICatalogObject appl = colA.Add() as ICatalogObject; ////////////////////////////////////////////////////////////////////////////// // Application. This process step will either create or update an Application // entry in the COM+ Catalog ////////////////////////////////////////////////////////////////////////////// foreach(object attr in ass.GetCustomAttributes(true) ) { if(attr is ApplicationAccessControlAttribute) { ApplicationAccessControlAttribute a = attr as ApplicationAccessControlAttribute; appl.set_Value("ApplicationAccessChecksEnabled", a.Value); appl.set_Value("AccessChecksLevel", a.AccessChecksLevel); if(Enum.IsDefined(typeof(AuthenticationOption), a.Authentication)) { appl.set_Value("Authentication", a.Authentication); } if(Enum.IsDefined(typeof(ImpersonationLevelOption), a.ImpersonationLevel)) { // It seems to me a MS bug, and this is a workaround if(a.ImpersonationLevel == ImpersonationLevelOption.Default) { appl.set_Value("ImpersonationLevel", ImpersonationLevelOption.Impersonate); } else { appl.set_Value("ImpersonationLevel", a.ImpersonationLevel); } } } // ... else if(attr is ApplicationCrmEnabledAttribute) { ApplicationCrmEnabledAttribute a = attr as ApplicationCrmEnabledAttribute; appl.set_Value("CRMEnabled", a.Value); } else AssemblyAttrExtension(attr, appl); // extension for a custom assembly:attributes } // run-time adjustment: if(bIsApplNameExist == false) { // use the short name of the assembly for application name appl.set_Value("Name", ass.FullName.Split(new char[] {','})[0]); } // now we can store it into the COM+ catalog colA.SaveChanges(); // save Application changes in the COM+ catalog Console.WriteLine(string.Format(" Step {0}. The Application '{1}' has been installed.", step++, appl.Name)); // ... } Before actually scanning process we need to ask the COM+ catalog for a particular object. In our case (see the about snippet code) we created new one: ICatalogObject appl = colA.Add() as ICatalogObject;This application object will automatically initiate its properties by default values. Now, during the scanning process for each found attribute these properties are going to be overwritten by your configuration values, for instance: appl.set_Value("Authentication", a.Authentication); The scanning of the assembly attributes is finishing saving all changes into the catalog: colA.SaveChanges(); The scanner has implemented all assembly and class attributes which are part of the .Net/COM+ Services. What about custom attributes? The scanner loop ends up in calling the chain function, AssemblyAttrExtension(attr, appl); which it will allow to handle your custom attributes in the loosely coupled design pattern (using the System.Reflection) without adding their references. This is your place to put a business logic based on your custom attributes. Two custom assembly attributes are shown in the following snippet code: static void AssemblyAttrExtension(object assemblyAttr, ICatalogObject appl) { Type t = assemblyAttr.GetType(); if(t.Name.ToString() == "ApplicationAdvancedAttribute") { MethodInfo mi = t.GetMethod("get_RunForever"); appl.set_Value("RunForever", mi.Invoke(assemblyAttr, null)); mi = t.GetMethod("get_CreatedBy"); appl.set_Value("CreatedBy", mi.Invoke(assemblyAttr, null)); mi = t.GetMethod("get_Deleteable"); appl.set_Value("Deleteable", mi.Invoke(assemblyAttr, null)); mi = t.GetMethod("get_ShutdownAfter"); appl.set_Value("ShutdownAfter", mi.Invoke(assemblyAttr, null).ToString()); } else if(t.Name.ToString() == "CustomDescriptionAttribute") { MethodInfo mi = t.GetMethod("get_Description"); appl.set_Value("Description", mi.Invoke(assemblyAttr, null)); } } Note that I created my CustomDescriptionAttribute attribute as a work around to the DescriptionAttribute, where are not accessors such as getter and setter for private field _desc. Custom Attributes.
Any class derived from the [assembly: ApplicationAdvanced(RunForever = true, ShutdownAfter = 60, CreatedBy = "Roman Kiss")] [assembly: CustomDescription("COM+ and C# Application Test")] Implementation file: using System; using System.Runtime.InteropServices; namespace LibOfCustomAttributes { [AttributeUsage(AttributeTargets.Assembly)] public class ApplicationAdvancedAttribute : Attribute { private string _CreatedBy = ""; private bool _Deleteable = true; private bool _RunForever = false; private long _ShutdownAfter = 3; // 3 minutes // public string CreatedBy { get { return _CreatedBy; } set { _CreatedBy = value;} } public bool Deleteable { get { return _Deleteable; } set { _Deleteable = value;} } public bool RunForever { get { return _RunForever; } set { _RunForever = value;} } public long ShutdownAfter { get { return _ShutdownAfter; } set { _ShutdownAfter = value;} } } [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class)] public class CustomDescriptionAttribute : Attribute { private string _Description = ""; // public CustomDescriptionAttribute(string desc) { Description = desc; } public string Description { get { return _Description; } set { _Description = value;}} } } Tips.Using the .Net Services in your design the following tips are useful:
Test.The Regasm2 program can be tested using an empty application boilerplate such as AttributeTester and LibOfCustomAttributes projects: using System; using System.Diagnostics; using System.EnterpriseServices; using System.EnterpriseServices.CompensatingResourceManager; using System.Runtime.InteropServices; // using RKiss.COMPlusInterfaces ; // using LibOfCustomAttributes; [assembly: ApplicationID("E970F84A-6B45-49c3-B3F7-3C210BFA6E9B")] [assembly: ApplicationName("AttributeTester")] [assembly: ApplicationActivation(ActivationOption.Server)] [assembly: Description("COM+ and C# Application Test")] [assembly: ApplicationAccessControl(Value = false, Authentication = AuthenticationOption.None)] [assembly: ApplicationQueuing(Enabled=false, QueueListenerEnabled=false)] [assembly: SecurityRole("Roman", true, Description = "Security role test")] [assembly: SecurityRole("John", true, Description = "Security role test")] [assembly: ApplicationCrmEnabled] [assembly: ApplicationAdvanced(RunForever = true, ShutdownAfter = 60, CreatedBy = "Roman Kiss")] // custom attribute [assembly: CustomDescription("COM+ and C# Application Test")] namespace RKiss.COMPlusInterfaces { #region Interfaces: IObjectControl, IObjectConstruct, IObjectConstructString [InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("51372aec-cae7-11cf-be81-00aa00a2fa25")] public interface IObjectControl { void Activate(); void Deactivate(); bool CanBePooled(); } [InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("41C4F8B3-7439-11D2-98CB-00C04F8EE1C4")] public interface IObjectConstruct { void Construct([In, MarshalAs(UnmanagedType.IDispatch)] object pCtorObj); } [InterfaceType(ComInterfaceType.InterfaceIsIDispatch), Guid("41C4F8B2-7439-11D2-98CB-00C04F8EE1C4")] public interface IObjectConstructString { string ConstructString { get; } } #endregion } namespace AttributeTesterAB { [Guid("F8D303FC-A118-4925-A6E5-39D5BB4C7774")] [Description("Test Component A")] [ObjectPooling(Enabled=true, MinPoolSize=2,MaxPoolSize=10)] [Transaction(TransactionOption.Disabled)] [ConstructionEnabled(Default="server=ATZ-ROMAN;uid=sa;pwd=;database=Logger")] [EventTrackingEnabled] [LoadBalancingSupported] [Synchronization(SynchronizationOption.RequiresNew)] [ExceptionClass("MyQCExceptionClass")] [CustomDescription("Test Component A")] public class TesterA : IObjectControl, IObjectConstruct { public TesterA() { Trace.WriteLine(string.Format("[{0}] TesterA.TesterA", this.GetHashCode())); } public string Echo(string msg) { return string.Format("TesterA.Echo([{0}]", msg); } //IObjectConstruct public void Construct(object pCtorObj) { string constr = (pCtorObj as IObjectConstructString).ConstructString; Trace.WriteLine(string.Format("[{0}] TesterA.Construct = {1}", this.GetHashCode(), constr)); } //IObjectControl public void Activate() { Trace.WriteLine(string.Format("[{0}] TesterA.Activate", this.GetHashCode())); } public void Deactivate() { Trace.WriteLine(string.Format("[{0}] TesterA.Deactive", this.GetHashCode())); } public bool CanBePooled() { Trace.WriteLine(string.Format("[{0}] TesterA.CanBePooled", this.GetHashCode())); return true; } } [Guid("FF1DBFA8-F1A6-408c-97A1-7E3AA1EFEBA1")] [Description("Test Component B")] [ObjectPooling(Enabled=false, MinPoolSize=5,MaxPoolSize=10)] [Transaction(TransactionOption.Supported)] [ConstructionEnabled(true, Default="server=ATZ-ROMAN;uid=sa;pwd=;database=Logger")] [EventTrackingEnabled] [Synchronization] [CustomDescription("Test Component A")] public class TesterB : IObjectControl, IObjectConstruct { public TesterB() { Trace.WriteLine(string.Format("[{0}] TesterB.TesterB", this.GetHashCode())); } public string Echo(string msg) { return string.Format("TesterB.Echo([{0}]", msg); } //IObjectConstruct public void Construct(object pCtorObj) { string constr = (pCtorObj as IObjectConstructString).ConstructString; Trace.WriteLine(string.Format("[{0}] TesterB.Construct = {1}", this.GetHashCode(), constr)) } //IObjectControl public void Activate() { Trace.WriteLine(string.Format("[{0}] TesterB.Activate", this.GetHashCode())); } public void Deactivate() { Trace.WriteLine(string.Format("[{0}] TesterB.Deactive", this.GetHashCode())); } public bool CanBePooled() { Trace.WriteLine(string.Format("[{0}] TesterB.CanBePooled", this.GetHashCode())); return true; } } }
Instructions:
Conclusion.The assembly image is a very powerful source of the information related with all application abstract definitions, configuration, etc. In this article, we've seen how easy can be retrieved and used during the deploying time. Beside that you have a small tool, which can be useful as a part of your development utility library for Applications driven by .Net Services.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||