C# CODE PROVIDER ( CSharpCodeProvider )


We can leverage CSharpCodeProvider features's to achieve various development related requirement which otherwise would not be possible at design time. A suitable example could be; work-around for "Modifying Custom Attribute's properties at run-time" which otherwise would not be possible in straight way.

Background:

In one of my earlier post "Generating Executable Code or Class Libraries for Custom Class Snippets (C# Script) at Runtime" at http://www.dotnetspider.com/resources/44223-Generating-Executable-Code-with-C-Scripting-at-Runtime.aspx I covered a general use of CSharpCodeProvider to compile C# code at run time. In this post I am providing little more insight of this class and also the real time example with 'Custom Attributes Manipulations' to elaborate the topic.

CSharpCodeProvider is a system class available in Microsoft.CSharp namespace and is derived from System's another baseclass CodeDomProvider of System.CodeDom.Compiler namespace. This class is useful incase we want to execute C# compiler from within our application. This provide has a method CompileAssemblyFromSource(), which receives 2 parameters; compiler parameters & the C# Custom Class and compiles the specific assembly & returns instance of CompilerResults class which contains instance of CompiledAssembly. Once we have instance of specific assembly, we can use it's CreateInstance() method to instentiate any member class at run-time by providing it's fully qualified classname (FQN .. e.g. namespace.folder.classsname).

We can leverage this feature of .Net to achieve various development related requirements which otherwise would not be possible at design time (or run time manipulations).

A suitable example could be; work-around for "Modifying Custom Attribute's properties at run-time" which otherwise would not be possible in straight way.


Objective of this Article:

A Custom Attribute is a useful feature provided by .Net framework that we can attach to a class or its member so that various operations (custom operations) could be performed on those class or members w.r.t. the Properties of custom attributes to give development flexibility to the developer. But "what-if" when there is a scenario in which you may want to modify custom attribute properties at run-time ?
** This manipulation is not possible with a standard coding **


Scenario

e.g.



ImageInfoClass imageInfo = new ImageInfoClass();

Object[] TableMapAttributes=imageInfo.GetType().GetCustomAttributes(typeof(TableMapAttribute), false);

// object of attribute
TableMapAttribute tableMapAttribute = (tableMapAttributes as TableMapAttribute[])[0];

// modifying properties (we are ImageInfo Class for the purpose of Employee image processing..)
tableMapAttribute.TargetTableName = "EmployeeTable";



At first, it seems that we have updated the attribute properties but its not correct. Because when next time if we fetch-again the same attribute from same imageInfo object, we will notice that attribute's properties do not persist "Earlier" stored values.

Now what? development stopped? ..... No! We have a work-around (this is the objective of this Article).

This work-around works well in most of the similar scenarions.


P O C - C O D E


Consider we have a Class "ImageInfo" and a Custom Attribute "TableMap" is attached to it, which has 2 properties "TableName" & "PrimaryKeyColumnName"

e.g.



[TableMapAttribute("<Table Name Placeholder>", <Primary Key Column name Placeholder>)]
public class ImageInfo
{
      ... member1
      ... member2
}



Note: At run-time we want to push "table name" & "primary key column name" to above attbibute (note that, table & primary key can vary as per requirement)

To achieve this, follow these steps:

   1. Create a "class template" for ImageInfo along with all the customization (like ... place "Hard-coded" Table name & Primary key name in the place holders)
   2. Also Create a HelperFunction() function (refer following Code snippet) and pass "class template" to it.
   3. This helper function compiles the assembly and finally returns the class instance to the caller.


Custom Attribute Class:



        public class TableMapAttribute : Attribute
        {
                public TableMapAttribute(String _TableName, String _PrimaryKeyColumnName)
                {
                        TableName = _TableName;
                        PrimaryKeyColumnName = _PrimaryKeyColumnName;
                }
                public String TableName {get; set;}
                public String PrimaryKeyColumnName {get; set;}
        }




