Modify programatically csproj files with Microsoft.Build.Evaluation (instead of Engine) - c#

I would like to read, modify and write back csproj files.
I've found this code, but unfortunately Engine class is depreciated.
Engine engine = new Engine()
Project project = new Project(engine);
project.Load("myproject.csproj");
project.SetProperty("SignAssembly", "true");
project.Save("myproject.csproj");
So I've continued based on the hint I should use Evaluation.ProjectCollection instead of Engine:
var collection = new ProjectCollection();
collection.DefaultToolsVersion = "4.0";
var project = new Project(collection);
// project.Load("myproject.csproj") There is NO Load method :-(
project.FullPath = "myproject.csproj"; // Instead of load? Does nothing...
// ... modify the project
project.Save(); // Interestingly there is a Save() method
There is no Load method anymore. I've tried to set the property FullPath, but the project still seems empty. Missed I something?
(Please note I do know that the .csproj file is a standard XML file with XSD schema and I know that we could read/write it by using XDocument or XmlDocument. That's a backup plan. Just seeing the .Save() method on the Project class I think I missed something if I can not load an existing .csproj. thx)

I've actually found the answer, hopefully will help others:
Instead of creating a new Project(...) and trying to .Load(...) it, we should use a factory method of the ProjectCollection class.
// Instead of:
// var project = new Project(collection);
// project.FullPath = "myproject.csproj"; // Instead of load? Does nothing...
// use this:
var project = collection.LoadProject("myproject.csproj")

Since i can't comment:
This won't work in .net core without first setting the MSBuild.exe path variable. The code to do so can be found here
https://blog.rsuter.com/missing-sdk-when-using-the-microsoft-build-package-in-net-core/
and is written here
private static void SetMsBuildExePath()
{
try
{
var startInfo = new ProcessStartInfo("dotnet", "--list-sdks")
{
RedirectStandardOutput = true
};
var process = Process.Start(startInfo);
process.WaitForExit(1000);
var output = process.StandardOutput.ReadToEnd();
var sdkPaths = Regex.Matches(output, "([0-9]+.[0-9]+.[0-9]+) \\[(.*)\\]")
.OfType<Match>()
.Select(m => System.IO.Path.Combine(m.Groups[2].Value, m.Groups[1].Value, "MSBuild.dll"));
var sdkPath = sdkPaths.Last();
Environment.SetEnvironmentVariable("MSBUILD_EXE_PATH", sdkPath);
}
catch (Exception exception)
{
Console.Write("Could not set MSBUILD_EXE_PATH: " + exception);
}
}

Related

Specflow BeforeTestRun Logging

[BeforeFeature]
public static void BeforeFeature()
{
featureTitle = $"{FeatureContext.Current.FeatureInfo.Title}";
featureRollFileAppender = new RollingFileAppender
{
AppendToFile = true,
StaticLogFileName = true,
Threshold = Level.All,
Name = "FeatureAppender",
File = "test.log",
Layout = new PatternLayout("%date %m%newline%exception"),
};
featureRollFileAppender.ActivateOptions();
log.Info("test");
}
I am attempting to use log4net to output a simple string, however, once the file has been generated, it does not contain any data.
No errors are thrown and the test does complete successfully.
It turns out that the previously selected RollingFileAppender was still open and I needed to select another RollingFileAppender. This is one of the issues when using multiple log files. Once this was resolved, the Info() method would output to my desired log file.
I was able to resolve my issue by adding the following code:
BasicConfigurator.Configure(nameRunRollFileAppender);
log = LogManager.GetLogger(typeof(Tracer));
log.Info("Output some data");

Add class to compiled assembly (in memory)

Since CompileAssemblyFromSource add custom functions in a smart way was ignored im going to ask this question differently so people will bother to read it.
cutting at the chase,i am making a language by "translating" the new syntax into c# and compiling it in memory in this fashion.
using (Microsoft.CSharp.CSharpCodeProvider CodeProv =
new Microsoft.CSharp.CSharpCodeProvider())
{
CompilerResults results = CodeProv.CompileAssemblyFromSource(
new System.CodeDom.Compiler.CompilerParameters()
{
GenerateInMemory = true
},
code);
var type = results.CompiledAssembly.GetType("MainClass");
var obj = Activator.CreateInstance(type);
var output = type.GetMethod("Execute").Invoke(obj, new object[] { });
Console.WriteLine(output);
}
basically i am executing a "main" function written inside the code variable.
and i am using some functions in the code variable i would like to include without adding it as a string at the bottom like this:
code += #"public void Write(string path, object thevar)
{
if (thevar.GetType() == typeof(string))
{
System.IO.File.WriteAllText(path,(string)thevar);
}
if (thevar.GetType() == typeof(string[]))
{
System.IO.File.WriteAllLines(path,(string[])thevar);
}
}";
Can i somehow add a class from my Actual main project in VS and let the compiled in memory code access it? without adding it as a string.
You can embed your source code file(s) as resources. With this technique you can edit the file in Visual Studio and access the contents of the files as if it was a string during run-time.
This link shows how to do it:
https://stackoverflow.com/a/433182/540832

Upgraded dotLess error: IImporter does not contain definition for 'Paths'

I have the following code in a project I try to upgrade the "dotless" NuGet package from the "1.2.2.0" to the latest (at moment "1.4.0.0"):
private void GetStylesheetContent(HttpContext context, string name)
{
var conf = BundleConfigSectionHandler.GetConfig();
var elt = conf.Stylesheets.GetBundle(name);
if (elt != null) {
Minifier minifier = null;
if (_conf.Stylesheets.Minify) {
minifier = new Minifier();
}
var files = elt.ListFiles();
var existingFiles = new List<string>();
StringBuilder buffer = new StringBuilder();
foreach (var file in files) {
var physicalFile = context.Request.MapPath(file);
if (File.Exists(physicalFile)) {
existingFiles.Add(physicalFile);
string content;
var path = VirtualPathUtility.GetDirectory(file);
if (file.EndsWith(".less", StringComparison.OrdinalIgnoreCase))
{
var reader = new dotless.Core.Input.VirtualFileReader();
var localpath = VirtualPathUtility.ToAbsolute(file);
content = reader.GetFileContents(localpath);
var parse = new Parser();
parse.Importer = new Importer(reader);
/*Error>*/ parse.Importer.Paths.Add(VirtualPathUtility.ToAbsolute(path));
var eng = new LessEngine(parse);
content = eng.TransformToCss(content, localpath);
The error is on the third line from bottom. It says:
Error 417 'dotless.Core.Importers.IImporter' does not contain a
definition for 'Paths' and no extension method 'Paths' accepting a
first argument of type 'dotless.Core.Importers.IImporter' could be
found (are you missing a using directive or an assembly reference?)
Is a pity that the team didn't left the old method with an [Obsolete] attribute and suggestion to upgrade.
Does anyone know how to replace the "Importer.Paths.Add" method ?
I'm not that familiar with the inner workings of dotless. But looking at the source code for the Importer. paths has been protected since version version 1.2.3. Looking around at the class a little more, it seems like you need to use an instance of dotless.Parser.Tree.Import to add your paths manually.
It does look like this is pretty far off the normal dotless path. So it wouldn't surprise me if the API is a little unstable in these areas. You also may want to look at how bundling works in a question like How to use ASP.Net MVC 4 to Bundle LESS files in Release mode? to see how they handle all the dotless classes.

WP7: collection of images

I have images in folder Images in my windows phone solution. How can i get collection of images in this folder? Build Action of all images is "Content".
It had been bugging me that it wasn't possible to do this so I've done a bit of digging and have come up with a way of getting a list of all image files with the build action of "Resource". - Yes, this isn't quite what was asked for but hopefully this will still be useful.
If you really must use a build action of "Content" I'd use a T4 script to generate the list of files at build time. (This is what I do with one of my projects and it works fine.)
Assuming that the images are in a folder called "images" you can get them with the following:
var listOfImageResources = new StringBuilder();
var asm = Assembly.GetExecutingAssembly();
var mrn = asm.GetManifestResourceNames();
foreach (var resource in mrn)
{
var rm = new ResourceManager(resource.Replace(".resources", ""), asm);
try
{
var NOT_USED = rm.GetStream("app.xaml"); // without getting a stream, next statement doesn't work - bug?
var rs = rm.GetResourceSet(Thread.CurrentThread.CurrentUICulture, false, true);
var enumerator = rs.GetEnumerator();
while (enumerator.MoveNext())
{
if (enumerator.Key.ToString().StartsWith("images/"))
{
listOfImageResources.AppendLine(enumerator.Key.ToString());
}
}
}
catch (MissingManifestResourceException)
{
// Ignore any other embedded resources (they won't contain app.xaml)
}
}
MessageBox.Show(listOfImageResources.ToString());
This just displays a list of the names, but hopefully it'll be easy to change this to do whatever you need to.
Any suggestions for improving this code will be greatly appreciated.

Passing MsBuild Command Line Arguments with BuildEngine

I have the following code to build a project from another C# app:
var buildEngine = new Engine();
buildEngine.RegisterLogger(new ConsoleLogger());
var success = buildEngine.BuildProjectFile(pathToCsProjFile);
if(!success)
{
Log.LogIt("On Noes! We Broke!");
}
else
{
Log.LogIt("It Worked!!!!!!");
}
Currently it builds the default configuration (Debug) but I want it to build the release version. If I were invoking MsBuild from the command line I would do something like:
C:\Windows\WinFX\v3.5>msbuild.exe *.proj /ToolsVersion:3.5 /p:Configuration=Release
How do I pass that configuration switch to the build engine?
You'll want to set the property, something like this should do the trick:
var pathToCsProjFile = "";
var buildEngine = new Engine();
var project = new Project(buildEngine);
project.Load(pathToCsProjFile);
project.SetProperty("Configuration", "Release");
var success = project.Build();
Use one of the other overloaded implementations of BuildProjectFile. I believe this one. Create a BuildPropertyGroup and add the properties you want. In this case 'Configuration' = 'Release'

Categories

Resources