C# for scripting (csx) location of script file - c#

In F# it's rather easy with predefined identifier __SOURCE_DIRECTORY__
https://stackoverflow.com/a/4861029/2583080
However this identifier does not work in C# scripting (csx files or C# Interactive).
> __SOURCE_DIRECTORY__
(1,1): error CS0103: The name '__SOURCE_DIRECTORY__' does not exist in the current context
Getting current directory in more traditional way will not work either.
Directory.GetCurrentDirectory()
Returns: C:\Users\$USER_NAME$\
new Uri(System.Reflection.Assembly.GetExecutingAssembly().CodeBase).LocalPath;
Returns: C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\CommonExtensions\Microsoft\ManagedLanguages\VBCSharp\InteractiveComponents\

In C# you can take advantage of caller information attributes (available since C# 5 / VS2012). Just declare a method like this:
string GetCurrentFileName([System.Runtime.CompilerServices.CallerFilePath] string fileName = null)
{
return fileName;
}
And call it without specifying the optional parameter:
string scriptPath = GetCurrentFileName(); // /path/to/your/script.csx

In csx, you are can add ExecutionContext as a parameter and access FunctionDirectory from it like so:
using System;
using Microsoft.Azure.WebJobs;
public static void Run(TimerInfo myTimer, ExecutionContext executionContext, ILogger log) {
var dir = executionContext.FunctionDirectory;
log.LogInformation($"Directory: {dir}");
}
ExecutionContext.FunctionDirectory will return the directory the contains the function's function.json file. It doesn't include the trailing .
At this time this seems to be the best documentation for ExecutionContext.
I am trying to find the answer to this question myself, and this was my previous answer.
In csx, the following helper method will return the directory "of the source file that contains the caller".
using System.IO;
...
public static string CallerDirectory([System.Runtime.CompilerServices.CallerFilePath] string fileName = null)
{
return Path.GetDirectoryName(fileName);
}
To call it, don't specify the fileName parameter.
var dir = CallerDirectory();

Related

Using log4net With Visual Studio Debugger

I'm having trouble getting my log4net.config file to load when using Visual Studio in debug mode for an Excel VSTO Plugin. The config file is in the top level directory of my project. I have the property "Copy to Output Directory" set to "Copy Always". This ensures the file is copied to bin/Debug/log4net.config. I can verify this is the case when I build.
However, the file won't load when I run in Debug mode. I gave up on trying to get the file to load automatically and decided to do it by code, as per the OP's code at the bottom of this question.
However, I realised that I needed to use an absolute path to the config file, as relative paths weren't picking it up. On further investigation, I realised that the executing DLL wasn't actually the DLL in the debug/bin folder. It was in the following location:
C:\Users\cbhandal\AppData\Local\assembly\dl3\MO52QQWP.9ZL\K36XZHGN.1PB\230751e6\d09b7fb2_19f6d401
Also the current working directory, as found by System.IO.Directory.GetCurrentDirectory(); was set to "C:\\Users\\cbhandal\\Documents".
Hard-coding the path as an absolute path works as in the following code:
var log4netConfig = "C:\\" + path + "\\Log4net.config";
var log4netInfo = new FileInfo(log4netConfig);
log4net.Config.XmlConfigurator.ConfigureAndWatch(log4netInfo);
But that's not a solution I can deploy. I'm stuck here. Wondering if there's a way to either force Visual studio to copy the .config file to that appdata/temp location, or if there's a way to programatically reference the folder where the original DLL lay- the one that was built. Or if anyone had any other solution?
For me the easiest solution was to use this:
https://stackoverflow.com/a/6963420/4754981
But there are several other solutions on that link for different approaches, each with their caveats.
So mine looks like this:
using System.Reflection;
using System.IO;
using System;
public static class Extensions {
private static string GetDirectory(this Assembly a) {
string codeBase = a.CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
return Path.GetDirectoryName(path);
}
private static void AlterLogPath(this log4net.Repository.ILoggerRepository repo, string newPath, string directory="") {
log4net.Repository.Hierarchy.Hierarchy h = (log4net.Repository.Hierarchy.Hierarchy) repo;
foreach (log4net.Appender.IAppender a in h.Root.Appenders) {
if (a is log4net.Appender.FileAppender) {
var fa = (log4net.Appender.FileAppender)a;
var fileName = Path.GetFileName(fa.File);
fa.File = newPath + (String.IsNullOrEmpty(directory)?"":(directory + Path.DirectorySeparatorChar.ToString())); // edit: filename is attached after next line automatically.
fa.ActivateOptions();
break;
}
}
}
}
and in the bootup (via [assembly: System.Web.PreApplicationStartMethod] or otherwise for asp), or main app..
static void Main() {
var PATH = Assembly.GetExecutingAssembly().GetDirectory() + Path.DirectorySeparatorChar.ToString();
log4net.Config.XmlConfigurator.ConfigureAndWatch(new FileInfo(PATH + "log4net.config"));
log4net.LogManager.GetRepository().AlterLogPath(PATH, "Logs");
}

