Click here to Skip to main content
15,891,431 members
Articles / Web Development / ASP.NET

Implementing Model-View-Presenter in ASP.NET

Rate me:
Please Sign up or sign in to vote.
4.80/5 (27 votes)
17 Nov 2007CPOL12 min read 129.7K   2.7K   120  
Three implementations of Model-View-Presenter in ASP.NET 2.0.
using System;
using System.Collections;
using System.Text;
using System.Data;
using SubSonic.Utilities;
using System.Diagnostics;

namespace SubSonic
{
    public class CodeGeneration
    {
        private static string tableList = "*";
        //private const string PROPERTY_TEMPLATE = "propTemplate";
        //private const string CLASS_TEMPLATE = "classTemplate";
        //private const string COLLECTION_TEMPLATE = "collectionTemplate";
        //private const string ODS_INSERT = "odsInsert";
        //private const string ODS_UPDATE = "odsUpdate";
        //private const string DEFAULT_NAMESPACE = "";

        #region Public Methods

        public static void EnsureProviderLoad()
        {
            DataService.LoadProviders();
        }


        #region Usings

        public static string GetUsingStatements(LanguageType languageType)
        {
            if (languageType == LanguageType.CSharp)
            {
                return ResourceHelper.GetString(TemplateName.CLASS_USINGS) + Environment.NewLine + Environment.NewLine;
            }
            else if (languageType == LanguageType.VB)
            {
                return ResourceHelper.GetString(TemplateName.CLASS_USINGS_VB) + Environment.NewLine + Environment.NewLine;
            }
            return String.Empty;
        }

        #endregion

        #region Scaffolds

        public static string GenerateScaffold(string table, LanguageType languageType, DataProvider provider)
        {
            string template = String.Empty;
            if (languageType == LanguageType.CSharp)
            {
                template = ResourceHelper.GetString(TemplateName.SCAFFOLD);
            }
            else if (languageType == LanguageType.VB)
            {
                template = ResourceHelper.GetString(TemplateName.SCAFFOLD_VB);
            }
            string result = template.Replace(TemplateVariable.TABLE_NAME, table);
            result = result.Replace(TemplateVariable.PROVIDER, provider.Name);
            return result;
        }

        public static string GenerateScaffold(string table, string masterPageFile, LanguageType languageType, DataProvider provider)
        {
            string template = String.Empty;
            if (languageType == LanguageType.CSharp)
            {
                template = ResourceHelper.GetString(TemplateName.SCAFFOLD_MASTER_PAGE);
            }
            else if (languageType == LanguageType.VB)
            {
                template = ResourceHelper.GetString(TemplateName.SCAFFOLD_MASTER_PAGE_VB);
            }
            string result = template.Replace(TemplateVariable.TABLE_NAME, table).Replace(TemplateVariable.MASTER_PAGE, masterPageFile);
            result = result.Replace(TemplateVariable.PROVIDER, provider.Name);
            return result;
        }

        #endregion

        #region Classes
        public static string GenerateClass(string tableName, StringBuilder generatedTableList, LanguageType languageType, DataProvider provider)
        {
            tableList = generatedTableList.ToString();

            CodeGenerationArgs args = new CodeGenerationArgs();
            args.TableName = tableName;
            args.Namespace = GetNameSpace(provider);
            args.Language = languageType;
            return GetClass(args, provider) + Environment.NewLine;
        }
        public static string GenerateClasses(DataProvider provider, string tables, LanguageType languageType)
        {
            tableList = tables;

            //make sure everything's loaded properly
            DataService.LoadProviders();

            //set the provider for this class
            //to the provider that was passed in


            //set the SubSonicConfig.ProviderName - this will ensure that we get schema from the proper DB
            StringBuilder sOut = new StringBuilder();
            string[] tbls = DataService.GetTableNames(provider.Name);
            foreach(string tbl in tbls)
            {
                if(isInList(tbl))
                {
                    CodeGenerationArgs args = new CodeGenerationArgs();
                    args.TableName = tbl;
                    args.Namespace = GetNameSpace(provider);
                    args.Language = languageType;

                    sOut.AppendLine(GetClass(args, provider));
                }
                
            }
            string result = sOut.ToString();
            return result;
            
        }

        public static string GenerateClasses(string tables, LanguageType languageType, DataProvider provider)
        {
            tableList = tables;

            //make sure everything's loaded properly
            DataService.LoadProviders();

            string result = GenerateClasses(provider, tableList, languageType);

            return result;
        }

        public static string GenerateClass(string table, LanguageType languageType, DataProvider provider)
        {
            return GenerateClasses(table, languageType, provider);
        }

        //TODO: Why are there two similar GenerateClass methods?
        //      One should be an override of the other if necessary.
        public static string GenerateClass(string table, string nameSpace, LanguageType languageType, DataProvider provider)
        {
            tableList = table;
            CodeGenerationArgs args = new CodeGenerationArgs();
            args.TableName = table;
            args.Namespace = nameSpace;
            args.Language = languageType;
            return GetClass(args, provider) + Environment.NewLine;
        }

        /// <summary>
        /// this method can't be used with the build provider nor class generators since it would cause a doubling of using statements
        /// </summary>
        public static string GenerateClass(string table, string className, string nameSpace, LanguageType languageType, DataProvider provider)
        {
            tableList = table;
            string usings = GetUsingStatements(languageType);
            CodeGenerationArgs args = new CodeGenerationArgs();
            args.TableName = table;
            args.Namespace = nameSpace;
            args.Language = languageType;
            args.ClassName = className;

            string classCode = GetClass(args, provider);
            string result = usings + Environment.NewLine + Environment.NewLine + classCode;
            return result;
        }

        #endregion

        #region Enums
        public static string GenerateEnums(LanguageType languageType)
        {
            string enums = String.Empty;//GetEnums();//GetEnums_VB();
            return enums;
        }

        #endregion

        #region SPs

        public static string GenerateSPs(LanguageType languageType, string nameSpace, DataProvider provider)
        {
            return GenerateSPs(languageType, nameSpace, true, provider);
        }

        public static string GenerateSPs(LanguageType languageType, DataProvider provider)
        {
            return GenerateSPs(languageType, true, provider);
        }

        public static string GenerateSPs(LanguageType languageType, bool useConfig, DataProvider provider)
        {
            return GenerateSPs(languageType, provider.GeneratedNamespace, useConfig, provider);
        }

