8

I am attempting to generate a .csproj file programatically using the MSBuild namespace, but the lack of code samples is making this a tedious and error prone process.

So far, I have got the following code but I am not sure how to add classes, references and such to the project.

public void Build()
{
    string projectFile = string.Format(@"{0}\{1}.csproj", Common.GetApplicationDataFolder(), _project.Target.AssemblyName);
    Microsoft.Build.Evaluation.Project p = new Microsoft.Build.Evaluation.Project();

    ProjectCollection pc = new ProjectCollection();

    Dictionary<string, string> globalProperty = new Dictionary<string, string>();

    globalProperty.Add("Configuration", "Debug");
    globalProperty.Add("Platform", "x64");

    BuildRequestData buildRequest = new BuildRequestData(projectFile, globalProperty, null, new string[] { "Build" }, null);

    p.Save(projectFile);

    BuildResult buildResult = BuildManager.DefaultBuildManager.Build(new BuildParameters(pc), buildRequest);
}

Thanks.

9
  • I would love to know the use case of this. You want to generate csproj files for someone else, who has VS, to use? Commented Jul 3, 2014 at 15:47
  • Sounds like a strange request. Have you looked at the Mono source on GitHub? Commented Jul 3, 2014 at 15:50
  • This is not a strange request. I want to generate an API dynamically that contains some functionality taken from an XML file that determines what will be included in the API. I will then call MSBuild to compile the project into an actual assembly. Commented Jul 3, 2014 at 15:55
  • 2
    This sounds like an XY problem. It is possible to dynamically emit assemblies using Reflection.Emit or compile dynamically generated code using CodeDom. I don't see why MSBuild is necessary based on what you've told us. Commented Jul 3, 2014 at 16:11
  • 1
    You are using the MSBuild api. It consumes project files, it doesn't create them. You need XmlWriter.Create() Commented Jul 3, 2014 at 16:29

1 Answer 1

13

If you really want to create a proj file with MSBuild API you have to use the Microsoft.Build.Construction namespace. Most of the types you will need are in the Micrsoft.Build.dll assembly. A short sample that creates a project file with a few properties and item groups:

class Program
{
    static void Main(string[] args)
    {
        var root = ProjectRootElement.Create();
        var group = root.AddPropertyGroup();
        group.AddProperty("Configuration", "Debug");
        group.AddProperty("Platform", "x64");

        // references
        AddItems(root, "Reference", "System", "System.Core");

        // items to compile
        AddItems(root, "Compile", "test.cs");

        var target = root.AddTarget("Build");
        var task = target.AddTask("Csc");
        task.SetParameter("Sources", "@(Compile)");
        task.SetParameter("OutputAssembly", "test.dll");

        root.Save("test.csproj");
        Console.WriteLine(File.ReadAllText("test.csproj"));
    }

    private static void AddItems(ProjectRootElement elem, string groupName, params string[] items)
    {
        var group = elem.AddItemGroup();
        foreach(var item in items)
        {
            group.AddItem(groupName, item);
        }
    }
}

Note that this just creates the proj file. It doesn't run it. Also, properties like "Configuration" and "Platform" are only meaningful in the context of a proj file generated by Visual Studio. Here they won't really do anything unless you add more property groups with conditions the way Visual Studio does automatically.

Like I indicated in my comments, I think this is wrong way to go about this. You really just want dynamic compilation of sources, which is available through CodeDom:

using System.CodeDom.Compiler;

class Program
{
    static void Main(string[] args)
    {
        var provider = CodeDomProvider.CreateProvider("C#");
        string[] sources = {
                               @"public abstract class BaseClass { public abstract void Test(); }",
                               @"public class CustomGenerated : BaseClass { public override void Test() { System.Console.WriteLine(""Hello World""); } }"
                           };

        var results = provider.CompileAssemblyFromSource(new CompilerParameters() {
            GenerateExecutable = false,
            ReferencedAssemblies = { "System.dll", "System.Core.dll" },
            IncludeDebugInformation = true,
            CompilerOptions = "/platform:anycpu"
        }, sources);
        if (results.Errors.Count > 0)
        {
            Console.WriteLine("{0} errors", results.Errors.Count);
            foreach(CompilerError error in results.Errors)
            {
                Console.WriteLine("{0}: {1}", error.ErrorNumber, error.ErrorText);
            }
        }
        else
        {
            var type = results.CompiledAssembly.GetType("CustomGenerated");
            object instance = Activator.CreateInstance(type);
            type.GetMethod("Test").Invoke(instance, null);
        }
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

After trying to use MSBuild I have admitted defeat and decided to bow to greater wisdom and use CodeDom instead. It is possible to do it using MSBuild but it's a lot more convoluted and complicated. It would certainly have helped if Microsft had better documentation.

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.