Azure Function : system.private.corelib : exception while executing function

I am writing a Azure Function for PDF conversion with dependencies on DataLogics PDF conversion and a Nuget package (mlkpwgen) for password generation.
Functions are
using System.IO;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs.Host;
using Newtonsoft.Json;
using System;
using MlkPwgen;
using Datalogics.PDFL;
using System.Diagnostics;
namespace FunctionApp1
{
public static class Function1
{
[FunctionName("Function1")]
public static IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequest req, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");
string name = req.Query["name"];
PDFConversion();
string requestBody = new StreamReader(req.Body).ReadToEnd();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
return name != null
? (ActionResult)new OkObjectResult($"Hello, {name}")
: new BadRequestObjectResult("Please pass a name on the query string or in the request body");
}
public static string PDFConversion()
{
using (Library lib = new Library())
{
String sInput = #"C:\Users\Kunal\Downloads\Indian Management.pdf";
String sOutput = #"C:\Users\Kunal\Downloads\WatermarkedOutput.pdf";
Document doc = new Document(sInput);
string ownerPassword = PasswordGenerator.Generate(length: 32);
string userPassword = PasswordGenerator.Generate(length: 32);
doc.Secure(PermissionFlags.Print | PermissionFlags.HighPrint, ownerPassword, userPassword);
WatermarkParams watermarkParams = new WatermarkParams();
watermarkParams.Rotation = 45.3f;
watermarkParams.Opacity = 0.15f;
watermarkParams.TargetRange.PageSpec = PageSpec.AllPages;
WatermarkTextParams watermarkTextParams = new WatermarkTextParams();
Color color = new Color(0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f);
watermarkTextParams.Color = color;
watermarkTextParams.Text = "Centre Code - Unit - 0101";
Font f = new Font("Arial", FontCreateFlags.Embedded | FontCreateFlags.Subset);
watermarkTextParams.Font = f;
watermarkTextParams.FontSize = 80f;
watermarkTextParams.TextAlign = HorizontalAlignment.Center;
doc.Watermark(watermarkTextParams, watermarkParams);
doc.EmbedFonts();
doc.Save(SaveFlags.Full | SaveFlags.Linearized, sOutput);
Process.Start(#"C:\Users\Kunal\Downloads\WatermarkedOutput.pdf");
return sInput;
}
}
}
}
I am getting the following Exception
"System.Private.CoreLib: Exception while executing function:
Function1. Datalogics.PDFL: The type initializer for
'Datalogics.PDFL.PDFLPINVOKE' threw an exception. Datalogics.PDFL: The
type initializer for 'SWIGExceptionHelper' threw an exception.
Datalogics.PDFL: Unable to load DLL 'DL150PDFLPINVOKE': The specified
module could not be found. (Exception from HRESULT: 0x8007007E)."
The same code works fine as a Console application. What am I missing here?
If fixing the hard-coded file names still doesn't help, the error sounds like a permission exception.
Azure Functions run on App Service, which has a sandbox for all the code, where some calls are not allowed. E.g. GDI32 which is used extensively by PDF generation libraries.
Read more in Azure Web App sandbox.
Thanks for reading through the question and trying to answer.
I found that even after adding reference to the Datalogics.PDFL.dll, the code was failing.
So i copied all the other dll's into the bin\debug folder and now the code works fine
DL150ACE.dll
DL150AdobeXMP.dll
DL150AGM.dll
DL150ARE.dll
DL150AXE8SharedExpat.dll
DL150BIB.dll
DL150BIBUtils.dll
DL150CoolType.dll
DL150JP2KLib.dll
DL150PDFL.dll
DL150PDFLPINVOKE.dll
DL150pdfport.dll
DL150pdfsettings.dll
DotNETViewerComponent.dll
Per this MS Forums post:
Azure Functions does not provide support for loading native binaries in its current release. Even if we were able to install this package, you may still encounter errors when those native dlls are loaded during runtime.
So this is expected behavior when trying to call native binaries. Please contact our Support department if you have any more questions about getting started using the PDF Library.