        public static string GenerateSPs(LanguageType languageType, string nameSpace, bool useConfig, DataProvider provider)
        {
            string find = provider.SPStartsWith;
            string replaceWith = provider.AppendWith;
            string spClassName = provider.SPClassName.Replace(" ", String.Empty).Trim();
            if (String.IsNullOrEmpty(spClassName))
            {
                spClassName = ClassName.STORED_PROCEDURES;
            }
            StringBuilder sOut = new StringBuilder();
            // If useConfig is false, we don't use the configuration 
            // setting and always generate the code. Used for
            // dynamic code generation that doesn't rely on web.config
            if (!useConfig || provider.UseSPs)
            {
                if (languageType == LanguageType.CSharp)
                {
                    if (!String.IsNullOrEmpty(nameSpace))
                    {
                        sOut.AppendLine();
                        sOut.AppendLine("namespace " + nameSpace);
                        sOut.AppendLine("{");
                        sOut.AppendLine();
                        sOut.AppendLine();
                    }

                    sOut.AppendLine("public class " + spClassName);
                    sOut.AppendLine("{");
                    sOut.AppendLine(GetSPs(find, replaceWith, languageType, provider));
                    sOut.AppendLine("}");

                    if (!String.IsNullOrEmpty(nameSpace))
                    {
                        sOut.AppendLine();
                        sOut.Append("}");
                    }
                }
                else if (languageType == LanguageType.VB)
                {
                    if (!String.IsNullOrEmpty(nameSpace))
                    {
                        sOut.AppendLine();
                        sOut.AppendLine("Namespace " + nameSpace);
                        sOut.AppendLine();
                        sOut.AppendLine();
                    }
                    sOut.AppendLine("Public Class " + spClassName);
                    sOut.AppendLine();
                    sOut.Append(GetSPs(find, replaceWith, languageType, provider));
                    sOut.AppendLine("End Class");

                    if (!String.IsNullOrEmpty(nameSpace))
                    {
                        sOut.AppendLine();
                        sOut.AppendLine("End Namespace");
                    }
                }
            }
            return sOut.ToString();
        }
        #endregion

        #region Views
        /// <summary>
        /// Returns the Views as ReadOnly classes
        /// </summary>
        /// <returns></returns>
        public static string GenerateViews(LanguageType languageType, DataProvider provider)
        {
            string sOut = String.Empty;


            string[] views = DataService.GetViewNames(provider.Name);

            foreach (string view in views)
            {
                if (Utility.StartsWith(view, provider.ViewStartsWith))
                {
                    CodeGenerationArgs args = new CodeGenerationArgs();
                    args.TableName = view;
                    args.Namespace = GetNameSpace(provider);
                    args.Language = languageType;
                    args.IsReadOnly = true;
                    sOut += GetView(args, provider) + Environment.NewLine;
                }
            }

            return sOut;
        }

        public static string GenerateView(string viewName, LanguageType languageType, DataProvider provider)
        {
            return GenerateView(GetNameSpace(provider), viewName, languageType, provider);
        }

        public static string GenerateView(string nameSpace, string viewName, LanguageType languageType, DataProvider provider)
        {
            CodeGenerationArgs args = new CodeGenerationArgs();
            args.TableName = viewName;
            args.Namespace = nameSpace;
            args.Language = languageType;

            return GetView(args, provider) + Environment.NewLine;
        }

        #endregion

        #region Structs
        public static string GenerateTableStruct(LanguageType languageType, DataProvider provider)
        {
            return GenerateTableStruct(languageType, provider.GeneratedNamespace, provider);
        }

        public static string GenerateTableStruct(LanguageType languageType, string nameSpace, DataProvider provider)
        {
            string[] tables = DataService.GetTableNames(provider.Name);
            return BuildTablesStruct(tables, nameSpace, languageType, provider);
        }

        public static string GenerateViewStruct(LanguageType languageType, DataProvider provider)
        {
            return GenerateViewStruct(languageType, provider.GeneratedNamespace, provider);
        }

        public static string GenerateViewStruct(LanguageType languageType, string nameSpace, DataProvider provider)
        {
            string[] views = DataService.GetViewNames(provider.Name);
            //check for multiple providers
            string sOut = BuildViewsStruct(views, nameSpace, languageType, provider);
            return sOut;
        }

        #endregion

        #endregion

        #region Private Methods
        #region Namespace
        static string GetNameSpace(DataProvider provider)
        {
            string result = null;

            try
            {
                result = provider.GeneratedNamespace;
            }
            catch (Exception)
            {
                //TODO: We shouldn't swallow all exceptions! 
                //Isn't it best to fail if we can't do this?
                //What exceptions can be throw here? P.H. -- 2007.02.16
            }

            if (String.IsNullOrEmpty(result))
            {
                result = "Subsonic.Generated";
            }
            return result;
        }

        #endregion

        #region Bools
        public static bool IsManyToMany(TableSchema.Table schema)
        {
            int keyCount = 0;
            bool bOut = false;

            foreach (TableSchema.TableColumn col in schema.Columns)
            {
                if (col.IsPrimaryKey)
                    keyCount++;
            }

            if (keyCount > 1)
            {
                bOut = true;
            }
            return bOut;
        }

        private static bool isInList(string tableName)
        {
            if (tableList.Trim() == "*")
            {
                return true;
            }
            else
            {
                //thanks chegan!
                return (Environment.NewLine + tableList.ToLower() + Environment.NewLine).Contains(Environment.NewLine + tableName.ToLower() + Environment.NewLine);

            }
        }

        private static string ReplaceTemplateValues(string template, string summary, string methodName, string methodType, string methodBody)
        {
            template = template.Replace(TemplateVariable.SUMMARY, summary);
            template = template.Replace(TemplateVariable.METHOD_NAME, methodName);
            template = template.Replace(TemplateVariable.METHOD_BODY, methodBody);
            template = template.Replace(TemplateVariable.METHOD_TYPE, methodType) + Environment.NewLine;
            return template;
        }

        #endregion