Making "Custom Class Template" and calling Helper Function:



        //...
        //...

        // At runtime we will pass Table name & Primary Key Column name into [TableMap("...", "...")] attribute

        StringBuilder sb = new StringBuilder();
        sb.Append("using System;");
        sb.AppendLine("using System.Collections.Generic;");
        sb.AppendLine("using System.Linq;");
        sb.AppendLine("using System.Text;");
        sb.AppendLine("using System.Runtime.Serialization;");
        sb.AppendLine("using RCFW.CustomAttributeClasses;");
        sb.AppendLine("using RCFW.InfoClasses;");
        sb.AppendLine(String.Format("namespace {0}", nameSpace));
        sb.AppendLine("{");
        sb.AppendLine(String.Format(" [TableMap({0}{1}{0},{0}{2}{0})]",'"',tableName,primaryKeyColumnName));
        sb.AppendLine("      public class ImageInfo");
        sb.AppendLine("      {");
        sb.AppendLine("            [PrimaryKey]");
        sb.AppendLine("            public int PK_Record_ID { get; set; }");
        sb.AppendLine("            public String ImagePath { get; set; }");
        sb.AppendLine("            public int FK_ModifiedBy_ID { get; set; }");
        sb.AppendLine("            public DateTime ModifiedOn { get; set; }");
        sb.AppendLine("            public string TableName { get; set; }");
        sb.AppendLine("            public string PrimaryKeyColumnName { get; set; }");
        sb.AppendLine("            public string ImageData { get; set; }");
        sb.AppendLine("            public String ImageName { get; set; }");
        sb.AppendLine("      }");
        sb.AppendLine("}");

        String classTemplate = sb.ToString();

        // additional assemply collection for above code to get complied without "missing reference(/s)" errors.

        Object[] requiredAssemblies = new Object[] { "AssemblyName1.dll" , "AssemblyName2.dll" };

        // Call here the Helper Function with classTemplate, FQN for class and assembly names

        try
        {
               dynamic imageInfo = HelperFunction(classTemplate,"Namespace.Folder.ImageInfo",requiredAssemblies)
        }
        catch(Exception ex)
        {
               // handle exception
        }

        // You can perform any operation to this imageInfo instance as you do with a normal object

        //...
        //...




Helper Function:



public static object HelperFunction(String classTemplate, String ClassFQN, Object[] requiredAssemblies)
{
        #region provider settings

        // setting version
        System.Collections.Generic.Dictionary providerOptions =
                                                new System.Collections.Generic.Dictionary
                                                {
                                                       { "CompilerVersion", "v3.5" }
                                                };

        // getting the provider
        Microsoft.CSharp.CSharpCodeProvider provider = new Microsoft.CSharp.CSharpCodeProvider(providerOptions);

        #endregion

        #region parameter settings

        System.CodeDom.Compiler.CompilerParameters parameters = new System.CodeDom.Compiler.CompilerParameters();

        parameters.GenerateExecutable = false; // we want to generate class library (dll) rather than executable
        parameters.GenerateInMemory = false; // code should be generated in the memory
        parameters.WarningLevel = 3; // default warning level for tracing
        parameters.TreatWarningsAsErrors = false; // keep it false else even-a small warning will break compilation
        parameters.CompilerOptions = "/optimize"; // make compact code

        parameters.ReferencedAssemblies.Add("System.Core.dll"); // add this to provide standard classes of framework

        // add more assemblies, that you passed as additional assemblies

        foreach (object o in requiredAssemblies) parameters.ReferencedAssemblies.Add(o as string);

        #endregion

        #region Compilation and object creation

        System.CodeDom.Compiler.CompilerResults results=provider.CompileAssemblyFromSource(parameters,classTemplate);

        if (results.Errors.Count != 0) throw new Exception("Run time compilation Failed!");

        var myClassObject = results.CompiledAssembly.CreateInstance(ClassFQN);

        #endregion

        return myClassObject;
}




I hope this article will help many developer working on the similar scenarios.

Incase any suggestions or help , please keep adding your queries.

(I will also upload the complete test project to implement this feature.)

Thanks


Comments

No responses found. Be the first to comment...


  • Do not include your name, "with regards" etc in the comment. Write detailed comment, relevant to the topic.
  • No HTML formatting and links to other web sites are allowed.
  • This is a strictly moderated site. Absolutely no spam allowed.
  • Name:
    Email: