I'm building a project installer. I have a static class for config, as each project this installer can install has different settings. These are stored in an internal static class rather than an external json because they are not user configurable, and typically set once and forgotten, but we do rebuild using the same config whenever a patch is released (We just change the APPLICATION_VERSION.
The config looks like this:
internal static class Config
{
public const string APPLICATION_NAME = "Application 42";
public const string APPLICATION_VERSION = "1.0.69";
public const string OUTPUT_EXE = $"{APPLICATION_NAME} {APPLICATION_VERSION} Installer.exe";
}
In the .csproj, I was hoping to rename the resultant Installer.exe to OUTPUT_EXE, but I'm a bit lost.
I have tried the following, and variations on a theme:
<Target Name="PostPublish" AfterTargets="Publish">
<Copy SourceFiles="$(PublishUrl)\$(PublishFile)" DestinationFiles="$(PublishUrl)\$([Installer]::Config.OUTPUT_EXE)" />
</Target>
I think that part of the issue is I can't access the internal const, so how do I do that, and also what is the variable holding the Publish exe?
Related
I installed System.Configuration.ConfigurationManager -Version 4.7.0 in one of my project that is as a class library project and then I added app.config file in it.
I've applied ASP.NET Core 3.1 in my project.
Now I want to get value of section.
For this purpose I did it like this:
namespace Alpha.Infrastructure.PaginationUtility
{
public class PagingInfo
{
public virtual int DefaultItemsPerPage { get; set; } = int.Parse(System.Configuration.ConfigurationManager.AppSettings["DefaultItemsPerPage"]);
}
}
But I got "ArgumentNullException: Value cannot be null" error!
How can I solve this problem?
App.config file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="DefaultItemsPerPage" value="3"/>
</appSettings>
</configuration>
SOLUTION
To solve your problem add the App.config file to the ENTRY POINT project.
This is your executable project like WinForms or WPF or Console App.
Not to the Class Library project.
The reason is when the app compiles the App.config gets renamed to "MyProjectName.Ext.config".
For example if you have Class Library project called ConfigUtils the file will be output as ConfigUtils.dll.config.
But ConfigurationManager expects the name of the entry point plus config.
So if your ConfigUtils is referenced from let's say MyApp Console project. Then the ouput file will be MyApp.exe.config
So for the ConfigurationManager to work right off without any further meddling you need to place your App.config file in the entry point project.
.NET CORE WAY
I recommend NOT to use ConfigurationManager...
Just use the new way of doing it!
To demonstrate how to do it in .NET Core I created a console app using .NET Core 3.1.3
I added also an extension method to wrap up the functionality for reusability.
First my code:
using Microsoft.Extensions.Configuration;
using System;
class Program
{
private static IConfiguration _configuration;
static void Main(string[] args)
{
_configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", true, true)
.Build();
var itemsPerPage = _configuration.GetValue<int>("appSettings", "DefaultItemsPerPage");
Console.WriteLine("Items Per Page:" + itemsPerPage.ToString());
Console.ReadLine();
}
}
public static class ConfigExtenstions
{
public static T GetValue<T>(this IConfiguration configuration, string configSection, string keyName)
{
return (T)Convert.ChangeType(configuration[$"{configSection}:{keyName}"], typeof(T));
}
private static T GetValue<T>(string configSection, string configSubSection, string keyName)
{
return (T)Convert.ChangeType(Configuration[$"{configSection}:{configSubSection}:{keyName}"], typeof(T));
}
}
My appsettings.json file looks like this:
{
"appSettings": {
"DefaultItemsPerPage": 3
}
}
The confuguration file can be modeled however you want. In this case I made it look like your app.config file.
Also I right clicked on the appsettings.json file in visual studio and set to "Copy Always".
For all of this to work you need to add 2 NuGet packages like this:
NOTES:
You can use other "Providers" to pull configuration from XML, INI and other types of files and sources. For that you need to add other NuGet packages etc.
This link even though it talks about ASP.NET Core Configuration it is very useful because the same concepts apply.
I have two projects in my solution, ChatProject and ChatProjectTest.
ChatProjectTest has a single class file that has many methods that test methods in ChatProject.
My problem is that ChatProject uses some files, like data.bin and log.txt, that are stored in the .exe folder (that is ChatProject\bin\Debug\ChatProject.exe). I want ChatProjectTest to use the same files, and not go ahead and create or load new files in its own .exe folder (ChatProjectTest\bin\Debug\ChatProject.exe), like it does currently.
To be clear, the file paths are stored in constant variables in ChatProject like this:
private static string DATABASE_NAME = "data.bin";
I didn't hardcode their paths.
One solution can be configuring your projects to put all the output files into the same the same bin/ directory under your solution root (instead of two separate bin/ folders).
To do this, go to the "Build" tab under project settings and set "Output path" to
$(SolutionDir)Bin\$(Configuration)\
Repeat this for both of your projects.
If you don't specify a path to a file it looks in the current directory for them.
To use the same ones, you either have to specify some sort of relative path from the Test Output, or build the tests into the same folder as the Project.
You may allow the target file path to be injected e. g. via an optional constructor parameter:
public class SampleClass
{
private readonly string OutputDirectory;
public SampleClass(string outputDirectory = ".")
{
OutputDirectory = outputDirectory;
}
public void WriteData(string data)
{
File.AppendAllText($"{OutputDirectory}\\data.bin", data);
}
}
[TestClass]
public class SampleClassTest
{
[TestMethod]
public void WriteDataTest()
{
var sut = new SampleClass("..\\..\\..\\ChatProject\\bin\\Debug");
sut.WriteData("data");
}
}
I do apologise if i didn't asked correctly in the title.. i don't know how to ask or what to call for what i need.
-
Let's say that i have a simple Application called "TestApp" written in C#.
Inside that application, i have the next variables:
int clientid = 123;
string apiurl = "http://somesite/TestApp/api.php";
When i have a new client, i need to create a new special TestApp.exe just for him, changing the 'clientid' variable inside the code.
It's possible to automate this process? To change that variable automatically and export an exe without for me to interfere with the process?
-
I asked this because i think/or i'm sure that it's possible because of the next popular examples:
http://download.cnet.com/2701-20_4-1446.html?tag=sideBar;downloadLinks
[ It creates a special .exe with a predefinied link from where to download the real file ]
http://torrent2exe.com/
[ It's embedding the .torrent file to a special .exe just with some custom variables changed, like torrent name or download size ]
Again, i do apologise if i didn't asked my question correctly and for my bad english, trying my best.
So you have two parts to your question:
You want to have variables inside the program based on client for your app
You want to automate the process of making the settings changes.
To make custom settings:
Use AppSettings.
First, add a reference to System.Configuration assembly.
In your app.config file:
<configuration>
<appSettings>
<add key="ClientID" value="123" />
<add key="ApiUrl" value="http://somesite/TestApp/api.php" />
</appSettings>
</configuration>
In your code, to read the settings:
using System;
using System.Configuration;
class Program
{
private static int clientID;
private static string apiUrl;
static void Main(string[] args)
{
// Try to get clientID - example that this is a required field
if (!int.TryParse( ConfigurationManager.AppSettings["ClientID"], out clientID))
throw new Exception("ClientID in appSettings missing or not an number");
// Get apiUrl - example that this isn't a required field; you can
// add string.IsNullOrEmpty() checking as needed
apiUrl = ConfigurationManager.AppSettings["apiUrl"];
Console.WriteLine(clientID);
Console.WriteLine(apiUrl);
Console.ReadKey();
}
}
More about AppSettings on MSDN
To automate the creation of settings:
This all depends on how complex you want to get.
When you build your project, your app.config file becomes TestApp.exe.config
You can use ConfigurationManager class to write Config files.
Further, you can write a little Exe that writes the config file with custom settings and execute it as part of a build action. Lots of ways to accomplish automation which depend on how you intend to deploy your application.
A quick example of writing an app.config file appSettings section programmatically:
public static void CreateOtherAppSettings()
{
Configuration config =
ConfigurationManager.OpenExeConfiguration("OtherApp.config");
config.AppSettings.Settings.Add("ClientID", "456");
config.AppSettings.Settings.Add("ApiUrl", "http://some.other.api/url");
config.Save(ConfigurationSaveMode.Modified);
}
We use Hudson to build our projects, and Hudson conveniently defines environment variables like "%BUILD_NUMBER%" at compile time.
I'd like to use that variable in code, so we can do things like log what build this is at run time. However I CAN NOT do System.Environment.GetEnvironmentVariable because that is accessing the run-time environment, what I want is something like:
#define BUILD_NUM = %BUILD_NUMBER%
or
const string BUILD_NUM = %BUILD_NUMBER%
Except I don't know the syntax. Can someone please point me in the right direction? Thanks!
Okay here's what I wound up doing. It's not very elegant, but it works. I created a pre-build step that looks like this:
echo namespace Some.Namespace > "$(ProjectDir)\CiInfo.cs"
echo { >> "$(ProjectDir)\CiInfo.cs"
echo ///^<summary^>Info about the continuous integration server build that produced this binary.^</summary^> >> "$(ProjectDir)\CiInfo.cs"
echo public static class CiInfo >> "$(ProjectDir)\CiInfo.cs"
echo { >> "$(ProjectDir)\CiInfo.cs"
echo ///^<summary^>The current build number, such as "153"^</summary^> >> "$(ProjectDir)\CiInfo.cs"
echo public const string BuildNumber = ("%BUILD_NUMBER%" == "" ? #"Unknown" : "%BUILD_NUMBER%"); >> "$(ProjectDir)\CiInfo.cs"
echo ///^<summary^>String of the build number and build date/time, and other useful info.^</summary^> >> "$(ProjectDir)\CiInfo.cs"
echo public const string BuildTag = ("%BUILD_TAG%" == "" ? #"nohudson" : "%BUILD_TAG%") + " built: %DATE%-%TIME%"; >> "$(ProjectDir)\CiInfo.cs"
echo } >> "$(ProjectDir)\CiInfo.cs"
echo } >> "$(ProjectDir)\CiInfo.cs"
Then I added "CiInfo.cs" to the project, but ignored it from version control. That way I never have to edit it or commit it, and the project always has a constant available that is the latest build number and time.
One way to do it is to add a build-step before compilation which does a regex replace in the appropriate source file(s) for %BUILD_NUMBER%.
One possibility is to use T4 to generate your configuration class with all the constants instantiated. T4 is well-integrated into MSVS, no need for your own custom build step.
define does not allow you to define contants in C# like you can in C/C++.
From this page:
The #define directive cannot be used to declare constant values as is typically done in C and C++. Constants in C# are best defined as static members of a class or struct. If you have several such constants, consider creating a separate "Constants" class to hold them.
If you are looking to reflect the build number in you AssemblyInfo class, most build tools support generating that class at build time. MSBuild has a task for it. As does NAnt. Not sure how Hudson does this.
I had a similar problem.
I was developing a Xamarin mobile app with an ASP.Net backend. I had a settings class that contains the backend server URL:
namespace Company.Mobile
{
public static class Settings
{
#if DEBUG
const string WebApplicationBaseUrl = "https://local-pc:44335/";
#else
const string WebApplicationBaseUrl = "https://company.com/";
#endif
}
}
It has different values for debug and release configurations. But this didn't work when several developers started working on the project. Every dev machine had its IP address, and mobile phones need to connect using unique IP addresses.
I needed to set the constant value from a file or an environment variable on each dev machine. This is where Fody fits in. I used it to create an in solution weaver. Here are the details.
I place my Settings class in the Xamarin app project. This project has to include the Fody Nuget package:
<ItemGroup>
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Fody" Version="6.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<WeaverFiles Include="$(SolutionDir)Company.Mobile.Models\bin\Debug\netstandard2.0\Company.Mobile.Models.dll" WeaverClassNames="SetDevServerUrlWeaver" />
</ItemGroup>
I make my setup work on Debug configuration only, because I don't want the substitution to happen on Release builds.
The weaver class is placed in a class library project (Company.Mobile.Models) that the mobile project depends on (you needn't and shouldn't have this dependency, but Fody docs says clearly that the project that contains the weaver must be built before the project that emits the weaved assembly). This library project includes the FodyHelpers Nuget package:
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<PackageReference Include="FodyHelpers" Version="6.2.0" />
</ItemGroup>
The weaver class is defined as follows:
#if DEBUG
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Fody;
namespace Company.Mobile.Models
{
public class SetDevServerUrlWeaver : BaseModuleWeaver
{
private const string SettingsClassName = "Settings",
DevServerUrlFieldName = "WebApplicationBaseUrl",
DevServerUrlSettingFileName = "devServerUrl.txt";
public override void Execute()
{
var target = this.ModuleDefinition.Types.SingleOrDefault(t => t.IsClass && t.Name == SettingsClassName);
var targetField = target.Fields.Single(f => f.Name == DevServerUrlFieldName);
try
{
targetField.Constant = File.ReadAllText(Path.Combine(this.ProjectDirectoryPath, DevServerUrlSettingFileName));
}
catch
{
this.WriteError($"Place a file named {DevServerUrlSettingFileName} and place in it the dev server URL");
throw;
}
}
public override IEnumerable<string> GetAssembliesForScanning()
{
yield return "Company.Mobile";
}
}
}
#endif
And here's the FodyWeavers.xml file placed in the Mobile app project:
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<SetDevServerUrlWeaver />
</Weavers>
The devServerUrl.txt simply contains my local IP:
https://192.168.1.111:44335/. This file must not be added to source control. Add it to your source control ignore file so that each developer have his version.
You may easily read the substituted value from an environment variable (System.Environment.GetEnvironmentVariable) or whatever place instead of a file.
I hoped there had been a better way to do this, like Roslyn, or this attribute that seems to do the job, but it doesn't.
I am using an app.config file to store the dynamic parameters of my application. The problem is, when I change a value in app.config file, and start the application, it doesn't load the new value from config file. Seems like the values in app.config file are being read and embedded in exe file only at compile time!
This is how I read the config file:
public class Helper
{
static Helper()
{
Foo = ConfigurationManager.AppSettings["Foo"];
}
public static string Foo { get; set; }
}
Am I missing something?
Are you sure you are changing the correct file? You don't want to change the app.config file, but the <exename>.exe.config file, in the same directory as the .exe
The app.config file is what you edit in the ide, but when you compile your app this file is renamed to <exename>.exe.config and copied to the output directory when you compile. The .exe looks for a file with the same name as itself with the .config extension when looking for the default configuration.
The static nature of your class and method may be causing you the issue. Maybe refactor it to the following...
public static class Helper
{
public static string Foo
{
get
{
return ConfigurationManager.AppSettings["Foo"];
}
}
}
Actually, thinking about it, it doesn't help you a great deal since ConfigurationManager.AppSettings["Foo"] is already (effectively) a static call - you're just adding another layer of abstraction that may well not be required.