        #region SPs
        private static string GetSPs(string find, string replaceWith, LanguageType languageType, DataProvider provider)
        {
            //get the SP list from the DB
            string[] SPs = DataService.GetSPList(provider.Name);

            StringBuilder spList = new StringBuilder();
            string spTemplate = String.Empty;

            if (languageType == LanguageType.CSharp)
            {
                spTemplate = ResourceHelper.GetString(TemplateName.STORED_PROCEDURE);
            }
            else if (languageType == LanguageType.VB)
            {
                spTemplate = ResourceHelper.GetString(TemplateName.STORED_PROCEDURE_VB);
            }

            foreach (string sp in SPs)
            {
                if (!sp.StartsWith("dt_") && Utility.StartsWith(sp, provider.SPStartsWith))
                {
                    string methodName;
                    Convention convention = new Convention(provider);
                    if (string.IsNullOrEmpty(find))
                    {
                        methodName = convention.SPName(sp, replaceWith);
                    }
                    else
                    {
                        methodName = convention.SPName(sp, find, replaceWith);
                    }
                    StringBuilder paramList = new StringBuilder();
                    StringBuilder argList = new StringBuilder();
                    //grab the parameters
                    IDataReader paramReader = DataService.GetSPParams(sp, provider.Name);
                    bool firstPass = true;
                    while (paramReader.Read())
                    {
                        //loop the params, pulling out the names and dataTypes
                        DbType dbType = DataService.GetDbType(paramReader[SqlSchemaVariable.DATA_TYPE].ToString(), provider.Name);
                        string Type = Utility.GetVariableType(dbType, languageType);
                        string paramName = paramReader[SqlSchemaVariable.NAME].ToString();
                        string arg = convention.ParameterName(paramName);
                        string dbTypeName = Enum.GetName(typeof(DbType), dbType);
                        string sMode = paramReader[SqlSchemaVariable.MODE].ToString();


                        //append to the arg list
                        if (!firstPass)
                        {

                            argList.Append(", ");
                        }

                        if (languageType == LanguageType.CSharp)
                        {
                            argList.Append(Type + " " + arg);
                            if (sMode == SqlSchemaVariable.MODE_INOUT)
                            {
                                //thanks to dmcgiv for this!
                                paramList.AppendLine("\tsp.Command.AddOutputParameter(\"" + paramName + "\",DbType." + dbTypeName + ");");
                            }
                            else
                            {
                                paramList.AppendLine("\tsp.Command.AddParameter(\"" + paramName + "\"," + arg + ", DbType." + dbTypeName + ");");
                            }
                        }
                        else if (languageType == LanguageType.VB)
                        {
                            argList.Append("ByVal " + arg + " As " + Type);

                            if (sMode == SqlSchemaVariable.MODE_INOUT)
                            {
                                paramList.AppendLine("\tsp.Command.AddOutputParameter(\"" + paramName + "\", DbType." + dbTypeName + ")");
                            }
                            else
                            {
                                paramList.AppendLine("\tsp.Command.AddParameter(\"" + paramName + "\"," + arg + ", DbType." + dbTypeName + ")");
                            }
                        }
                        firstPass = false;
                    }


                    paramReader.Close();

                    //put it all together
                    string spMethod;
                    spMethod = spTemplate.Replace(TemplateVariable.STORED_PROCEDURE_NAME, sp);
                    spMethod = spMethod.Replace(TemplateVariable.METHOD_NAME, methodName);
                    spMethod = spMethod.Replace(TemplateVariable.PARAMETERS, paramList.ToString());
                    spMethod = spMethod.Replace(TemplateVariable.ARGUMENT_LIST, argList.ToString());
                    spMethod = spMethod.Replace(TemplateVariable.PROVIDER, provider.Name);

                    spList.AppendLine(spMethod);
                    spList.AppendLine();
                }
            }
            return spList.ToString();
        }
        #endregion

        #region Views
        private static string GetView(CodeGenerationArgs args, DataProvider provider)//string nameSpace, string viewName, LanguageType languageType, DataProvider provider)
        {
            //this is here for reverse-compat
            return GetClass(args, provider);
        }
        #endregion

        #region Classes
        //an override for className

        private static string GetClass(CodeGenerationArgs args, DataProvider provider)
        {
            string namespaceStart = String.Empty;
            string namespaceEnd = String.Empty;
            string classTemplate = String.Empty;

            if (args.Language == LanguageType.CSharp)
            {
                namespaceStart = Environment.NewLine + "namespace " + args.Namespace + Environment.NewLine + "{" + Environment.NewLine;
                namespaceEnd = Environment.NewLine + "}" + Environment.NewLine + Environment.NewLine;
                if (args.IsReadOnly)
                {
                    classTemplate = ResourceHelper.GetString(TemplateName.READ_ONLY_CLASS);

                }
                else
                {
                    classTemplate = ResourceHelper.GetString(TemplateName.CLASS);

                }
            }
            if (args.Language == LanguageType.VB)
            {
                if (args.IsReadOnly)
                {
                    classTemplate = ResourceHelper.GetString(TemplateName.READ_ONLY_CLASS_VB);

                }
                else
                {
                    classTemplate = ResourceHelper.GetString(TemplateName.CLASS_VB);

                }
                namespaceStart = Environment.NewLine + "Namespace " + args.Namespace + Environment.NewLine + Environment.NewLine;
                namespaceEnd = "End Namespace" + Environment.NewLine + Environment.NewLine;
            }

            string sOut = String.Empty;

            

            TableSchema.Table tbl = DataService.GetTableSchema(args.TableName, provider.Name);
            
            string className = tbl.ClassName;
            //Debug.Assert(!tbl.ClassName.Contains(" "), args.TableName);
            bool isReadOnly = args.IsReadOnly;
            LanguageType languageType = args.Language;

            if (!String.IsNullOrEmpty(args.Namespace))
            {
                sOut += namespaceStart;
            }

            //create the collection
            string collectionClass = GenerateCollection(className, isReadOnly, languageType);

            //load the properties
            string propList = GenerateProperties(tbl, languageType);

            
            //add the collection first
            string classOut = collectionClass + Environment.NewLine + Environment.NewLine;
            classOut += classTemplate.Replace(TemplateVariable.CLASS_NAME, className).Replace(TemplateVariable.PROPERTY_LIST, propList);
            classOut = classOut.Replace(TemplateVariable.TABLE_NAME, tbl.Name);

            //place holder for now - don't know what this does...
            //foreign key lookups

            string methodList = GenerateForeignKeyMethods(className, tbl, languageType);

            methodList += GeneratePKMethods(tbl, languageType);
            
            classOut = classOut.Replace(TemplateVariable.METHOD_LIST, methodList);

            //column struct
            string columnStruct = GenerateColumnsStruct(tbl, languageType);
            classOut = classOut.Replace(TemplateVariable.COLUMNS_STRUCT, columnStruct);

            if (!isReadOnly)
            {
                string odsInsertMethod = GenerateODSInsertMethod(className, tbl, languageType);
                string odsUpdateMethod = GenerateODSUpdateMethod(className, tbl, languageType);
                classOut = classOut.Replace(TemplateVariable.INSERT, odsInsertMethod);
                classOut = classOut.Replace(TemplateVariable.UPDATE, odsUpdateMethod);
            }


            string schema = GenerateSchema(tbl, languageType);
            classOut = classOut.Replace(TemplateVariable.TABLE_SCHEMA, schema);
            

            //Many To Many
            string manyMethodList = GenerateManyToManyMethods(className, tbl, languageType);
            sOut += classOut.Replace(TemplateVariable.MANY_METHODS, manyMethodList);


            if (!String.IsNullOrEmpty(args.Namespace))
            {
                sOut += namespaceEnd;
            }
            return sOut;
        }