nunit test working directory

I have the following code (sample1.evol - file attached to my unit test project):
[Test]
public void LexicalTest1()
{
var codePath = Path.GetFullPath(#"\EvolutionSamples\sample1.evol");
//.....
}
I found that the working directory of test execution is not the assembly directory: (in my case codepath variable assigned to d:\EvolutionSamples\sample1.evol).
So, how can I change the execution working directory (without hardcode)? What will be the best practice to load any files attached to test case?
You can use following to get the directory of assembly running the code something like
var AssemblyDirectory = TestContext.CurrentContext.TestDirectory
I use this for integration tests that need to access data files.
On any machine the test needs to run create a system environment variable named TestDataDirectory that points to the root of where your test data is.
Then have a static method that gets the file path for you..
public static class TestHelper
{
const string EnvironmentVariable = "TestDataDirectory";
static string testDataDir = Environment.GetEnvironmentVariable(EnvironmentVariable);
public static string GetTestFile(string partialPath)
{
return Path.Combine(testDataDir, partialPath);
}
}
...
[Test]
public void LexicalTest1()
{
var codePath = TestHelper.GetTestFile(#"\EvolutionSamples\sample1.evol");
//.....
}
I am using this code:
var str = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
if (str.StartsWith(#"file:\")){
str = str.Substring(6);
}
Getting in str variable the assembly directory.
We were having a problem where tests run using ReSharper and NCrunch would work, but the native VS Test Runner would not be able to find the files, when given just a relative file path for the test to use. I solved it by creating a function that you pass the relative test file path into, and it will give you the absolute file path.
private static string _basePath = Path.GetDirectoryName(typeof(NameOfYourTestClassGoesHere).Assembly.Location);
private string GetAbsoluteTestFilePath(string relativePath) => Path.Combine(_basePath, relativePath);
You would then use the function like so:
var input = File.ReadAllLines(GetAbsoluteTestFilePath(#"TestData/YourTestDataFile.txt"));

Roslyn: workspace loads in console application but not in msbuild task

I have a custom msbuild task with this command:
var workspace = Workspace.LoadStandAloneProject(csprojPath);
When I run it, it throws the following error:
System.InvalidCastException was unhandled by user code
Message=Unable to cast transparent proxy to type 'Roslyn.Utilities.SerializableDataStorage'.
Source=Roslyn.Services
StackTrace:
at Roslyn.Utilities.RemoteServices.CreateInstance[T]()
at Roslyn.Services.Host.TemporaryStorageServiceFactory.CreateService(IWorkspaceServiceProvider workspaceServices)
at Roslyn.Services.Host.WorkspaceServiceProviderFactory.Provider.c__DisplayClass7.b__4()
at Roslyn.Utilities.NonReentrantLazy`1.get_Value()
at Roslyn.Services.Host.WorkspaceServiceProviderFactory.Provider.GetService[TWorkspaceService]()
at Roslyn.Services.SolutionServices..ctor(IWorkspaceServiceProvider workspaceServices, ILanguageServiceProviderFactory languageServicesFactory)
at Roslyn.Services.Solution..ctor(SolutionId id, String filePath, VersionStamp version, VersionStamp latestProjectVersion, ILanguageServiceProviderFactory languageServiceProviderFactory, IWorkspaceServiceProvider workspaceServices)
at Roslyn.Services.Host.SolutionFactoryServiceFactory.SolutionFactoryService.CreateSolution(SolutionId id)
at Roslyn.Services.Host.TrackingWorkspace.CreateNewSolution(ISolutionFactoryService solutionFactory, SolutionId id)
at Roslyn.Services.Host.TrackingWorkspace..ctor(IWorkspaceServiceProvider workspaceServiceProvider, Boolean enableBackgroundCompilation, Boolean enableInProgressSolutions)
at Roslyn.Services.Host.HostWorkspace..ctor(IWorkspaceServiceProvider workspaceServiceProvider, Boolean enableBackgroundCompilation, Boolean enableInProgressSolutions, Boolean enableFileTracking)
at Roslyn.Services.Host.LoadedWorkspace..ctor(ILanguageServiceProviderFactory languageServiceProviderFactory, IWorkspaceServiceProvider workspaceServiceProvider, IProjectFileService projectFileFactsService, IDictionary`2 globalProperties, Boolean enableBackgroundCompilation, Boolean enableFileTracking)
at Roslyn.Services.Host.LoadedWorkspace..ctor(ExportProvider exportProvider, Boolean solutionLoadOnly, Boolean enableFileTracking)
at Roslyn.Services.Host.LoadedWorkspace..ctor(Boolean enableFileTracking)
at Roslyn.Services.Host.LoadedWorkspace.LoadStandAloneProject(String projectFileName, String configuration, String platform, String language, Boolean enableFileTracking)
at Roslyn.Services.Workspace.LoadStandAloneProject(String projectFileName, String configuration, String platform, String language, Boolean enableFileTracking)
...
The same code, when run in a console application, with the same project, runs fine.
Any ideas? Googling has not been helpful!
Here's a sample MsBuild task with Roslyn.
In order to reconstruct the command line needed by the Workspace.LoadProjectFromCommandLineArguments method, we have to pass some info from the msbuild file into our task.
The referenced assemblies: the
#(ReferencePath) item group.
The cs files to be compiled: the #(Compile) item group.
The base directory: the $(MSBuildProjectDirectory) built-in property.
That's all that Roslyn needs to parse your source files. (See the note at the end of this post.)
So create a C# class library project.
These are the project references that you'll need:
Microsoft.Build.Framework
Microsoft.Build.Utilities.v4.0
Roslyn.Compilers
Roslyn.Services
The code for the custom MsBuild task:
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Roslyn.Services;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace RoslynMsBuildTask
{
public class RoslynTask : Task
{
[Required]
public ITaskItem[] ReferencePath { get; set; }
[Required]
public ITaskItem[] Compile { get; set; }
[Required]
public ITaskItem BaseDirectory { get; set; }
public override bool Execute()
{
Log.LogMessage(MessageImportance.High, "RoslynTask.Execute called...\n");
// Format the command line with the minimal info needed for Roslyn to create a workspace.
var commandLineForProject = string.Format("/reference:{0} {1}",
ReferencePath.Select(i => i.ItemSpec).ToSingleString(",", "\"", "\""),
Compile.Select(i => i.ItemSpec).ToSingleString(" ", "\"", "\""));
// Create the Roslyn workspace.
var workspace = Workspace.LoadProjectFromCommandLineArguments("MyProject", "C#", commandLineForProject, BaseDirectory.ItemSpec);
// Make sure that Roslyn actually parsed the project: dump the source from a syntax tree to the build log.
Log.LogMessage(MessageImportance.High, workspace.CurrentSolution.Projects.First()
.Documents.First(i => i.FilePath.EndsWith(".cs")).GetSyntaxRoot().GetText().ToString());
return true;
}
}
public static class IEnumerableExtension
{
public static string ToSingleString<T>(this IEnumerable<T> collection, string separator, string leftWrapper, string rightWrapper)
{
var stringBuilder = new StringBuilder();
foreach (var item in collection)
{
if (stringBuilder.Length > 0)
{
if (!string.IsNullOrEmpty(separator))
stringBuilder.Append(separator);
}
if (!string.IsNullOrEmpty(leftWrapper))
stringBuilder.Append(leftWrapper);
stringBuilder.Append(item.ToString());
if (!string.IsNullOrEmpty(rightWrapper))
stringBuilder.Append(rightWrapper);
}
return stringBuilder.ToString();
}
}
}
To demonstrate that it actually works, add the following lines at the end of your csproj file (just before the closing Project tag). But only if the project was already built successfully and it can find your task dll in the output folder.
<Target Name="AfterBuild" DependsOnTargets="RoslynTask"/>
<UsingTask AssemblyFile="$(OutputPath)\RoslynMsBuildTask.dll" TaskName="RoslynMsBuildTask.RoslynTask" />
<Target Name="RoslynTask">
<RoslynTask ReferencePath="#(ReferencePath)" Compile="#(Compile)" BaseDirectory="$(MSBuildProjectDirectory)" />
</Target>
It will dump the source of your first cs file to the build output.
Note that other csc.exe switches (like ConditionalDirectives, output type, etc) may also matter depending on the type of analysis you are trying to do. You can also pass them to your task using this pattern. See $(MSBuildToolsPath)\Microsoft.CSharp.targets file, CoreCompile target, Csc task for a complete list of properties that MsBuild passes to csc.exe.
This is a limitation of MSBuild. Roslyn can't invoke MSBuild recursively during a build to determine the project properties/files/references. In order to create a Roslyn IProject during in a build task, try using the LoadFromCommandLineArgs() method instead. You'll need to construct your task to take the same arguments as the CscTask ends up passing to the compiler.
Hope this helps!

Print the source filename and linenumber in C#

Is there any way to retrieve the current source filename and linenumber in C# code and print that value in the console output? Like LINE and FILE in C?
Please advise.
Many thanks
Anders Hejlsberg presented new API for that in BUILD keynote:
Print current file name, method name and line number
private static void Log(string text,
[CallerFilePath] string file = "",
[CallerMemberName] string member = "",
[CallerLineNumber] int line = 0)
{
Console.WriteLine("{0}_{1}({2}): {3}", Path.GetFileName(file), member, line, text);
}
Test:
Log(".NET rocks!");
Output:
Program.cs_Main(11): .NET rocks!
What's going on here?
You define a method with optional parameters and decorate them with special attributes. If you call method without passing actual arguments (leave defaults) - the Framework populates them for you.
This answer is outdated! See #taras' answer for more recent information.
No constant :(
What you can do is a lot uglier :
string currentFile = new System.Diagnostics.StackTrace(true).GetFrame(0).GetFileName();
int currentLine = new System.Diagnostics.StackTrace(true).GetFrame(0).GetFileLineNumber();
Works only when PDB files are available.
You can use the StackTrace object from the System.Diagnostics namespace but the information will only be available if the PDB files are there.
PDB files are generated by default for both the Debug and Release builds the only difference is that Debug is setup to generate a full debug info where as the Release build is setup to only generate a pdb (full/pdb-only).
Console.WriteLine(new StackTrace(true).GetFrame(0).GetFileName());
Console.WriteLine(new StackTrace(true).GetFrame(0).GetFileLineNumber());
There are no constants defined for that as of now.
The .NET way of doing it is using StackTrace class.
It however works only for Debug builds. So in case you use it, you can have the code using StackTrace between
#if DEBUG
//your StackTrace code here
#endif
You can read about using #if preprocessors for your DEBUG vs. RELEASE builds in the following Stackoverflow thread.
C# if/then directives for debug vs release
EDIT: Just in case you still need this debugging information in release builds, read the following answer on Stackoverflow:
Display lines number in Stack Trace for .NET assembly in Release mode
If you want some more internal detail, but you don't specifically need filename and line number, you can do something like this:
System.Diagnostics.Debug.Print(this.GetType().ToString() + " My Message");
This has an advantage over printing out the filename in that if you put this in a parent class, it will print out the child class name that is actually running the code.
If you wanted to write your own version of Debug.Assert, then here's a more complete answer:
// CC0, Public Domain
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System;
public static class Logger {
[Conditional("DEBUG")]
public static void Assert(bool condition, string msg,
[CallerFilePath] string file = "",
[CallerMemberName] string member = "",
[CallerLineNumber] int line = 0
)
{
// Debug.Assert opens a msg box and Trace only appears in
// a debugger, so implement our own.
if (!condition)
{
// Roughly follow style of C# error messages:
// > ideone.cs(14,11): error CS1585: Member modifier 'static' must precede the member type and name
Console.WriteLine($"{file}({line}): assert: in {member}: {msg}");
// Or more precisely match style with a fake error so error-parsing tools will detect it:
// Console.WriteLine($"{file}({line}): warning CS0: {msg}");
}
}
}
class Program {
static void Main(string[] args) {
Logger.Assert(1+1 == 4, "Why not!");
}
}
Try it online.

Categories