1

I currently have a c# utility that runs a very long list of steps, for instance:

  • copy a file from here to there i.e.: foreach(file in list) File.Copy ()
  • create a bunch of directories foreach (dir in list) Directory.Create
  • Loop into a list of directories and create a file. foreach (dir in list) File.Create()
  • Take a know list of files and replace some text inside foreach (file in list) Execute ("replace.exe", text1, text2)

etc.

What I did was to take all these steps and create a bunch of methods in c# that do those steps. The advantage of that is, I get to check for each command I run, log any problems and so on. The downside of that is, it is hard coded.

I would like to (and I need to) move those steps out of the code, like in a configuration file or xml file and then read the commands one at a time and run them inside c#. In this way I still have the advantage of checking for return errors, creating logs, etc. but I don't have the actual list and commands hard coded.

I don't want to use a script (like Perl or batch) because I want to be able to monitor some of the steps I do. In other words, I really want to keep the actual execution inside my c# program but have the program being guided by the configuration file that I feed to it.

xml is quite flexible but I am not sure I can do all that with it. For instance how do I turn into xml something like:

list1 = {file1, file2};
list2  = {dir1,dir2,dir3};
foreach (file in list1)
 File.Copy(file,dir2\\file);
 File.Copy(file,dir3\\file);

Any ideas on what I could/should use to accomplish that?

Thanks Tony

3
  • 5
    Consider using PowerShell instead? Commented Apr 6, 2011 at 16:49
  • I have not thought about that but this would be an external process (I think). I honestly never used PowerShell so I am not sure how that could help me. Tony Commented Apr 7, 2011 at 21:37
  • PowerShell can be started as an external process, or can be hosted from within another process. For instance, SQL Server Management Studio allows you to right-click a node in the tree view and start PowerShell running with that node as the current item. Commented Apr 7, 2011 at 21:46

4 Answers 4

1

Here is my loop task

    [XmlAttribute]
    public string DataTableName { get; set; }

    public TaskListClass TaskList { get; set; }

    public override void Execute(WorkContainer container)
    {
        int iteration = 0;
        string originalTaskName=String.Empty;

        // Log Start
        base.Execute(container);

        // Get the Data Table Based upon the ContainerKey
        DataTable loopTable = container.GetKeyValue<DataTable>(DataTableName);

        // If not found throw exception and exit
        if (loopTable == null)
        {
            throw new ArgumentException(
                "DataTable Not Found or Initialized For Loop Variable",
                DataTableName);
        }

        // For each row returned run the task in order as a mini-process
        foreach (DataRow row in loopTable.Rows)
        {
            iteration++;

            foreach (TaskBase task in TaskList.Tasks)
            {
                // If it is the first iteration then create a new string with the iteration number
                // else just replace the last iteration number with the current iteration number
                if (iteration == 1)
                {
                    task.Name = String.Format("Iteration {0} - {1}", iteration, task.Name);
                }
                else
                {
                    task.Name = task.Name.Replace(String.Format("Iteration {0}",iteration-1),String.Format("Iteration {0}",iteration));
                }

                // Call the task Constructor to Initialize the object
                task.GetType().GetConstructor(new Type[0]).Invoke(null);

                foreach (DataColumn dc in loopTable.Columns)
                {
                    // Store in Stack Variable
                    string propertyName = dc.ColumnName;

                    // If a field in the table matches a 
                    // property Name set that property
                    PropertyInfo propInfo = task.GetType().GetProperty(propertyName);
                    if (propInfo != null)
                    {
                        propInfo.SetValue(task, row[dc], null);
                    }                        
                    else
                    {
                        // Else if the task has a Parameters collection add the 
                        // name of the column and value to the Parameter Collection
                        propInfo = task.GetType().GetProperty("Parameters");

                        if (propInfo != null)
                        {
                            List<Parameter> parameters = propInfo.GetValue(task, null) as List<Parameter>;

                            // If the parameter Name already Exist then Override it
                            // This means that the values in the DataTable override the values in the XML
                            if (parameters.Contains(new Parameter { Name = propertyName }))
                            {
                                parameters.Remove(new Parameter { Name = propertyName });
                            }

                            parameters.Add(new Parameter { Name = propertyName, Value = row[dc].ToString(), Type="String" });
                        }
                        else
                        {
                            ParameterNotFoundException pExp = new ParameterNotFoundException(propertyName, task.GetType().ToString());
                            throw new TaskException("Unable to assign a Parameter", pExp.Message, pExp);
                        }

                    }
                }

                task.Execute(container);
            }
        }

        base.Finish(container);
    }