        #endregion

        #region Generators
        /// <summary>
        /// Generates the schema in the class itself, so we don't need to hit the DB at runtime
        /// </summary>
        /// <param name="tbl"></param>
        /// <param name="languageType"></param>
        /// <returns></returns>
        private static string GenerateSchema(TableSchema.Table tbl, LanguageType languageType)
        {


            string eol = ";";
            if (languageType == LanguageType.VB)
            {
                eol = String.Empty;
            }

            StringBuilder sb = new StringBuilder();

            sb.AppendLine("BaseSchema = new TableSchema.Table(\"" + tbl.Name + "\", DataService.GetInstance(\"" + tbl.Provider.Name + "\"))" + eol);
            if (languageType == LanguageType.VB)
            {
                //sb.AppendLine("If Schema Is Nothing");
                
                sb.AppendLine("\t\t" + "Dim columns As TableSchema.TableColumnCollection = New TableSchema.TableColumnCollection()");
            }
            else
            {
                //sb.AppendLine("if(Schema == null)");
                //sb.AppendLine("\t\t{");
                //sb.AppendLine("BaseSchema = new TableSchema.Table()" + eol);
                sb.AppendLine("\t\t" + "TableSchema.TableColumnCollection columns = new TableSchema.TableColumnCollection()" + eol);
            }
            sb.AppendLine("\t\t" + "BaseSchema.Name = \"" + tbl.Name + "\"" + eol);
            //sb.AppendLine("\t\t" + "BaseSchema.ProviderName = \"" + tbl.Provider.Name + "\"" + eol);
            sb.AppendLine("\t\t" + "BaseSchema.SchemaName = \"" + tbl.SchemaName + "\"" + eol);
            sb.AppendLine(String.Empty);
            foreach (TableSchema.TableColumn column in tbl.Columns)
            {
                string varName = "col" + column.ArgumentName;
                if (languageType == LanguageType.VB)
                {
                    sb.AppendLine("\t\t" + "Dim " + varName + " As TableSchema.TableColumn = New TableSchema.TableColumn(BaseSchema)");
                }
                else
                {
                    sb.AppendLine("\t\t" + "TableSchema.TableColumn " + varName + " = new TableSchema.TableColumn(BaseSchema);");
                }

                sb.AppendLine("\t\t" + varName + ".ColumnName = \"" + column.ColumnName + "\"" + eol);
                sb.AppendLine("\t\t" + varName + ".DataType = DbType." + column.DataType + eol);

                sb.AppendLine("\t\t" + varName + ".MaxLength = " + column.MaxLength + eol);
                //sb.AppendLine("\t\t" + varName + ".PropertyName = \"" + column.PropertyName + "\"" + eol);
                //sb.AppendLine("\t\t" + varName + ".DisplayName = \"" + Utility.ParseCamelToProper(column.PropertyName) + "\"" + eol);
                //sb.AppendLine("\t\t" + varName + ".ParameterName = \"" + column.ParameterName + "\"" + eol);
                //sb.AppendLine("\t\t" + varName + ".ArgumentName = \"" + column.ArgumentName + "\"" + eol);
                if (languageType == LanguageType.VB)
                {
                    sb.AppendLine("\t\t" + varName + ".AutoIncrement = " + column.AutoIncrement + eol);
                    sb.AppendLine("\t\t" + varName + ".IsNullable = " + column.IsNullable + eol);
                    sb.AppendLine("\t\t" + varName + ".IsPrimaryKey = " + column.IsPrimaryKey + eol);
                    sb.AppendLine("\t\t" + varName + ".IsForeignKey = " + column.IsForeignKey + eol);
                    sb.AppendLine("\t\t" + varName + ".IsReadOnly = " + column.IsReadOnly + eol);
                }
                else
                {
                    sb.AppendLine("\t\t" + varName + ".AutoIncrement = " + column.AutoIncrement.ToString().ToLower() + eol);
                    sb.AppendLine("\t\t" + varName + ".IsNullable = " + column.IsNullable.ToString().ToLower() + eol);
                    sb.AppendLine("\t\t" + varName + ".IsPrimaryKey = " + column.IsPrimaryKey.ToString().ToLower() + eol);
                    sb.AppendLine("\t\t" + varName + ".IsForeignKey = " + column.IsForeignKey.ToString().ToLower() + eol);
                    sb.AppendLine("\t\t" + varName + ".IsReadOnly = " + column.IsReadOnly.ToString().ToLower() + eol);
                }
                sb.AppendLine("\t\t" + "columns.Add(" + varName + ")" + eol);
                sb.AppendLine(String.Empty);
            }
            sb.AppendLine("\t\t" + "BaseSchema.Columns = columns" + eol);
            if (languageType == LanguageType.VB)
            {
                //sb.AppendLine("\t\t" + "End If" + eol);
                //sb.AppendLine("\t\t" + "Return Schema" + eol);
            }
            else
            {
                //sb.AppendLine("\t\t" + "}" + eol);
                //sb.AppendLine("\t\t" + "return Schema" + eol);
            }
            return sb.ToString();
        }

        private static string GetNamespaceStart(string nameSpace, LanguageType langType)
        {
            if (!String.IsNullOrEmpty(nameSpace))
            {
                if (langType == LanguageType.VB)
                {
                    return Environment.NewLine + "Namespace " + nameSpace + Environment.NewLine + Environment.NewLine;
                }
                return Environment.NewLine + "namespace " + nameSpace + Environment.NewLine + "{" + Environment.NewLine;
            }
            return String.Empty;
        }

