Hey all quick question from someone new to C#/programming. I have a console app where one of my string variables in the job is a path to a directory. However, I am always changing it between 2 different paths (1 prod and 1 dev). So I constantly need to recompile/rebuild the solution when I want to change the hard-coded path.
Is there a way to define it somehow in the config and then a variable in the job can point to it somehow? I'd like to just get away from having the path hard-coded in the solution.
Any advice would be appreciated. Thank you!
That is what settings are for. Easiest way to add them is via the project properties:
After you added them you will get a grid to enter a default value.
This value can be accessed in code via:
var path = MyApp.Properties.Settings.Default.PATH;
The default value is stored in the app.config file, which is right next to the MyApp.exe as MyApp.exe.config. If you choose the setting to be "user-changeable" a changed value (after calling MyApp.Properties.Settings.Default.Save();) is stored in %AppData%.
See THIS question on how to transform the app.config depending on build-type.
Add an app.config file to your project by right-clicking it in Solution Explorer. app.config is XML file.
In app.config, under section, add your path like this:
Access path in code like this:
string path = ConfigurationManager.AppSettings["path"];
Then just change the value inside app.config when you want to point to a different path, without recompiling your code.
The alternative to chrfin's answer is to define the variable as so
#if Debug
string myPath = "something";
#else
string myPath = "something else";
then when you choose your build type it will change the line of code that is compiled.
Related
I'm using the roslyn API to write a DiagnosticAnalyzer and CodeFix.
After I have collected all strings and string-interpolations, I want to write all of them to a file but I am not sure how to do this the best way.
Of course I can always simply do a File.WriteAllText(...) but I'd like to expose more control to the user.
I'm also not sure about how to best trigger the generation of this file, so my questions are:
I do not want to hard-code the filename, what would be the best way to expose this setting to the user of the code-analyzer? A config file? If so, how would I access that? ie: How do I know the directory?
If one string is missing from the file, I'd like to to suggest a code fix like "Project contains changed or new strings, regenerate string file". Is this the best way to do this? Or is it possible to add a button or something to visual studio?
I'm calling the devenv.com executable from the commandline to trigger builds, is there a way to force my code-fix to run either while building, or before/after? Or would I have to "manually" load the solution with roslyn and execute my codefix?
I've just completed a project on this. There are a few things that you will need to do / know.
You will probably need to switch you're portable class library to a class library. otherwise you will have trouble calling the File.WriteAllText()
You can see how to Convert a portable class library to a regular here
This will potentially not appropriately work for when trying to apply all changes to document/project/solution. When Calling from a document/project/solution, the changes are precalcuated and applied in a preview window. If you cancel, an undo action is triggered to undo all changes, if you write to a file during this time, and do not register an undo action you will not undo the changes to the file.
I've opened a bug with roslyn but you can handle instances by override the preview you can see how to do so here
And one more final thing you may need to know is how to access the Solution from the analyzer which, Currently there is a hack I've written to do so here
As Tamas said you can use additional files you can see how to do so here
You can use additional files, but I know on the version I'm using resource files, are not marked as additional files by default they are embeddedResources.
So, for my users to not have to manually mark the resource as additonalFiles I wrote a function to get out the Designer.cs files associated with resource files from the csproj file using xDoc you can use it as an example if you choose to parse the csproj file:
protected List<string> GetEmbeddedResourceResxDocumentPaths(Project project)
{
XDocument xmldoc = XDocument.Load(project.FilePath);
XNamespace msbuild = "http://schemas.microsoft.com/developer/msbuild/2003";
var resxFiles = new List<string>();
foreach (var resource in xmldoc.Descendants(msbuild + "EmbeddedResource"))
{
string includePath = resource.Attribute("Include").Value;
var includeExtension = Path.GetExtension(includePath);
if (0 == string.Compare(includeExtension, RESX_FILE_EXTENSION, StringComparison.OrdinalIgnoreCase))
{
var outputTag = resource.Elements(msbuild + LAST_GENERATED_TAG).FirstOrDefault();
if (null != outputTag)
{
resxFiles.Add(outputTag.Value);
}
}
}
return resxFiles;
}
For config files you can use the AdditionalFiles msbuild property, which is passed to the analyzers through the context. See here.
I've got some issues running Puma.Net. I've got all the functions looking fine in the code but when it comes this point:
Value = pumaPage.RecognizeToString();
It then gives an error saying the library dibapi.dll can't be found. But I just can't even add it as a reference it says something like
Can't add reference Make sure the file is accessible and that it is a assembly or Com-Component.
So I gave it all the rights it needs to be read, write & executed. I even gave it full controll on all the users but it just won't work.
Maybe I made a mistake somewhere so here is the full code of the programm.
static void Main()
{
string Image = "V:/Test_images/value.PNG";
Console.WriteLine("Running the Program!");
var pumaPage = new PumaPage(Image);
string Value;
using (pumaPage)
{
pumaPage.FileFormat = PumaFileFormat.RtfAnsi;
pumaPage.EnableSpeller = false;
pumaPage.Language = PumaLanguage.Digits;
Value = pumaPage.RecognizeToString();
}
Console.WriteLine("The Value is" + Value);
Console.ReadLine();
}
I've added the Puma.Net dll and "using Puma.Net;" so it should work. Does someone got any idea what could be wrong?
Here is also the errormessage that appears all the time.
The Error Message which appears
If you need a translation just tell me.
Btw it is a Console Application and I would love to keep it that way. If it is not possible then I can also try to use turn it into a Form Application but that's a whole new part for me so it could take a while to get into it.
You need to copy dibapi.dll to the output folder as described in the documentation:
Steps to add Puma.NET to your project:
1. Add reference to Puma.Net.dll;
2. Make sure that after project building the output folder (i.e. bin\Debug or bin\Release)
contains files Puma.Net.dll and puma.interop.dll. If the last is not present (IDE didn’t
copy it) copy it to the folder manually;
3. Copy dibapi.dll to the output folder;
Is there a way to find out the assembly name at design-time (i.e. not using reflection or runtime APIs such as System.Reflection.Assembly.GetEntryAssembly) from within Visual Studio?
The scenario requires a tool to get the assembly name that a Visual Studio project will eventually compile into.
This is like parsing the AssemblyName property of the .csproj - I am wondering if there are any APIs that can give this information reliably.
Please do not respond back with runtime APIs that use reflection - there is no assembly file present at the time I need the assembly name - just the metadata of the assembly in the csproj file.
if you are calling the tool via a post/pre-build event, this data is very easy to access.
Just go to the "project properties->Build Events" tab, then select either "edit pre-build" or "edit post-build", depending on when you want the tool to run. This should bring up an edit window with the ever helpful "Macros >>" button. Press this and you will be given a heap of macros to use and should be pretty much everything you need.
The "API" you could use is LINQ to XML after all the .csproj file is just xml. (and you can get the location of the .csproj file if you need from the solution file which for some reason is not XML but can be easily parsed)
You can use "TargetName" available in Macros for Post-build events. It will give you the assembly name for your project.
After a quick run through MSDN I found this article which might be a good start for some further research:
Accessing Project Type Specific Project, Project Item, and Configuration Properties
I think you will need to write some regular expression that will give you the value of "AssemblyTitle" attribute in AssemblyInfo.cs file.
Something like this:
public class Assembly
{
public static string GetTitle (string fileFullName) {
var contents = File.ReadAllText (fileFullName); //may raise exception if file doesn't exist
//regex string is: AssemblyTitle\x20*\(\x20*"(?<Title>.*)"\x20*\)
//loading from settings because it is annoying to type it in editor
var reg = new Regex (Settings.Default.Expression);
var match = reg.Match (contents);
var titleGroup = match.Groups["Title"];
return (match.Success && titleGroup.Success) ? titleGroup.Value : String.Empty;
}
}
a question I haven't found an answer to after googling for a long time (then a long break from it and searching again)...
Say, I've got 2 Settings in my application settings. String1 and String2. Say, further, we shipped the product and start adding minor features (more things to configure) and we add a String3.
How, without traversing the .config file manually, can I add missing entries? When shipped as an update (without OneClick btw.) the existing .config file only has String1 and String2.
While defaulting for String3, the application somehow understands that an entry is missing, so it ought to be possible, or so I think, to add this one setting with the default value, so that either another program or a user, doesn't have to type the whole tags manually, without knowing what name it really is.
Thanks in advance!
Qudeid
Hi folks again,
I've just whipped up the following piece of code that works for me as I wanted.
Just to explain it a little:
I first open the config file using the ConfigurationManager, get the corresponding section and set the ForceSave to true, so that section is sure to save.
Then, the "magic" starts. I iterate through all properties of the assembly's settings and let linq do its magic to find if the element exists. If not, I create it and append it to the file.
Note: This piece of code is only for application settings, not so for user settings as this is a different section. I haven't tried/tested it, but it could be as simple as changing this line:
ConfigurationSectionGroup sectionGroup = configFile.SectionGroups["applicationSettings"];
to this line:
ConfigurationSectionGroup sectionGroup = configFile.SectionGroups["userSettings"];
as this is the corresponding name of that section. No guarantees, though.
Here's my code:
/// <summary>
/// Loads own config file and compares its content to the settings, and adds missing entries with
/// their default value to the file and saves it.
/// </summary>
private void UpdateSettings()
{
// Load .config file
Configuration configFile = ConfigurationManager.OpenExeConfiguration(typeof(Settings).Assembly.Location);
// Get the wanted section
ConfigurationSectionGroup sectionGroup = configFile.SectionGroups["applicationSettings"];
ClientSettingsSection clientSettings = (ClientSettingsSection)sectionGroup.Sections[0];
// Make sure the section really is saved later on
clientSettings.SectionInformation.ForceSave = true;
// Iterate through all properties
foreach (SettingsProperty property in Settings.Default.Properties)
{
// if any element in Settings equals the property's name we know that it exists in the file
bool exists = clientSettings.Settings.Cast<SettingElement>().Any(element => element.Name == property.Name);
// Create the SettingElement with the default value if the element happens to be not there.
if (!exists)
{
var element = new SettingElement(property.Name, property.SerializeAs);
var xElement = new XElement(XName.Get("value"));
XmlDocument doc = new XmlDocument();
XmlElement valueXml = doc.ReadNode(xElement.CreateReader()) as XmlElement;
valueXml.InnerText = property.DefaultValue.ToString();
element.Value.ValueXml = valueXml;
clientSettings.Settings.Add(element);
}
}
// Save config
configFile.Save();
}
When you create a setting in Visual Studio (Project -> Properties -> Settings.settings) you assign a value to that setting in the settings editor. From the settings definition (really an XML file) a code file is generated with a class that gives you access to the settings. This class will as a default use the value assigned to the setting in the settings editor. However, when the setting is accessed it will look for a value of that setting in the App.config file. If there is a value it will override the default value in the code generated file.
What this means is that if you add a setting to your project but doesn't provide a value for that setting in the App.config file the value of the setting will be the default value assigned in the settings editor.
To override the value assign it in the App.config file for the application.
Because your application can be split into multiple assemblies created by multiple projects there is no way to automate a process where adding a setting in a dependent assembly creates an entry for that setting the App.config file for the main project. You have to do that yourself I'm afraid.
But that is exactly the beauty of the system: Two .exe projects can have a dependency on the same .dll project that defines a setting. In each .exe project you can override the setting in the App.config file for the .exe project or you can decide to use the default value defined by the .dll project.
I am using the following code within a class:
string filePath = HttpContext.Current.Server.MapPath("~/email/teste.html");
The file teste.html is in the folder
But when it will open the file the following error is being generated:
Object reference not set to an instance of an object.
Don't use Server.MapPath. It's slow. Use this instead, HttpRuntime.AppDomainAppPath. As long as your web site is running, this property is always available to you.
Then use it like this:
string filePath = Path.Combine(HttpRuntime.AppDomainAppPath, "email/teste.html");
if the code is not running from within a thread is executing a httprequest then HttpContext.Current is null (for example when you method is called via BeginInvoke) - see http://forums.asp.net/t/1131004.aspx/1 .
You can always use HttpRuntime see http://msdn.microsoft.com/en-us/library/system.web.httpruntime.aspx
If there is no HttpContext (e.g. when the method is called via BeginInvoke, as Yahia pointed out), the call to HttpContext.Current.Server.MapPath() must fail. For those scenarios, there's HostingEnvironment.MapPath() in the System.Web.Hosting namespace.
string filePath = HostingEnvironment.MapPath("~/email/teste.html");
You can use something like the following piece of code. One thing to note is that I was facing an issue, where I was trying to access a .txt file from within a TestMethod and everything was failing except for this...and yeah it works for non-Unit Test Scenarios too.
string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,#"..\..") + "\\email\\teste.html";
Issue: I had an "Images" folder inside a class library project. But using the above answers, I was not able to get the physical path of the folder to read/write the files inside that folder.
Solution: The below code worked for me to get a physical path in the class library project.
string physicalPath = System.IO.Path.GetFullPath("..\\..\\Images");
I hope, it will help someone who is facing the same issue as me.