The XML looks something like this

    <LoopTask Name="Loop Report Creation" DataTableName="[? DistributionList ?]">
      <TaskList>
        <ReportBatchTask Name="Report In Loop">
          <ConnectionName>xxx</ConnectionName>
          <Parameters />
        </ReportBatchTask>
      </TaskList>
    </LoopTask>
Sign up to request clarification or add additional context in comments.

Comments

1

To do what you're asking, you would have to have each possible action defined in code, so if you create a new action, that would require editing your program. There shouldn't be any problem configuring existing actions any way you want, though.

<TaskList>
  <MyTask>
    <Action type="CreateDirectory">
      <target>dir1</target>
      <target>dir2</target>
    </Action>
  </MyTask>
  <MyTask>
    <SourceFiles>
      <file>mypath\file1.txt</file>
      <file>mypath\file2.txt</file>
    </SourceFile>
    <Action type="Replace">
      <originalText>Text to replace</originalText>
      <replacementText>Some new text</replacementText>
    </Action>
    <Action type="Copy">
      <target>dir1</target>
      <target>dir2</target>
    </Action>
  </MyTask>
</TaskList>

If you had a separate class for each possible Action your would then create and execute an appropriate Action using what you read in from the XML.

1 Comment

I really like this suggestion. I will need to figure out loops but mostly it solve my problem. Thanks Tony
1

Yes I have done a similar project: Here are some idea's without having to type the whole thing.

I created Tasks and every Task inherieted from Task class for basic logging and

 public virtual void Execute(WorkContainer container)

But every class did something so the Copy Class would have a source, destination and overwrite properties.

Then I would create a Task List which was a List of Task that could then be serialized into xml

would look something like

   <TaskList>
    <TransactionSqlTask Name="Get Interest Register Data">
      <Parameters>
        <Parameter>
          <Type>String</Type>
          <Value>M</Value>
          <Name>PaymentInd</Name>
        </Parameter>
      </Parameters>
      <DataTableName>
        <string>InterestRegisterData</string>
      </DataTableName>
      <IgnoreOutputDataTable>false</IgnoreOutputDataTable>
      <StoredProcName>proc_Report_NotesInterestRegister</StoredProcName>
      <ConnectionName>xxx</ConnectionName>
    </TransactionSqlTask>

...

That Xml would be a command line argument which would be read in, deserialized and turn back into a List of Task

Then I would just loop through the List and Call .Execute

Now there was also a Work Container to pass values from one task to another. See this post

Using Generics to return a literal string or from Dictionary<string, object>

I can provide more detail later, right now I do not have the project infront of me but hopefully this gave you some ideas.

1 Comment

This is a good suggestion and it has some similarities with the next suggestion. Thanks Tony
0

Have you considered rewriting your functions as MSBuild tasks?

That way you can leverage the pre-existing msbuild tasks, and write your script as an msbuild file (which fits nicely in any build process). You can even debug MSBuild tasks

2 Comments

I haven't thought about that but then it goes back to my initial problem of having to run an external process that is like a black-box to me. A Perl script or a batch file would be the same as using msbuild. Perl is definitely debug-able. I do see your point of msbuild fitting nicely in a build process. Thanks Tony
You don't have to call an external process.. MSBuild exposes a C# library, see here: stackoverflow.com/questions/536539/how-to-call-msbuild-from-c

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.