        private static string GetNamespaceEnd(string nameSpace, LanguageType langType)
        {
            if (!String.IsNullOrEmpty(nameSpace))
            {
                if (langType == LanguageType.VB)
                {
                    return Environment.NewLine + "End Namespace" + Environment.NewLine + Environment.NewLine;
                }

                return Environment.NewLine + "}" + Environment.NewLine + Environment.NewLine;
            }
            return String.Empty;
        }

        private static string WrapCodeInNamespace(string code, string nameSpace, LanguageType langType)
        {
            return GetNamespaceStart(nameSpace, langType) + code + GetNamespaceEnd(nameSpace, langType);
        }


        private static string ReplaceStructVariables(string code, string declareList, string assignList, LanguageType langType)
        {
            if (langType == LanguageType.VB)
            {
                code = code.Replace(TemplateVariable.STRUCT_LIST, declareList);
                code = code.Replace(TemplateVariable.STRUCT_ASSIGNMENTS, assignList);
            }
            else
            {
                code = code.Replace(TemplateVariable.STRUCT_LIST, assignList);
            }
            return code;
        }


        static string BuildViewsStruct(string[] views, string nameSpace, LanguageType languageType, DataProvider provider)
        {
            string sOut;

            //pull the template
            if (languageType == LanguageType.VB)
            {
                sOut = ResourceHelper.GetString(TemplateName.VIEW_LIST_VB);
            }
            else
            {
                sOut = ResourceHelper.GetString(TemplateName.VIEW_LIST);
            }
            //create the table list
            StringBuilder assignList = new StringBuilder();
            StringBuilder declareList = new StringBuilder();
            foreach (string view in views)
            {
                string alteredName = DataService.GetTableSchema(view, provider.Name).ClassName;
                if (languageType == LanguageType.VB)
                {
                    assignList.AppendLine(alteredName + " = @\"" + view + "\"");
                    declareList.AppendLine("\t\tPublic Shared " + alteredName + " As String");
                }
                else
                {
                    assignList.AppendLine("\t\tpublic static string " + alteredName + " = @\"" + view + "\";");
                }
            }

            sOut = ReplaceStructVariables(sOut, declareList.ToString(), assignList.ToString(), languageType);
            return WrapCodeInNamespace(sOut, nameSpace, languageType);
        }


        static string BuildTablesStruct(string[] tables, string nameSpace, LanguageType languageType, DataProvider provider)
        {
            string sOut;
            //pull the template
            if (languageType == LanguageType.VB)
            {
                sOut = ResourceHelper.GetString(TemplateName.TABLE_LIST_VB);
            }
            else
            {
                sOut = ResourceHelper.GetString(TemplateName.TABLE_LIST);
            }
            //create the table list
            StringBuilder assignList = new StringBuilder();
            StringBuilder declareList = new StringBuilder();
            foreach (string table in tables)
            {
                string alteredName = DataService.GetTableSchema(table, provider.Name).ClassName;
                if (languageType == LanguageType.VB)
                {
                    assignList.AppendLine(alteredName + " = @\"" + table + "\"");
                    declareList.AppendLine("\t\tPublic Shared " + alteredName + " As String");
                }
                else
                {
                    assignList.AppendLine("\t\tpublic static string " + alteredName + " = @\"" + table + "\";");
                }
            }

            sOut = ReplaceStructVariables(sOut, declareList.ToString(), assignList.ToString(), languageType);
            return WrapCodeInNamespace(sOut, nameSpace, languageType);
        }

        static string GenerateColumnsStruct(TableSchema.Table table, LanguageType languageType)
        {
            string sOut;

            if (languageType == LanguageType.VB)
            {
                sOut = ResourceHelper.GetString(TemplateName.COLUMN_STRUCT_VB);
            }
            else
            {
                sOut = ResourceHelper.GetString(TemplateName.COLUMN_STRUCT);
            }

            StringBuilder assignList = new StringBuilder();
            StringBuilder declareList = new StringBuilder();
            foreach (TableSchema.TableColumn col in table.Columns)
            {
                string alteredName = col.PropertyName;
                if (languageType == LanguageType.VB)
                {
                    assignList.AppendLine("\t\t" + alteredName + " = \"" + col.ColumnName + "\"");
                    declareList.AppendLine("\t\tPublic Shared " + alteredName + " As String");
                }
                else
                {
                    assignList.AppendLine("\t\tpublic static string " + alteredName + " = @\"" + col.ColumnName + "\";");
                }
            }
            sOut = ReplaceStructVariables(sOut, declareList.ToString(), assignList.ToString(), languageType);
            return sOut;
        }


        static string GenerateCollection(string className, bool isReadOnly, LanguageType languageType)
        {
            //pull the template
            string sOut;
            if (languageType == LanguageType.VB)
            {
                if (isReadOnly)
                {
                    sOut = ResourceHelper.GetString(TemplateName.READ_ONLY_COLLECTION_VB);
                }
                else
                {
                    sOut = ResourceHelper.GetString(TemplateName.COLLECTION_VB);
                }
            }
            else
            {
                if (isReadOnly)
                {
                    sOut = ResourceHelper.GetString(TemplateName.READ_ONLY_COLLECTION);
                }
                else
                {
                    sOut = ResourceHelper.GetString(TemplateName.COLLECTION);
                }
            }
            sOut = sOut.Replace(TemplateVariable.CLASS_NAME, className);
            return sOut;
        }

