|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
BackgroundI was playing with code generation tools and came across Code Smith which seemed very promising and uses an ASP.NET host engine to parse and generate output. I started studying more on ASP.NET and the ASP.NET Host Engine and realized that all that is done in Code Smith can be done via plain ASP.NET and Visual Studio. As a result, I invented SmartCodeGenerator. The large majority of this research was done about a year ago, however, I got busy with Pageflakes and other projects and did not get a chance to publish any of my work. Anyway, finally I arranged some time and published SmartCodeGenerator (SCG) at Codeplex. For the latest source code and updates please refer to Codeplex. This article is based on CTP2.5 and is more of an architectural overview of SCG. To get familiar on how to use SCG to generate text based output, please refer to the tutorials at SmartCodeGenerator Quick start v2.0 and check this Article at Code Project "SmartCodeGenerator - Code Generation experience with Visual Studio and ASP.NET- Usage Overview". For the user forum which allows the sharing of SCG-based resources and ideas, please refer to SmartCodeGenerator Community Site. IntroductionSmartCodeGenerator (SCG) is an ASP.NET 2.0 website or an ASP.NET 1.1 web application and is a fully-fledged template-based code generator that allows you to generate code for any text language. Templates are written in Visual Studio as ASP.NET User Controls. The code generation process can be customized by using properties. The current feature list of SmartCodeGenerator includes:
The entire development life cycle of creating custom templates using SmartCodeGenerator is done using Visual Studio by creating an ASP.NET application. So during the generation of templates, intellisense, compilation, debug, source view, design view, codebehind file and all other features of Visual Studio (2005 or 2003) are available to you. SCG supports custom properties, and any properties that are defined in the SmartCodeGenerator Terms and VerbsWhile reading the introduction, you came across terms such as Property Type,
TheProperties Class: is simply a .NET class and any properties that are defined in this class are automatically picked up by the SCG Framework and a relevant UI is generated to collect data from an end user. For example, I have defined a string property TestProperty.
public class TheProperties
{
private string _testProperty;
public string TestProperty
{
get
{
return _testProperty;
}
set
{
_testProperty = value;
}
}
}
And the SCG Framework will automatically generate a UI for this property like this.
SmartCodeGenerator UIProperty: is ASP.NET User Control. The So you should have already noticed there is no need to learn any new technologies. Code is written as you would normally write code for an ASP.NET application. SmartCodeGenerator FrameworkI will discuss the SCG Framework from 2 different perspectives. Firstly, I will discuss the SCG workflow.
Secondly, I will discuss SCG Framework's heart and soul LoadUIProperty Pipeline:
Generate (Output) Pipeline:
Based on responsibility, the SmartCodeGenerator Framework can be divided into four sections.
Identify Custom PropertiesI have seen custom properties offered in Code Smith and it's a very handy concept where an end user can enter values for the custom properties and code is generated accordingly. In Code Smith we declare custom properties using tags like this <%@ Property Name="MSN" Optional="True" Type="System.String" Category=
"General" Default="shahed.khan@gmail.com" Description="Email Address" %>
Declaring this as a tag is very cumbersome and I was not very happy with this style. Initially, I used to declare custom properties for SCG as an ASP.NET Profile object, which offered me two good things for free:
I was able to do Profile. (dot) and the VS2005 would create a Profile class on the fly and give intellisense support on the Profile object. And to my knowledge, Code Smith does not have intellisense support on custom properties. I was quite happy with it but when I wanted to write a version of SCG that will support .NET 1.1 as well, I soon realized that the Profile object is not available in the ASP.NET 1.1 framework. I had to change my code a bit and introduced the
public class TheProperties
{
private string _testProperty;
public string TestProperty
{
get
{
return _testProperty;
}
set
{
_testProperty = value;
}
}
}
The way I used to define custom properties changed a bit also - no more tagging, and now custom properties are defined as properties of the class. By doing this change I could use the code snippets feature from VS 2005. If you have code snippets installed, try typing "prop" and press Tab you will see the property is automatically declared for you. I was happier with this solution, but had to sacrifice one thing: I cannot use the
public class ScPageBase: System.Web.UI.Page
{
…
private object properties;
public object TheProperties
{
get { return properties; }
set { properties = value; }
}
…
}
public class ScTemplateBase: System.Web.UI
{
…
public ScPageBase ParentPage
{
get
{
if (this.Page is ScPageBase)
return ((ScPageBase)this.Page);
throw new Exception("Parent Page is not ScPageBase");
}
}
...
}
If you have already explored any of the public partial class Templates_DefaultTemplate : ScTemplateBase
{
}
The SCG Framework picks up properties that are defined in this
public class ScPageBase: System.Web.UI.Page
{
…
private object properties;
public object TheProperties
{
get { return properties; }
set { properties = value; }
}
…
}
SCG offers this flexibility to allow you to create your own custom wizard (entirely in your own style and as per your own requirements) for collecting data for custom properties. Also, as you will write ASP.NET pages for your wizard, it is probably one of the easiest to write and extend. Generate UI for Custom PropertiesHere I'll describe how the UI is generated on the fly. The SCG Framework picks up the object passed to the protected void _LoadUIProperties(object sender, EventArgs e)
{
…
object profile = this.TheProperties;
foreach (PropertyInfo info in profile.GetType().GetProperties())
{
…
if (info.PropertyType.IsPublic)
{
foreach (PropertyTypeAndUIPropertyMap map in
this.UIPropertyMapCollection)
{
if (map.PropertyType == info.PropertyType.ToString())
{
UserControl ctl = _Util.LoadControl(Page,
string.Format("~/PropertyControls/{0}",
map.PropertyUI), info);
ctl.ID = info.Name;
control.Controls.Add(ctl);
break;
}
}
}
}
}
A .NET type and a <?xml version="1.0"?>
<ArrayOfPropertyTypeAndUIPropertyMap xmlns:xsi="http://www.w3.org/
2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<PropertyTypeAndUIPropertyMap>
<PropertyType>System.String</PropertyType>
<PropertyUI>ScStringUIProperty.ascx</PropertyUI>
</PropertyTypeAndUIPropertyMap>
…
</ArrayOfPropertyTypeAndUIPropertyMap>
This file contains an array of Now you might be asking where the SCG Framework will find the ScStringUIProperty.ascx file. If you open up any of your SmartCodeGenerator Projects you will notice there is a Folder named "PropertyControls". SCG Framework looks for the mapped ascx usercontrols in this folder. In addition, all the ascx controls that are mapped in the above XML file were stored in this folder. Identify Templates and Output PathIn an void _Default_OnPreGenerate(object sender, EventArgs e)
{
//You can use the handy IOList to Map Input Template and OutputPath
//Add any number of InputTemplateAndOutputPath object to the IOList
//To generate codes in batch mode just iterate through the IOList
this.IOList.Add(new InputTemplateAndOutputPath(
"~/Templates/FirstTemplate.ascx",
@"c:\temp\FirstTemplate.cs"));
this.IOList.Add(new InputTemplateAndOutputPath(
"~/Templates/SecondTemplate.ascx", @"c:\temp\Second.cs"));
this.IOList.Add(new InputTemplateAndOutputPath(
"~/Templates/ThirdTemplate.ascx", @"c:\temp\Third.cs"));
….
}
Notice the framework comes with the The
public class ScPageBase: System.eb.UI.Page
{
…
private InputTemplateAndOutputPathCollection ioList = null;
public InputTemplateAndOutputPathCollection IOList
{
get
{
if (ioList == null)
ioList = new InputTemplateAndOutputPathCollection();
return ioList;
}
}
…
}
So, your Templates folder may end up having hundreds of templates but you can run your desired templates from this section. Simply map your desired input and output (using the At this stage, we have to add objects to the IOList manually by writing code, but a UI / Wizard could be created to map Generate OutputThis is the final step where I generate the output. Open up the default.aspx.cs file. Notice what is done in the following 2 methods. void _Default_OnGenerate(object sender, EventArgs e)
{
GenerateFiles(IOList, e);
}
private void GenerateFiles(InputTemplateAndOutputPathCollection ioList,
EventArgs e)
{
//This is going to Loop through all the templates of IOList
foreach (InputTemplateAndOutputPath io in ioList)
{
…
_Util.GenerateOutputAsFile(Page, io, e);
//To Get GeneratedText use the following Method
//string generatedText = _Util.GeneratedOutputAsText(Page, io, e);
}
}
Here I iterate through the InputOutputTemplate list (IOList) and generate files by calling Let's now look at the SCG Framework from the pipelines point of view. I will discuss in this section The Default PageThe Default Page has a huge responsibility in the SCG Framework. It coordinates among the responsibilities mentioned above. It acts as the host for the UI and offers a Generate Button to enable text based output. In addition, it reports which files are generated. The Default page inherits from
ScPageBase Events
LoadUIProperty Pipeline:
Generate (Output) Pipeline:
Before going deeper in to the discussion, I also want to introduce the
public class ScEventArgs : EventArgs
{
public Hashtable Item;
public ScEventArgs(Hashtable item)
{
this.Item = item;
}
}
The Hashtable dict = new Hashtable();
dict.Add("control", PanelProperties);
ScEventArgs args = new ScEventArgs(dict);
LoadUIProperties_PipeLine(sender, args);
Here I created a LoadUIProperties Pipeline
public void LoadUIProperties_PipeLine(object sender, EventArgs e)
{
PreLoadUIProperties (sender, e);
LoadUIProperties (sender, e);
LoadUIPropertiesComplete(sender, e);
}
The protected void Page_Init(object sender, EventArgs e)
{
//Assign TheProperties object
this.TheProperties = new TheProperties();
//Hook UI loading feature for custom properties
this.OnLoadUIProperties += new EventHandler(_LoadUIProperties);
}
protected void Page_Load(object sender, EventArgs e)
{
//Pass panel into pipeline
Hashtable dict = new Hashtable();
dict.Add("control", PanelProperties);
//
ScEventArgs args = new ScEventArgs(dict);
//Execute Pipe_Line
LoadUIProperties_PipeLine(sender, args);
}
protected void _LoadUIProperties(object sender, EventArgs e)
{
…
object profile = this.TheProperties;
foreach (PropertyInfo info in profile.GetType().GetProperties())
{
…
if (info.PropertyType.IsPublic)
{
foreach (PropertyTypeAndUIPropertyMap map in
UIPropertyMapCollection)
{
if (map.PropertyType == info.PropertyType.ToString())
{
UserControl ctl = _Util.LoadControl(Page,
string.Format("~/PropertyControls/{0}",
map.PropertyUI), info);
ctl.ID = info.Name;
control.Controls.Add(ctl);
break;
}
}
}
}
}
The above code is self explanatory. Generate (Output) PipelineIf you open up the Default.aspx.cs file you will see the following piece of code: protected void btnGenerate_Click(object sender, EventArgs e)
{
//Executes the Generate_Pipeline [ this executes OnPreGenerate, OnGenerate,
OnGenerateComplete & OnCleanTheProperties event ]
//Prepare ScEventArgs and pass it to the PipeLine
Hashtable dict = new Hashtable();
ScEventArgs args = new ScEventArgs(dict);
Generate_PipeLine(sender, args);
}
public void Generate_PipeLine(object sender, EventArgs e)
{
PreGenerate(sender, e);
Generate(sender, e);
GenerateComplete(sender, e);
//Clean TheProperties to avoid serialization issue
CleanTheProperties(sender, e);
}
There is another pipeline for generating text based output and this pipeline is executed when the Generate button is clicked. In the public ScStringUIProperty(PropertyInfo propertyInfo)
{
ParentPage.OnPreGenerate += new EventHandler(
ScStringUIProperty_OnPreGenerate);
…
}
void ScStringUIProperty_OnPreGenerate(object sender, EventArgs e)
{
string property = tbProperty.Text;
if (propertyInfo.CanWrite)
this.propertyInfo.SetValue(ParentPage.TheProperties, property, null);
}
In the constructor I hook up this protected void Page_Load(object sender, EventArgs e)
{
this.OnPreGenerate += new EventHandler(_Default_OnPreGenerate);
this.OnGenerate += new EventHandler(_Default_OnGenerate);
this.OnGenerateComplete += new EventHandler(_Default_OnGenerateComplete);
}
void _Default_OnPreGenerate(object sender, EventArgs e)
{
…
this.IOList.Add(new InputTemplateAndOutputPath(
"~/Templates/Example3Template.ascx",
@"c:\tmp\nainai\{0}TestEntity1.cs"));
//Adding a report item in the args
((ScEventArgs)e).Item.Add("report", string.Empty);
lblReport.Text = string.Empty;
}
void _Default_OnGenerate(object sender, EventArgs e)
{
GenerateFiles(IOList, e);
}
void _Default_OnGenerateComplete(object sender, EventArgs e)
{
lblReport.Text = ((ScEventArgs)e).Item["report"].ToString();
}
The above code is fairly self explanatory. In the
OK, let's explore the private void GenerateFiles(InputTemplateAndOutputPathCollection ioList,
EventArgs e)
{
//This is going to Loop through all the templates of IOList
foreach (InputTemplateAndOutputPath io in ioList)
{
…
_Util.GenerateOutputAsFile(Page, io, e);
//To Get GeneratedText use the following Method
//string generatedText = _Util.GeneratedOutputAsText(Page, io, e);
}
}
Here, I iterate through the IOList collection and can call the public void GenerateOutputAsFile(Page page, InputTemplateAndOutputPath io,
EventArgs e)
public void GenerateOutputAsFile(Page page, InputTemplateAndOutputPath io,
EventArgs e)
{
string code = string.Empty;
UserControl ctl = page.LoadControl(io.InputPathFilename) as UserControl;
code = GetGeneratedTemplateCode(ctl, e);
//Check Directory and if necessary Create
FileInfo info = new FileInfo(io.OutputPathFilename);
if (!Directory.Exists(info.DirectoryName))
Directory.CreateDirectory(info.DirectoryName);
//Create File
using (StreamWriter sw = File.CreateText(io.OutputPathFilename))
{
sw.Write(code);
}
}
Here, I dynamically load the mapped Finally, this is the magical piece of code called private string GetGeneratedTemplateCode(UserControl ctl,
EventArgs e)
{
string code = string.Empty;
if (ctl is ScTemplateBase)
{
((ScTemplateBase)ctl).PreGenerateTemplateCode(this, e);
code = GetGeneratedTemplateCode(((ScTemplateBase)ctl));
//ah feeling light
ctl = null;
}
return code;
}
private string GetGeneratedTemplateCode(ScTemplateBase ctrl)
{
StringBuilder stringBuilder = new StringBuilder();
StringWriter stringWriter = new StringWriter(stringBuilder);
HtmlTextWriter writer = new HtmlTextWriter(stringWriter);
ctrl.RenderControl(writer);
return stringBuilder.ToString();
}
It is not rocket science. ((ScTemplateBase)ctl).PreGenerateTemplateCode(this, e);
This is why all public partial class Templates_Example2Template : ScTemplateBase
{
public override void PreGenerateTemplateCode( object sender, EventArgs e)
{
//e is accessible here
theclassname = TheProperties.ClassName;
}
}
And finally if you need to do any clean up, you can do this using the The DBSchemaProviderWith SmartCodeGenerator you get a Database Schema Discovery API for MS SQL Server, Oracle and the MySQL database engines. I have used Microsoft® Provider Design Pattern to write the Schema Discovery API. For a refresher on the Provider Pattern, have a look at my previous article "Flexible and Plug-in-based .NET Applications Using the Provider Pattern". To allow you to discover the database schema details, the providers come with the following signature.
public abstract class DBSchemaProvider : ProviderBase
{
// Methods
protected DBSchemaProvider();
public abstract ParameterSchemaCollection
GetCommandParameters(CommandSchema command);
public abstract CommandResultSchemaCollection
GetCommandResultSchemas(CommandSchema command);
public abstract CommandSchemaCollection GetCommands(DatabaseSchema
database);
public abstract string GetCommandText(CommandSchema command);
public abstract string GetDatabaseName(DatabaseSchema database);
public abstract string GetDescription();
public abstract ExtendedPropertyCollection
GetExtendedProperties(SchemaBase schemaObject);
public abstract string GetName();
public abstract ColumnSchemaCollection GetTableColumns(TableSchema table);
public abstract DataTable GetTableData(TableSchema table);
public abstract IndexSchemaCollection GetTableIndexes(TableSchema table);
public abstract TableKeySchemaCollection GetTableKeys(TableSchema table);
public abstract PrimaryKeySchema GetTablePrimaryKey(TableSchema table);
public abstract TableSchemaCollection GetTables(DatabaseSchema database);
public abstract ViewColumnSchemaCollection GetViewColumns(ViewSchema view);
public abstract DataTable GetViewData(ViewSchema view);
public abstract ViewSchemaCollection GetViews(DatabaseSchema database);
public abstract string GetViewText(ViewSchema view);
public static DBSchemaProvider Instance();
public static DBSchemaProvider Instance(string providerName);
}
To write a new provider for a different database all you have to do is implement these methods. All the source code of how I implement providers for MS SQL Server, Oracle and MySQL is available for download from CodePlex. Writing SCG Templates and Using SmartCodeGeneratorThere is nothing new to learn here if you already know how to write an ascx UserControl. All you have to do is write an ascx UserControl, but remember to inherit the UserControl from public partial class Templates_Example2Template : ScTemplateBase
{
public override void PreGenerateTemplateCode( object sender, EventArgs e)
{
//e is accessible here
theclassname = TheProperties.ClassName;
}
}
And here is a simple example of a <%@ Control Language="C#" AutoEventWireup="true"
CodeFile="Example2Template.ascx.cs"
Inherits="Templates_Example2Template" %>
public class
<%=TheProperties.ClassName%>
{
public <%=TheProperties.ClassName%>()
{
//Add constructor here
<%for (int i = 0; i < 3; i++)
{
%>
//Hey this is Cool
<%} %>
}
}
For more details on how to write templates and how to use SmartCodeGenerator please refer to the tutorials at http://www.smartcodegenerator.com. How to write your own UIPropertyYou can introduce ConclusionSmartCodeGenerator is a very powerful code generation framework. Here it couples Visual Studio and ASP.NET. However, this framework can be used with any ASP.NET IDE that exists in the world and code can be generated. You simply need to create an ASP.NET project, add a reference to the DLLs supplied with SCG. SCG also ships with Database Schema Discovery API's for MS SQL Server, Oracle and MySQL, and implementing new providers for different databases is very easy. An SCG Project is simply a ASP.NET project and not dependent on any third party tools, and SCGTemplates and UIProperties are ascx UserControls. For a ASP.NET developer there is nothing new to learn to start using SmartCodeGenerator. I strongly believe that you will agree with me in saying: "Code Generation has never been this easy". Thank you for being with me so far and please join the community and share your SCGTemplates and UIProperties. Proof reading done by: Christopher Heale Resources and Links related to SmartCodeGenerator
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||