|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
IntroductionIn this article I am going to take you through a scenario where you can use dynamically created assemblies in your software solutions. I will be focusing more on System.CodeDom namespace, but towards the end I will discuss the use of System.Reflection.Emit namespace that helps you to create dynamic assemblies. ScenarioConsider business scenarios were due to market trends and other effects you decide to give your clients a special offer. This can change according to the time of the year, type of the item etc. In addition you will decide to give discounts for certain selected item category according to the quantity or the total price. Furthermore you want to change the validation rules of your application. For instance you want to restrict the quantity of some item category that can be bought remotely. Imagine that your system architecture is where your clients logging in to your server using windows application, using a socket or Remoting facilities provided in .NET framework. When you look at this problem you will see that you cannot hard code these requirements in to your client application. It’s purely because parameters and equations are decided by the server itself according to the market trends and to maximize the profit margins. So your client application must have the capability to be configured by the server. In addition in this type of architecture you have to reduce the network communication and utilization of bandwidth unnecessarily. To tackle this problem you can use the capabilities of the System.CodeDom namespace. Main feature provided by this technique is to generate source code at runtime in multiple languages. Then System.CodeDom.Compiler will give you freedom to compile the source code you dynamically created with relevant language compiler in to a class library or an executable. In my demo application I am going to dynamically create an assembly and use it for discount calculations, special offer calculations and for some validations. Here I am going to consider a situation of a book shop, Where there are set of product categories such as Books, Magazines, Gifts and Stationary. There could be sub product categories as well. Below I have mentioned some of the rules and offers I am going to implement in this application. Book category - “B”
Gift Category – “G”
· There is 2.5% discount if the total value exceeds 100$ Magazine category - “M” and sub category Technical – “T” · If order value is over 750, there is a choice to select magazine from set of magazine populated on the order window. Stationary category - “S” · If the order value is over 500$ there is choice to select free gift from set of gifts populated on the order window. As I clearly mentioned these values and the parameters will be changed according to the market trends and profit margins to be archived. ImplementationEven though I have used a Rules.xml xml document to store my dynamic rules sent from the sever, in the real circumstances these rules are sent from the server as strings and it will change according to the requirements. If you inspect the xml, you can see that in the body element what you have is C# code. Similarly ReturnType element is C# type. Imagine that Rule.xml is sent from the server or similar set of data is sent from the server. Then the next step is to include this in a C# class and create an assembly on the fly. First let’s concentrate on how to create a class using the code segments sent from the server. CodeCompileUnit is the main object that maintains or modals the structure of the source we are going to write also known as the CodeDOM graph of a source. Using this object we can add specific namespaces we want, to import the referenced namespaces used in the source and define the types such as classes that will be declared in side that program structure. There are set of classes in the System.Codedom namespace that is referred by this CodeCompileUnit class. Please go through the following code segment. // Declare a new namespace CodeNamespace codeNamespace = new CodeNamespace("DynamicAssemblyDemo.DynamicAssembly"); // Add the new namespace to the compile unit. codeCompileUnit.Namespaces.Add(codeNamespace); // Add the new namespace import for the System namespace. codeNamespace.Imports.Add(new CodeNamespaceImport("System")); // Declare a new type called BookShopRuleAssembly. bookShopRuleClass = new CodeTypeDeclaration("BookShopRuleClass"); // Add the new type to the namespace type collection. codeNamespace.Types.Add(bookShopRuleClass); In the above code segment you can see I have used CodeNamespace class to declare the namespace of the dynamic class I am going to create (DynamicAssemblyDemo.DynamicAssembly). Then I have imported System namespace which will be used in this class. Other important class I have used here is CodeTypeDeclaration which represents a type declaration for a class, structure etc. Name of the dynamic class is BookShopRuleClass. In a CodeTypeDeclaration object you can find set of interesting properties to shape your type in my case the class just like a normal class. For instance you can add the comment to code, break the code in to regions, you can add member fields and Methods. I have used the Member property of this class extensively to add local fields and methods. CodeTypeMemberCollection is a collection of CodeTypeMember type which is the base class of many important types that can be added as members to a class. In the following table extracted from the MSDN you can see those classes. System.CodeDom.CodeMemberEvent : Represents a declaration for an event of a type. System.CodeDom.CodeMemberField : Represents a declaration for a field of a type. System.CodeDom.CodeMemberMethod : Represents a declaration for a method of a type. System.CodeDom.CodeMemberProperty : Represents a declaration for a property of a type. System.CodeDom.CodeSnippetTypeMember : Represents a member of a type using a literal code fragment. System.CodeDom.CodeTypeDeclaration : Represents a type declaration for a class, structure, interface, or enumeration In this implementation I have used CodeMemberField and CodeMemberMethod types to setup the class structure I want. Now let’s move in to the SetMethod and GetFieldCode methods. CodeMemberField GetFieldCode(string name,CodeTypeReference fieldType) { CodeMemberField field = new CodeMemberField(); field.Name = name; field.Attributes = MemberAttributes.Public; field.Type = fieldType; return field; } The above code is straight forward. Here we create a field that will be added to the class we created. This CodeMemberField object is added to the Member collection of the bookShopRuleClass. Here is the sample code. CodeMemberField tempField = GetFieldCode(“category”, new CodeTypeReference(typeof(System.String))); bookShopRuleClass.Members.Add(tempField);//adding to the class Similarly I have added the methods that will be called form ultimately for validations and special offer calculations. CodeMemberMethod SetMethod(string name,CodeTypeReference returnType,string methodBody) { CodeMemberMethod method = new CodeMemberMethod(); method.Name = name; CodeSnippetStatement statement = new CodeSnippetStatement(methodBody); // adding method body method.ReturnType = returnType; method.Statements.Add(statement); // adding the statment in to the method method.Attributes = MemberAttributes.Public; return method; } Main things you have to keep in mind are that rules you have to adhere are same as the normal class. You cannot have same method signatures duplicated everywhere or have incorrect syntax inside the method bodies. To avoid the duplication of the same signature within the class I have used the category code integrated with the method name. For example to GetDiscount_B() and GetDiscount_M() method signatures avoid the duplication of the GetDiscount method inside the same class. In the Above code CodeSnippetStatement class helps us a lot to insert a literal code segment in to the source code without any modifications. Now our BookShopRuleClass implementation is done. Whole implantation is inserted in to the CodeCompileUnit object. Next step is to compile the code. For this vital step we have to use the System.CodeDom.Compiler namespace. Given the code structure or CodeCompileUnit/s we can compile it using an appropriate compiler and generate assembly file or an executable. Here we have to use some derived class of CodeDomProvider to compile the structure. In this case we have to use CSharpCodeProvider class to compile our BookShopRuleClass. In this implementation I used GenerateCodeFromCompileUnit and CompileAssemblyFromDom methods respectively to generate the source code and to get the compiled assembly. Apart from the CompileAssemblyFromDom method there are other two methods to compile a code, Namely CompileAssemblyFromFile and CompileAssemblyFromSource methods. I think the method says where you can use these methods. After creating this assembly it’s all Reflection afterwards. In the RuleManager class using that assembly I have created a BookShopRuleClass instance and using reflection invoking the specific method accordingly. I will not go in to details of RuleManager class since it’s not in the scope of this article. If you have some knowledge in Refection, you can easily understand the implementation there. For sake of completeness I will talk about System.Reflection.Emit namespace very little which is another way of creating dynamic assemblies. However I must say that this is very hectic and time consuming way. Also for the above scenario this technique is bit difficult to use. Even a simple mistake will cause the assembly creation process to fail. Please go through the code segment below. Before that it is somewhat important understand the opcodes and other syntax used in MSIL (Microsoft intermediate language). Also to get familiarized with these syntax use the MSIL Disassembler (Ildasm.exe) tool and go through some of the Assemblies you created. public Assembly CreatAssembly() { AssemblyBuilder ab = null; try { AssemblyName an = new AssemblyName(); // we can define asseblies unique identity // Version is 1.0.0.0 an.Version = new Version(1, 0, 0, 0); // Set the assembly name an.Name = "BookShopRuleWithEmit"; // Define a dynamic assembly // AssemblyBuilderAccess : Defines the access mode of the assembly to Run ab = Thread.GetDomain().DefineDynamicAssembly(an, AssemblyBuilderAccess.RunAndSave); // Define a dynamic module and the filename of the assembly ModuleBuilder modBuilder = ab.DefineDynamicModule("BookShopRuleWithEmit", "BookShopRuleWithEmit.dll"); // Create the public BookShopRules class (can spesify the namespace if needed) TypeBuilder tb = modBuilder.DefineType("BookShopRuleWithEmit.BookShopRules", TypeAttributes.Public); // Define two fields (both Public) FieldBuilder price = tb.DefineField("price", typeof(double), FieldAttributes.Public); FieldBuilder qty = tb.DefineField("quantity", typeof(double), FieldAttributes.Public); // Define GetDiscount public method, with retuen type double MethodBuilder adderBldr = tb.DefineMethod("GetDiscount", MethodAttributes.Public, CallingConventions.Standard, typeof(double), new Type[0]); //now the implementaion of the method ILGenerator ilgen = adderBldr.GetILGenerator(); // ILGenerator emit the instuctions. Label failed = ilgen.DefineLabel(); Label failed2 = ilgen.DefineLabel(); // to lable the instruction. Label endOfMthd = ilgen.DefineLabel(); ilgen.Emit(OpCodes.Ldarg_0); ilgen.Emit(OpCodes.Ldfld, qty); ilgen.Emit(OpCodes.Ldarg_0); //condition one if((price*quantity) < 100) ilgen.Emit(OpCodes.Ldfld, price); //return ((price*quantity)*.025) ilgen.Emit(OpCodes.Mul); ilgen.Emit(OpCodes.Ldc_R8, 100.00); ilgen.Emit(OpCodes.Bgt_S, failed); ilgen.Emit(OpCodes.Ldarg_0); ilgen.Emit(OpCodes.Ldfld, qty); ilgen.Emit(OpCodes.Ldarg_0); ilgen.Emit(OpCodes.Ldfld, price); // Body of the statement ilgen.Emit(OpCodes.Mul); ilgen.Emit(OpCodes.Ldc_R8, 0.025); ilgen.Emit(OpCodes.Mul); ilgen.Emit(OpCodes.Br_S, endOfMthd); ilgen.MarkLabel(failed); ilgen.Emit(OpCodes.Ldarg_0); // condition two ilgen.Emit(OpCodes.Ldfld, qty); // else if ((price*quantity) < 500) ilgen.Emit(OpCodes.Ldarg_0); //return ((price*quantity)*.05) ilgen.Emit(OpCodes.Ldfld, price); ilgen.Emit(OpCodes.Mul); ilgen.Emit(OpCodes.Ldc_R8, 500.00); ilgen.Emit(OpCodes.Bgt_S, failed2); ilgen.Emit(OpCodes.Ldarg_0); ilgen.Emit(OpCodes.Ldfld, qty); ilgen.Emit(OpCodes.Ldarg_0); ilgen.Emit(OpCodes.Ldfld, price); // body of else if ilgen.Emit(OpCodes.Mul); ilgen.Emit(OpCodes.Ldc_R8, 0.05); ilgen.Emit(OpCodes.Mul); ilgen.Emit(OpCodes.Br_S, endOfMthd); ilgen.MarkLabel(failed2); ilgen.Emit(OpCodes.Ldarg_0); ilgen.Emit(OpCodes.Ldfld, qty); ilgen.Emit(OpCodes.Ldarg_0); ilgen.Emit(OpCodes.Ldfld, price); // body of else return ((price*quantity)*.075) ilgen.Emit(OpCodes.Mul); ilgen.Emit(OpCodes.Ldc_R8, 0.075); ilgen.Emit(OpCodes.Mul); ilgen.Emit(OpCodes.Br_S, endOfMthd); ilgen.MarkLabel(endOfMthd); // returning from the method ilgen.Emit(OpCodes.Ret); tb.CreateType(); ab.Save("BookShopRuleWithEmit.dll"); // saving the dll in the disk } catch (Exception e) { } return ab; } ConclusionNow you can see the value of System.CodeDom namespace. Using dynamically generated assembly we can reduce the server client conversations immensely. Hence optimizing the network usage as well reducing the sever load. ReferenceSystem.CodeDom-http://msdn.microsoft.com/en-us/library/system.codedom.aspx System.Reflection.Emit-http://msdn.microsoft.com/en-us/library/system.reflection.emit.aspx AcknowledgementMr. Rohan Mapatuna. Mr. Uditha Bandara
|
||||||||||||||||||||||