        static string GenerateODSUpdateMethod(string className, TableSchema.Table tbl, LanguageType languageType)
        {
            string updateMethod;
            string updateSetList = String.Empty;
            string updateArgList = String.Empty;

            foreach (TableSchema.TableColumn col in tbl.Columns)
            {
                string varType = Utility.GetVariableType(col.DataType, col.IsNullable, languageType);
                string propertyName = col.PropertyName;
                string argName = col.ArgumentName;


                if (!Utility.IsAuditField(col.ColumnName))
                {
                    updateSetList += updateSetList.Length == 0 ? String.Empty : (Environment.NewLine + "\t\t");

                    if (languageType == LanguageType.CSharp)
                    {
                        string argument = varType + " " + argName;
                        updateSetList += "item." + propertyName + " = " + argName + ";";
                        updateArgList += updateArgList.Length == 0 ? argument : (", " + argument);
                    }
                    else if (languageType == LanguageType.VB)
                    {
                        string argument = "ByVal " + argName + " As " + varType;
                        updateSetList += "item." + propertyName + " = " + argName;
                        updateArgList += updateArgList.Length == 0 ? argument : (", " + argument);
                    }
                }
            }


            string updateTemplate;
            if (languageType == LanguageType.CSharp)
            {
                updateTemplate = ResourceHelper.GetString(TemplateName.ODS_UPDATE);
            }
            else
            {
                updateTemplate = ResourceHelper.GetString(TemplateName.ODS_UPDATE_VB);
            }

            updateMethod = updateTemplate.Replace(TemplateVariable.ARGUMENT_LIST, updateArgList);
            updateMethod = updateMethod.Replace(TemplateVariable.CLASS_NAME, className);
            updateMethod = updateMethod.Replace(TemplateVariable.SET_LIST, updateSetList);
            return updateMethod;
        }


        static string GenerateODSInsertMethod(string className, TableSchema.Table tbl, LanguageType languageType)
        {
            string insertSetList = String.Empty;
            string insertArgList = String.Empty;
            string insertMethod;

            foreach (TableSchema.TableColumn col in tbl.Columns)
            {
                string varType = Utility.GetVariableType(col.DataType, col.IsNullable, languageType);
                string propertyName = col.PropertyName;
                string argName = col.ArgumentName;

                if (!Utility.IsAuditField(col.ColumnName))
                {
                    insertSetList += insertSetList.Length == 0 ? String.Empty : (Environment.NewLine + "\t\t");

                    if (languageType == LanguageType.CSharp)
                    {
                        string argument = varType + " " + argName;
                        if (!col.AutoIncrement)
                        {
                            insertArgList += insertArgList.Length == 0 ? argument : (", " + argument);
                            insertSetList += "item." + propertyName + " = " + argName + ";";
                        }
                    }
                    else if (languageType == LanguageType.VB)
                    {
                        string argument = "ByVal " + argName + " As " + varType;
                        if (!col.AutoIncrement)
                        {
                            insertArgList += insertArgList.Length == 0 ? argument : (", " + argument);
                            insertSetList += "item." + propertyName + " = " + argName;
                        }
                    }
                }
            }

            string insertTemplate;
            if (languageType == LanguageType.CSharp)
            {
                insertTemplate = ResourceHelper.GetString(TemplateName.ODS_INSERT);
            }
            else
            {
                insertTemplate = ResourceHelper.GetString(TemplateName.ODS_INSERT_VB);
            }
            insertMethod = insertTemplate.Replace(TemplateVariable.ARGUMENT_LIST, insertArgList);
            insertMethod = insertMethod.Replace(TemplateVariable.CLASS_NAME, className);
            insertMethod = insertMethod.Replace(TemplateVariable.SET_LIST, insertSetList);
            return insertMethod;
        }

        static string GeneratePKMethods(TableSchema.Table tbl, LanguageType languageType)
        {
            StringBuilder methodList = new StringBuilder();
            ArrayList usedMethodNames = new ArrayList();

            foreach (TableSchema.TableColumn col in tbl.Columns)
            {
                if (col.IsPrimaryKey)
                {
                    string propFKTable = GetPrimaryKeyTableSetter(tbl, col, usedMethodNames, languageType);

                    if (!string.IsNullOrEmpty(propFKTable))
                    {
                        methodList.AppendLine(propFKTable);
                    }
                }
            }


            return methodList.ToString();
        }


        static string GenerateForeignKeyMethods(string className, TableSchema.Table tbl, LanguageType languageType)
        {
            //string methodList = string.Empty;
      
            StringBuilder methodList = new StringBuilder();
            //StringBuilder output = new StringBuilder();
            //if (languageType == LanguageType.VB) {
            //    output.AppendLine("Public Property " + propertyName + "() As " + fkTableNameSingular);
            //    output.AppendLine("\tGet" + Environment.NewLine + "\t\t return " + fkTableNameSingular + ".FetchByID(" + pkPropertyName + ")" + Environment.NewLine + "\tEnd Get");
            //    output.AppendLine("\tSet" + Environment.NewLine + "\t\t MarkDirty()" + Environment.NewLine + "\t\tSetColumnValue(\"" + pkPropertyName + "\", Value." + Convention.PropertyName(tblFK.PrimaryKey.ColumnName, table.Name) + ")" + Environment.NewLine + "\tEnd Set" + Environment.NewLine + "End Property");
            //} else {
            //    output.AppendLine("public " + fkTableNameSingular + " " + propertyName + Environment.NewLine + "{");
            //    output.AppendLine("\tget { return " + fkTableNameSingular + ".FetchByID(" + pkPropertyName + "); }");
            //    output.Append("\tset" + Environment.NewLine + "\t{" + Environment.NewLine + "\t\tMarkDirty();" + Environment.NewLine + "\t\tSetColumnValue(\"" + pkPropertyName + "\", value." + Convention.PropertyName(tblFK.PrimaryKey.ColumnName, table.Name) + ");" + Environment.NewLine + "\t}" + Environment.NewLine + "}");
            //}
            //return output.ToString();
            
            
            ArrayList usedPropertyNames = new ArrayList();

            foreach (TableSchema.TableColumn col in tbl.Columns)
            {
                if (col.IsForeignKey)
                {
                    string propFKTable = GetForeignKeyTableSetter(tbl, col, className, usedPropertyNames, languageType);
                    if (!string.IsNullOrEmpty(propFKTable))
                    {
                        methodList.AppendLine(propFKTable);
                    }
                }
            }

            
            return methodList.ToString();
        }

