Let's say I have some projects, a library Foo and two projects Bar and Baz, which depend on Foo. Foo contains some configuration that will be shared between Bar and Baz, but Bar and Baz will also do some configuration that is different between them.
In Foo, I have a configuration file:
/* /dev/Foo/fooConfig.json */
{
"lorem": "ipsum",
"dolor": "set"
}
and a method that does the initial configuration:
/* /dev/Foo/configuration.cs */
public static IConfigurationBuilder BuildBaseConfiguration()
{
return new ConfigurationBuilder()
.AddJsonFile("fooConfig.json")
}
Then in Bar, I have something similar:
/* /dev/Bar/barConfig.json */
{
"semper": "suspendisse"
}
/* /dev/Bar/Program.cs */
public static void main()
{
BuildBaseConfiguration()
.AddJsonFile("barConfig.json")
.Build();
}
Normally, Foo is distributed as a NuGet package, but during development, I reference it locally by including the following Bar.csproj:
<Reference Include="Foo">
<HintPath>../Foo/bin/Debug/net6.0/Foo.dll</HintPath>
</Reference>
I've made sure that fooConfig.json is being copied to the output directory, and that it is indeed appearing after successfully running a build.
However, after running Bar, I get the following error:
System.IO.FileNotFoundException: The configuration file 'fooConfig.json' was not found and is not optional. The expected physical path was '/dev/Bar/bin/Debug/net6.0/fooConfig.json'.
It would seem that .NET Core is looking for the config file using a relative file path based on the working directory at runtime (/dev/Bar/bin/Debug/net6.0), rather than where the file is actually kept (../Foo/bin/Debug/net6.0/fooConfig.json).
How do I correct this behavior, so that .NET Core references the real location of fooConfig.json?
By default ConfigurationBuilder is using AppContext.BaseDirectory (see the source code) as root for the file search. You can try something like the following to override this behaviour:
// for development environment:
var compositeFileProvider = new CompositeFileProvider(new[]
{
new PhysicalFileProvider(AppContext.BaseDirectory),
new PhysicalFileProvider(Path.GetDirectoryName(typeof(SomeTypeFromFooDll).Assembly.Location))
});
var cfgBuilder = new ConfigurationBuilder()
.SetFileProvider(compositeFileProvider);
// ... rest of cfg setup
P.S.
Personally I think that library should not come with it's config files and I would refactor the code in such way that it exposes the settings type (with some default values) and it is up to the consuming project to handle those.
Related
I am trying to Re-create my BeforeTestRun step to run my setup only once per whole execution not per thread.
I had a look a Custom Deployment steps I have implemented some already but For my setup i need to bring in some values from the app.config file I am trying something like this
my Default.srprofile file contains:
<DeploymentTransformation>
<GlobalSteps>
<Custom type="Test.CustomDeploymentStep, Test"></Custom>
</GlobalSteps>
</DeploymentTransformation>
and my CustomDeploymentStep.cs:
public class CustomDeploymentStep : IDeploymentTransformationStep
{
public static string baseUrl;
public void Apply(IDeploymentContext deploymentContext)
{
baseUrl = ConfigurationManager.AppSettings["URL"];
}
public void Restore(IDeploymentContext deploymentContext)
{
DoSomething();
}
}
My app config contains the following:
<add key="URL" value="http://google.com" />
But That does not work, The ConfigurationManager.AppSettings only returns one key and one value
"key" : "TestProjectRetargetTo35Allowed" "value":"true"
How can I load my configuration from app.config into the Apply() method in CustomDeploymentStep?
Also If there is a better/more efficient way of generating pre-defined data in specflow with thread safe execution, please do let me know
I ran into the same problem once I needed to use custom deployment steps in more than one project in a large solution. This appears to be a bug within the TechTalk.SpecRun.Framework. The error is likely "Error applying global deployment step. Global steps cannot contain test assembly specific settings." and if you look inside the TestAssembly while debugging you will see the TestAssemblyConfigFilePath is null and/or swallowing another exception.
It doesn't register a project specific configuration file. My workaround was to save the config file into debug and access what I need like so:
string appConfigFilePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\App.config";
ExeConfigurationFileMap configMap = new ExeConfigurationFileMap();
configMap.ExeConfigFilename = appConfigFilePath;
var config = ConfigurationManager.OpenMappedExeConfiguration(configMap, ConfigurationUserLevel.None);
var baseUrl = config.AppSettings.Settings["URL"].Value;
I am using .NET Core 3.1. Let's say that I have the following solution structure:
MySolution
ClassLibrary
Files
a.txt
b.txt
GetFile.cs
Project1
Project2
...
And let's say that GetFile.cs has a function ReadFile which reads the file from Files and returns its content.
public class FileReaderService : IFileReaderService
{
private readonly IHostEnvironment _env;
public FileReaderService(IHostEnvironment env)
{
_env = env;
}
public string ReadFile(string fileName)
{
var currentPath = _env.ContentRootPath; // not correct
return "";
}
}
However, when I try to get the current directory in ReadFile with _env.ContentRootPath, it returns the directory of calling project. I don't want it to be dependent on calling project.
How can I achieve that each project will be able to call ReadFile and that correct file from Files will be returned? I need to be able to add, remove and change these files while the app is running.
I have found some questions on SO but they all seem to be outdated. I need a solution which will work on .NET Core.
Instead of using the environment to determine the root file path, set it at the configuration level (ex appSettings.json ) and inject that path into the service itself. Either project can set its own path, use the same one, or both retrieve from some external configuration instead of appSettings.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1
Here's the deal: I want to create a C# console app. When run, this app will look in a particular folder for dll's with classes that implement a particular interface, and then run a method on those dll's.
I haven't done this before but from my reading that should be "easy enough" from an IoC/Ninject perspective. I think you can do something with kernel.Bind() to load assemblies of a certain interface in a certain directory. I think/hope I can figure that part out (if you know otherwise, please do tell!).
But here is my quandary.
Here is a visual to help first:
MainProgramFolder
-- MainProgram.exe
-- MainProgram.exe.config
-- LibraryFolder
----- Library1Folder
--------Library1.dll
--------Library1.dll.config
----- Library2Folder
--------Library2.dll
--------Library2.dll.config
The dll's that implement this interface are technically stand alone apps -- they are just libraries instead of exe's (or, rather, I'd like them to be for IoC purposes). I'd like for them to be run in their own context, with their own app.configs. So for example, MainProgram.exe would bind the ILibrary interface to classes inside Library1.dll and Library2.dll because they implement ILibrary. But inside Library1, it calls ConfigurationManager to get its settings. When I call Class.Method() for each of the bindings from MainProgram, how can I ensure they are referencing their own .config's and not MainProgram.exe.config? (Also, fwiw, these additional libraries will likely not be a part of the assembly or even namespace of the main programs -- we're basically providing a drop folder for an application to kind of "subscribe" to the main program's execution.)
IOW, I know you can attach an app.config to a class library but I wouldn't know how, after the bindings have been resolved from the IOC, to make those dll's "see" its own config rather than the main program's config.
All thoughts appreciated!
Thanks
Tom
First, to load and bind all of your classes you'll need ninject.extensions.conventions, and something like this:
var kernel = new StandardKernel();
/*add relevant loop/function here to make it recurse folders if need be*/
kernel.Bind(s => s.FromAssembliesMatching("Library*.dll")
.Select(type => type.IsClass && type.GetInterfaces().Contains(typeof(ILibrary)))
.BindSingleInterface()
.Configure(x=>x.InSingletonScope()));
To make each instance load its configuration as if it was the entry point you will need to run it in a new app domain. Your ILibrary implementation needs to inherit MarshalByRefObject and be Serializable so that it will run correctly in the alternate appdomain
[Serializable]
public class LibraryA :MarshalByRefObject, ILibrary
You can then add this activation strategy to your kernel that will cause it to swap out instances of ILibrary with an instance loaded in an alternate appdomain with your config file convention before they are returned.
public class AlternateAppDomainStrategy<T> : ActivationStrategy
{
public override void Activate(IContext context, InstanceReference reference)
{
if (reference.Instance.GetType().GetInterfaces().Contains(typeof(T)))
{
var type = reference.Instance.GetType();
var configFilePath = type.Assembly.GetName().Name + ".dll.config";
var file = new FileInfo(configFilePath);
if (file.Exists)
{
var setup = new AppDomainSetup() { ConfigurationFile = file.FullName, ApplicationBase = AppDomain.CurrentDomain.BaseDirectory };
var domain = AppDomain.CreateDomain(type.FullName, null, setup);
var instance = domain.CreateInstanceAndUnwrap(type.Assembly.FullName, type.FullName);
reference.Instance = instance;
}
else
{
throw new FileNotFoundException("Missing config file", file.FullName);
}
}
}
}
And Add it to your kernel
kernel.Components.Add<IActivationStrategy, AlternateAppDomainStrategy<ILibrary>>();
From there you can simply instantiate your ILibrary instances and call methods on them. They will load in their own app domains with their own configs. It gets a lot more complicated if you need to pass things in/out of the instance either via methods or constructor, but from the sound if it you don't so this should be OK.
var libs = kernel.GetAll<ILibrary>();
foreach (var lib in libs)
{
lib.Method();
}
I'm developing a WinForms application for 2 clients. The difference between the clients are only in branding: ClientA gets LogoA.png. ClientB gets LogoB.png. I need to ship the application to them as in installer and as zip file with all executables.
I'm thinking putting the images in different resource files and compile them as satellite assemblies and on the build server, when I produce zip-file and installer, I include only ResourceA for ClientA and ResourceB for ClientB. That is the plan, but I've never done this before.
The documentation says that resource files should be identified by language and culture codes. Both of my clients will run their machines in English (en-GB or en-US). I can ignore the recommendation and call the resources by the name of clients. But would they be picked up by the application? (taking there is only one resource file and machine culture does not match the resource culture code).
Is there a better solution for that?
p.s. I know about compiler directives, but it is making code hacky and dirty. Possibly, in the future, clients will have different text on the screens and that is the perfect case for the resources.
You can create a separate build configuration for each company. Then you can change the .csproj file to have msbuild tasks which will replace default resource file with chosen company resources, here is example how to check current configuration in msbuild.
<PropertyGroup Condition="'$(Configuration)' == 'CompanyABuild'">
//set resource to point to company A
</PropertGroup>
<PropertyGroup Condition="'$(Configuration)' == 'CompanyBBuild'">
//set resource to point to company B
</PropertGroup>
You can add to separate resource file one for clientA another one for clientB (ClientA.resx, Clinetb.resx). Then add a config entry in your app.config file with the name of the resource to use.
Then you need to create a wrapper class which will provide you resources depending on the config value, you need to use dynamic objects and resource managers here is a sample code:
class Program
{
static void Main(string[] args)
{
var res = new CompanyAResource();
var companyResources = new global::System.Resources.ResourceManager("ConsoleApplication1.CompanyAResource", typeof(CompanyAResource).Assembly);
dynamic resources = new DynamicResources(companyResources);
string name = resources.CompanyName;
Console.WriteLine(name);
}
}
public class DynamicResources : System.Dynamic.DynamicObject
{
private ResourceManager resources;
public DynamicResources(ResourceManager resources)
{
this.resources = resources;
}
public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out object result)
{
result = this.resources.GetString(binder.Name);
return true;
}
}
I am configuring log4net to use a separate configuration file. This can be done by adding the following line in your AssemblyInfo.cs file.
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "Log4Net.config", Watch = true)]
This location is relative to the program directory, yet I want it in a user's APPDATA folder. Something like:
ConfigFile = #"C:\Users\UserName\AppData\Roaming\MyApplication\Logging\Log4Net.config"
The problem is that this is not a relative path, i.e., a different usernames are not supported etc. What I really want is the following:
ConfigFile = #"%APPDATA%\MyApplication\Logging\Log4Net.config"
This of course does not work. I was hoping that this might fix it:
ConfigFile = #"${APPDATA}\MyApplication\Logging\Log4Net.config"
or this:
ConfigFile = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
#"MyApplication\Logging\Log4Net.config")
No luck. The two first just thinks it is part of a relative path, the latter is refused since it is in AssemblyInfo:
Error 1 An attribute argument must be a constant expression, typeof
expression or array creation expression of an attribute parameter
type C:\Source\ ... \Tools\Logging\Logging\Properties\AssemblyInfo.cs 39 56 Logging
Do I have to configure the application to use the custom path at startup? I really don't want to, because I want the code to be as agnostic as possible.
I ended up performing the configuration at startup. In the implementation of my logging facade I just said:
class Log4NetLogger : ILogger
{
public Log4NetLogger(...)
{
if (!m_Configured){
string configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), #"MyApplication\Logging\Log4Net.config");
log4net.Config.XmlConfigurator.ConfigureAndWatch(new FileInfo(configFile));
m_Configured = true;
}
...
}
...
}