        static string GenerateManyToManyMethods(string className, TableSchema.Table tbl, LanguageType languageType)
        {
            string manyMethods = String.Empty;
            StringBuilder manyMethodList = new StringBuilder();
            string[] manyList = DataService.GetManyToMany(tbl.Provider.Name, tbl.Name);
            foreach (string mapTable in manyList)
            {
                if (mapTable.EndsWith(tbl.Provider.ManyToManySuffix))
                {
                    string template;
                    if (languageType == LanguageType.CSharp)
                    {
                        template = ResourceHelper.GetString(TemplateName.MANY_TO_MANY_METHODS);
                    }
                    else
                    {
                        template = ResourceHelper.GetString(TemplateName.MANY_TO_MANY_METHODS_VB);
                    }
                    string foreignPK = String.Empty;
                    //pull map table
                    TableSchema.Table mtbl = DataService.GetTableSchema(mapTable, tbl.Provider.Name);

                    //there should be two keys on this table - ignore the one that's for 
                    //this class
                    foreach (TableSchema.TableColumn col in mtbl.Columns)
                    {
                        if (col.IsPrimaryKey && !Utility.IsMatch(col.ColumnName, tbl.PrimaryKey.ColumnName))
                        {
                            foreignPK = col.ColumnName;
                            break;
                        }
                    }

                    if (!String.IsNullOrEmpty(foreignPK))
                    {
                        //now, get foreign table
                        try
                        {
                            string fTableName = DataService.GetForeignKeyTableName(foreignPK, tbl.Name, tbl.Provider.Name);
                            if(isInList(fTableName))
                            {
                                TableSchema.Table ftbl = DataService.GetTableSchema(fTableName, tbl.Provider.Name);
                                string fClassName = ftbl.ClassName;
                                string pkPropertyName = tbl.PrimaryKey.DisplayName;
                                //now that we have the schema, we can set the template
                                manyMethods = template.Replace(TemplateVariable.CLASS_NAME, className);
                                manyMethods = manyMethods.Replace(TemplateVariable.PK_PROP, pkPropertyName);
                                manyMethods = manyMethods.Replace(TemplateVariable.PK, tbl.PrimaryKey.ColumnName);
                                //manyMethods = manyMethods.Replace(TemplateVariable.PK_VAR, tbl.PrimaryKey.ColumnName.ToLower());
                                manyMethods = manyMethods.Replace(TemplateVariable.PK_VAR, tbl.PrimaryKey.ColumnName);
                                manyMethods = manyMethods.Replace(TemplateVariable.MAP_TABLE, mtbl.Name);
                                manyMethods = manyMethods.Replace(TemplateVariable.FOREIGN_PK, ftbl.PrimaryKey.ColumnName);
                                //manyMethods = manyMethods.Replace(TemplateVariable.FK_VAR, ftbl.PrimaryKey.ColumnName.ToLower());
                                manyMethods = manyMethods.Replace(TemplateVariable.FK_VAR, ftbl.PrimaryKey.ColumnName);
                                manyMethods = manyMethods.Replace(TemplateVariable.FOREIGN_TABLE, ftbl.Name);
                                manyMethods = manyMethods.Replace(TemplateVariable.FOREIGN_CLASS, fClassName);
                            }
                        }
                        catch
                        {
                            manyMethods = "";
                        }
                    }
                }
                else
                {
                }

                manyMethodList.AppendLine(manyMethods);
            }
            return manyMethods;
        }



        static string GenerateProperties(TableSchema.Table tbl, LanguageType languageType)
        {
            StringBuilder sOut = new StringBuilder();

            string propTemplate;
            if (languageType == LanguageType.CSharp)
            {
                propTemplate = ResourceHelper.GetString(TemplateName.PROPERTY);
            }
            else
            {
                propTemplate = ResourceHelper.GetString(TemplateName.PROPERTY_VB);
            }

            foreach (TableSchema.TableColumn col in tbl.Columns)
            {
                string varType = Utility.GetVariableType(col.DataType, col.IsNullable, languageType);
                string propertyName = col.PropertyName;
                string getLine = GetPropertyGetter(col, languageType);
                string setLine = GetPropertySetter(col, languageType);

                sOut.AppendLine(propTemplate.Replace(TemplateVariable.PROPERTY_NAME, propertyName)
                                    .Replace(TemplateVariable.GETTER, getLine)
                                    .Replace(TemplateVariable.SETTER, setLine)
                                    .Replace(TemplateVariable.PROPERTY_TYPE, varType));
            }

            return sOut.ToString();
        }

        #endregion

        #region Props

        private static string GetPrimaryKeyTableSetter(TableSchema.Table table, TableSchema.TableColumn column, ArrayList usedMethodNames, LanguageType languageType)
        {
            StringBuilder allMethods = new StringBuilder();
            TableSchema.Table[] pkTables = DataService.GetPrimaryKeyTables(table.Name, table.Provider.Name);
            if (pkTables != null)
            {
                foreach(TableSchema.Table pkTable in pkTables)
                {
                    if(isInList(pkTable.Name) && (pkTable.PrimaryKey != null))
                    {
                        string methodFKTableTemplate;
                        if(languageType == LanguageType.VB)
                        {
                            methodFKTableTemplate = ResourceHelper.GetString(TemplateName.METHOD_VB);
                        }
                        else
                        {
                            methodFKTableTemplate = ResourceHelper.GetString(TemplateName.METHOD);
                        }

                        //if (String.IsNullOrEmpty(pkTable[0]) || String.IsNullOrEmpty(pkTable[1]))
                        //{
                        //    return String.Empty;
                        //}
                        string tblNameFK = pkTable.ClassName;
                        string tblNameFKPlural = pkTable.ClassNamePlural;
                        string tblNameFKCollection = tblNameFK + "Collection";
                        string structNameFK = pkTable.PrimaryKey.PropertyName;
                        string propertyName = column.PropertyName;

                        string methodBody;
                        if(languageType == LanguageType.VB)
                        {
                            methodBody = "\t\tReturn New " + tblNameFKCollection + "().Where(" + tblNameFK + ".Columns." +
                                         structNameFK + ", " + propertyName + ").Load()";
                        }
                        else
                        {
                            methodBody = "\t\treturn new " + tblNameFKCollection + "().Where(" + tblNameFK + ".Columns." +
                                         structNameFK + ", " + propertyName + ").Load();";
                        }

                        string methodName = table.Provider.RelatedTableLoadPrefix + tblNameFKPlural;
                        string summary = "One to Many - Returns " + tblNameFKPlural;

                        if(Utility.IsMatch(tblNameFK, tblNameFKPlural))
                        {
                            methodName = methodName + "Records";
                        }

                        if(tblNameFK == table.Name)
                        {
                            methodName = "Child" + methodName;
                        }
                        if(usedMethodNames.Contains(methodName))
                        {
                            methodName += "From" + propertyName;
                        }
                        if(!usedMethodNames.Contains(methodName))
                        {
                            usedMethodNames.Add(methodName);
                            methodFKTableTemplate = ReplaceTemplateValues(methodFKTableTemplate, summary, methodName, tblNameFKCollection, methodBody);
                            allMethods.Append(methodFKTableTemplate);
                        }
                    }
                }
            }
            return allMethods.ToString();
        }

        private static string GetForeignKeyTableSetter(TableSchema.Table table, TableSchema.TableColumn column, string className, ArrayList usedPropertyNames, LanguageType languageType)
        {
            TableSchema.Table fkTable = DataService.GetForeignKeyTable(column, table);

            if (fkTable == null || !isInList(fkTable.Name))
            {
                return String.Empty;
            }

            //TableSchema.Table tblFK = null;
            //try
            //{
            //    tblFK = DataService.GetTableSchema(fkTableName, SubSonicConfig.ProviderName);//Query.BuildTableSchema(fkTableName,SubSonicConfig.ProviderName);
            //}
            //catch
            //{
            //    Debug.WriteLine("Failed for " + fkTableName);
            //    return null;
            //}

            bool isManyToMany = IsManyToMany(fkTable);

            string methodType = fkTable.ClassName;
            string methodTypeCollection = methodType +  "Collection";
            string fkTableNameSingular = fkTable.ClassName;
            string pkStructName = fkTable.PrimaryKey.PropertyName;
            string pkPropertyName = column.PropertyName;

            if (String.IsNullOrEmpty(pkStructName))
            {
                if (fkTable.Columns.Count > 0)
                {
                    pkStructName = fkTable.Columns[0].ColumnName;
                }
            }

            if (!isManyToMany)
            {
                if (!IsManyToMany(table))
                {
                    string propertyName = table.Provider.RelatedTableLoadPrefix + fkTableNameSingular;

                    if (fkTable.Name == table.Name)
                    {
                        propertyName = "Parent" + propertyName;
                    }
                    if (usedPropertyNames.Contains(propertyName))
                    {
                        propertyName += "From" + pkPropertyName;
                    }
                    usedPropertyNames.Add(propertyName);

                    StringBuilder output = new StringBuilder();
                    if (languageType == LanguageType.VB)
                    {
                        output.AppendLine("Public Property " + propertyName + "() As " + fkTableNameSingular);
                        output.AppendLine("\tGet" + Environment.NewLine + "\t\t return " + fkTableNameSingular + ".FetchByID(" + pkPropertyName + ")" + Environment.NewLine + "\tEnd Get");
                        output.AppendLine("\tSet" + Environment.NewLine + "\t\t MarkDirty()" + Environment.NewLine + "\t\tSetColumnValue(\"" + pkPropertyName + "\", Value." + fkTable.PrimaryKey.PropertyName + ")" + Environment.NewLine + "\tEnd Set" + Environment.NewLine + "End Property");
                    }
                    else
                    {
                        output.AppendLine("public " + fkTableNameSingular + " " + propertyName + Environment.NewLine + "{");
                        output.AppendLine("\tget { return " + fkTableNameSingular + ".FetchByID(" + pkPropertyName + "); }");
                        output.Append("\tset" + Environment.NewLine + "\t{" + Environment.NewLine + "\t\tMarkDirty();" + Environment.NewLine + "\t\tSetColumnValue(\"" + pkPropertyName + "\", value." + fkTable.PrimaryKey.PropertyName + ");" + Environment.NewLine + "\t}" + Environment.NewLine + "}");
                    }
                    return output.ToString();

                }
                else
                {
                    string methodFKTableTemplate;
                    string methodBody;

                    if (languageType == LanguageType.VB)
                    {
                        methodFKTableTemplate = ResourceHelper.GetString(TemplateName.METHOD_VB);
                        methodBody = "\t\tReturn New " + fkTableNameSingular + "Collection().Where(" + fkTableNameSingular + ".Columns." + pkStructName + ", " + pkPropertyName + ").Load()";
                    }
                    else
                    {
                        methodFKTableTemplate = ResourceHelper.GetString(TemplateName.METHOD);
                        methodBody = "\t\treturn new " + fkTableNameSingular + "Collection().Where(" + fkTableNameSingular + ".Columns." + pkStructName + ", " + pkPropertyName + ").Load();";
                    }
                    string methodName = table.Provider.RelatedTableLoadPrefix + table.ClassNamePlural;
                    if (usedPropertyNames.Contains(methodName) || Utility.IsMatch(methodName, className))
                    {
                        methodName += "From" + pkPropertyName;
                    }
                    if (!usedPropertyNames.Contains(methodName))
                    {
                        string summary = "Returns " + fkTableNameSingular;
                        string methodFKTable = ReplaceTemplateValues(methodFKTableTemplate, summary, methodName, methodTypeCollection, methodBody);
                        usedPropertyNames.Add(methodName);
                        return methodFKTable;
                    }
                }
            }
            return String.Empty;
        }

        private static string GetPropertyGetter(TableSchema.TableColumn column, LanguageType languageType)
        {
            StringBuilder sb = new StringBuilder();

            string varType = Utility.GetVariableType(column.DataType, column.IsNullable, languageType);

            if (languageType == LanguageType.VB)
            {
                sb.AppendFormat("\t\t\tReturn GetColumnValue(Of {0})(\"{1}\")", varType, column.ColumnName);
            }
            else
            {
                sb.AppendFormat("\t\t\treturn GetColumnValue<{0}>(\"{1}\");", varType, column.ColumnName);
            }
            return sb.ToString();
        }

        private static string GetPropertySetter(TableSchema.TableColumn column, LanguageType languageType)
        {
            StringBuilder sb = new StringBuilder();

            if (languageType == LanguageType.VB)
            {
                sb.AppendLine("\t\t\tMarkDirty()");
                sb.AppendLine("\t\t\tSetColumnValue(\"" + column.ColumnName + "\", Value)");
            }
            else
            {
                sb.AppendLine("\t\t\tMarkDirty();");
                sb.AppendLine("\t\t\tSetColumnValue(\"" + column.ColumnName + "\", value);");
            }
            return sb.ToString();
        }

        #endregion

        #endregion

    }
    /// <summary>
    /// Helper class to hold arguments for passing to methods
    /// </summary>
    class CodeGenerationArgs
    {
        public string Template = string.Empty;
        public TableSchema TableSchema = null;
        public LanguageType Language = LanguageType.CSharp;
        public string[] TableNames = null;
        public bool IsReadOnly = false;
        public string ClassName = string.Empty;
        public string TableName = string.Empty;
        public string SPName = string.Empty;
        public string Namespace = string.Empty;
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions