I have some generic interface
namespace SimpleLibrary
{
public interface IImported<T>
{
T Value { get; set; }
}
}
and its implementation in another .dll
namespace ImportedLibrary
{
[Export(typeof(IImported<>))]
public class ExportedEntry<T> : IImported<T>
{
public T Value { get; set; }
}
}
Then I make import into another class
namespace SimpleLibrary
{
public class MainEntry
{
[Import]
public IImported<string> Imported { get; set; }
public MainEntry()
{
try
{
var catalog = new DirectoryCatalog(Dir);
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
Imported.Value = "Gomo Sapiens";
}
catch (CompositionException ex)
{
System.Diagnostics.Debugger.Launch();
}
catch (Exception ex)
{
System.Diagnostics.Debugger.Launch();
}
}
private string Dir
{
get
{
var dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "SimpleLibrary");
if (!Directory.Exists(dir))
{
dir = AppDomain.CurrentDomain.BaseDirectory;
}
return dir;
}
}
}
}
Then I create console application, put .dll with class marked [Export] inside bin\Debug folder and run
static void Main(string[] args)
{
MainEntry me = new MainEntry();
Console.WriteLine(me.Imported.Value);
Console.ReadLine();
}
Everything works fine, console displays line "Gomo Sapiens".
However, when I create Wix Installer with some custom action that uses the same MainEntry class and runs after InstallFinalize, import doesn't work:
<Binary Id="ServerActions" SourceFile="path to CA.dll" />
<CustomAction Id="RunMainEntry" BinaryKey="ServerActions" DllEntry="RunMainEntry" Execute="immediate" Return="check" />
<InstallExecuteSequence>
<Custom Action='RunMainEntry' After='InstallFinalize'>NOT Installed AND NOT WIX_UPGRADE_DETECTED</Custom>
</InstallExecuteSequence>
and custom action code
public class CustomActions
{
[CustomAction]
public static ActionResult RunMainEntry(Session session)
{
MainEntry me = new MainEntry();
session.Log(me.Imported.Value);
return ActionResult.Success;
}
}
Custom action throws CompositionException. Notice that my installer initially copies all files (.dll and .exe) inside Program Files(x86)\SimpleLibrary, then calls custom action with MEF composition. I want to highlight that at the moment of calling MainEntry constructor all .dlls do lie inside Program Files folder and MEF sees them but can't find what to import. And problem appears only with generics types, simple interfaces do import.
.NET Framework version is 4.5, C# 6.0. CustomAction.config:
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
</configuration>
Also tried to use MefContrib, situation is the same.
Important thing is that when I run console app manually from Program Files(x86) after installation complete everything again works fine.
So the question is how can I use MEF from Wix's custom actions and if I can't - why?
I'm fairly certain that when running a custom action the installer will unpack the CA.dll binary into a temporary directory and run it from there. If you added anything to the PATH it will not be reflected within the installer's environment so you won't detect any of the new files.
You can verify with running with msi logging (/l*v log.txt) and/or running with procmon also capturing at the same time.
You will probably need to package the dependent DLLs within the self-extracting CA.dll itself.
You can do this by defining <CustomActionContents> in your custom action project and setting the value of this MSBuild property to a ;-separated list of paths to the dependent DLLs. Then when you build, these DLLs along with your custom action dll will be packaged into the CA.dll which will get extracted to a temporary directory for use at runtime for the custom action.
I actually have another answer with better details about what CustomActionContents is and what it does here. It says "space-delimited" in the comment but I've verified it is ;-delimited. This also gives you the location of the file where the MSBuild target and property is defined if you want to read that code as well.
Related
I have a static C# library project which relies on configuration. This library may be used in two scenarios:
From managed C# applications. Easy - the application's app.config file will be used, via ConfigurationManager
From unmanaged C++ applications (using COM)
In case 2. there is no app.config. I would like the library to still be able to use ConfigurationManager but it should explicitly load an XML config file with the same structure as app.config.
This question talks about how to manually load a config file: Loading custom configuration files
But how can the library detect which case 1/2 its in? I would be happy with an InitLib method which passes a config name, or a static initializer, but I can't see how to put the pieces together.
The config file name to use in case 2 could either be passed in directly, or hardcoded as MyAssembly.Lib.config or similar.
So to clarify: you have a class in a class library, the library references System.Configuration.ConfigurationManager.dll, and the class looks does something like this:
using System.Configuration;
namespace FooLibrary
{
public class Foo
{
public Foo()
{
var bar = ConfigurationManager.AppSettings("FooLibrary.Foo.Bar");
}
}
}
Now when you call this class from a console application, say, Baz.exe, there will exist a Baz.exe.config:
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="FooLibrary.Foo.Bar" value="Baz" />
</appSettings>
</configuration>
And all works.
It doesn't work when the library runs in a context without a configuration file. While I think it's possible to give an unmanaged application, say, Qux.exe a configuration file Qux.exe.config which .NET will then read for assemblies loaded from that executable, that situation isn't ideal. Even if that'd work (I think, but am not sure, that it's just a file name convention, not something the runtime does for executables on startup). It's possible that the executable running the assembly is not under your control anyway (e.g. somewhere in System32).
While you could let the library load a configuration file relative to its own location, and to answer your question:
But how can the library detect which case 1/2 its in?
You could just test for the AppSettings key anyway, and if not found, assume you've got no configuration file, and open that of the DLL instead:
public class Foo
{
private readonly string _bar;
public Foo()
{
// Read from .exe.config or Web.config
_bar = ConfigurationManager.AppSettings("FooLibrary.Foo.Bar");
if (string.IsNullOrEmpty(_bar))
{
// Assume that if not present, own config must be loaded:
var dllConfigPath = Assembly.GetExecutingAssembly().Location;
// Will just append ".config" despite its name
Configuration config = ConfigurationManager.OpenExeConfiguration(dllConfigPath);
_bar = config.AppSettings.Settings["test"]?.Value;
if (string.IsNullOrEmpty(_bar))
{
// Not found in both configs, now what?
}
}
}
}
And you should refactor it a bit to read multiple variables. And now you want to unit test this code, so you'll have to provide a configuration file there as well.
These problems are solved by not reading the configuration in class libraries, but in application startup, and passing the appropriate values to the constructor of the classes from the library:
public Foo(string bar) { ... }
// And then from your executable:
var foo = new Foo(ConfigurationManager.AppSettings["FooLibrary.Foo.Bar"));
// And your unit test
var foo = new Foo("FromTest");
But you can't pass constructor parameters through COM, which the compiler will tell you as well. So you'll have to provide a parameterless constructor, and something like an InitLib(string bar) method for COM only.
And if you, despite all the above, still insist on reading the config there, then it would look something like this:
public class Foo
{
private string _bar;
public Foo()
{
// Read from .exe.config or Web.config
_bar = ConfigurationManager.AppSettings["FooLibrary.Foo.Bar"];
// Can't throw here if _bar is empty, maybe we're called from COM
}
public void ReadDllConfig()
{
// Assume that if not present, own config must be loaded:
var dllConfigPath = Assembly.GetExecutingAssembly().Location;
// Will just append ".config" despite its name
Configuration config = ConfigurationManager.OpenExeConfiguration(dllConfigPath);
_bar = config.AppSettings.Settings["test"]?.Value;
// Can throw here if _bar is empty
}
public void FooForCom()
{
// TODO: test _bar again, it can still be null.
}
}
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 Visual Studio, the core project which is a Windows Service Executable, and the Unit Test Project.
The core project has two original files broken out like this
File1.cs:
using static Proj.ConfigHelper;
namespace Proj
{
class MyClass
{
(lots of code)
}
}
File2.cs looks something like this.
namespace Proj
{
static class ConfigHelper
{
public static NameValueCollection AppSettings { get { return ConfigurationManager.AppSettings; } }
public static NameValueCollection CustomSection { get { return ConfigurationManager.GetSection("CustomSection") as NameValueCollection; } }
}
}
Both of those classes are internal and made visible to the unit test project via InternalsVisibleToAttribute.
Inside of the UnitTest project which is a discrete project within the same solution (and so it has its own app.config), calling ConfigHelper.AppSettings results in a 0-item collection, and calling ConfigHelper.CustomSection results in null. If I attempt to unit test a method within File1.cs that depends on those settings, they run as default values as if they were not configured at all. I don't quite understand why this is happening. Can anyone help me understand what I did wrong? It seems as though the ConfigHelper is not loading the App.Config for its own project.
The app.config for the Windows Service Project is set to "always copy" and the app.config for the unit test project is set to "never copy"
The test will use its own config so you need to mirror it. There are work runarounds: Can a unit test project load the target application's app.config file?
I want to create a Kofax Export Connector and register it in the Administration module. I created a Class Library (.NET Framework) with the following code for the setup and release
KfxReleaseSetupScript.cs
namespace Kofax_CoBRA_Export
{
[Guid("b826cc5a-ed80-4fe1-a80f-86a08cca2851")]
public interface IKfxReleaseSetupScript
{
ReleaseSetupData SetupData { get; set; }
KfxReturnValue OpenScript();
KfxReturnValue CloseScript();
KfxReturnValue RunUI();
KfxReturnValue ActionEvent(KfxActionValue action, string dataStringOne, string dataStringTwo);
}
[Guid("39a4f6f6-0de1-40b2-8934-d9a7c2c79468")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("Kofax_CoBRA_Export.KfxReleaseSetupScript")]
internal class KfxReleaseSetupScript : IKfxReleaseSetupScript
{
// Interface Implementation
}
}
KfxReleaseScript.cs
namespace Kofax_CoBRA_Export
{
[Guid("091d8f6c-b4c4-42d4-81aa-3b86b31ce46d")]
public interface IKfxReleaseScript
{
ReleaseData DocumentData { get; set; }
KfxReturnValue OpenScript();
KfxReturnValue CloseScript();
KfxReturnValue ReleaseDoc();
}
[Guid("e034c243-ae35-4823-9f2f-10bb6a6fe5c0")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("Kofax_CoBRA_Export.ReleaseScript")]
internal class KfxReleaseScript : IKfxReleaseScript
{
// Interface Implementation
}
}
My .inf registration file contains this code
[Scripts]
Kofax_CoBRA_Export
[Kofax_CoBRA_Export]
SetupModule=.\bin\Debug\Kofax_CoBRA_Export.dll
SetupProgID=Kofax_CoBRA_Export.KfxReleaseSetupScript
SetupVersion=1.0
ReleaseModule=.\bin\Debug\Kofax_CoBRA_Export.dll
ReleaseProgID=Kofax_CoBRA_Export.KfxReleaseScript
ReleaseVersion=1.0
SupportsNonImageFiles=True
RemainLoaded=True
SupportsKofaxPDF=True
SupportsOriginalFileName=True
SupportsMultipleInstances=False
DisplayName=Kofax_CoBRA_Export
When I select the .inf file in the adminstration module I just get an empty box so there is nothing to install.
I took the information from
Kofax Capture Developer's Guide 10.0.0
KCEC-Text Exporter Sample
Kofax Capture API Reference Guide
Kofax Capture Export Type Library
but I really don't get why I get anything to install in the administration module. Any help would be appreciated.
When providing a relative path, Kofax expects the binaries in its own directory (usually C:\Program Files (x86)\Kofax\CaptureSS\ServLib\Bin on a server, as admin.exe runs using this working path). In your case that would translate to C:\Program Files (x86)\Kofax\CaptureSS\ServLib\Bin\bin\Debug\Kofax_CoBRA_Export.dll.
Note that Kofax recommends copying all custom binaries including your inf file to the server directory, however I prefer creating a sub folders for my code, putting all files there. Then, my inf file would look as follows:
[Scripts]
SmartCAP.KEC.EnergieAG.SAP
[SmartCAP.KEC.EnergieAG.SAP]
SetupModule=SmartCAP.KEC.EnergieAG.SAP.dll
SetupProgID=SmartCAP.KEC.EnergieAG.SAP.Setup
SetupVersion=11.0
ReleaseModule=SmartCAP.KEC.EnergieAG.SAP.dll
ReleaseProgID=SmartCAP.KEC.EnergieAG.SAP
ReleaseVersion=11.0
SupportsNonImageFiles=True
SupportsKofaxPDF=True
Note that Kofax still needs to be able to resolve all dependencies you used in your solution - most definitely internal ones such as the Kofax.ReleaseLib.Interop.DLL - so, you could either copy them there, or - that's what I'd prefer, use a custom assembly resolver in your code, pointing to the server directory.
I am trying to update a custom configuration section of a web.config file during the installation of my product in a custom action. I wanted to use the actual configration classes to do this however when the installer runs it loads my installer class but then the
Configuration.GetSection throw a File Not Found exception as it is trying to load my custom section class from the windows system directory. I managed to get this to work by copying the required assemblies into the windows system directory but this is not an ideal solution as I cannot guarantee I will always have access to that directory.
How else can I solve this problem?
My update code looks like this
[RunInstaller(true)]
public partial class ProjectInstaller : Installer
{
public override void Install(System.Collections.IDictionary stateSaver)
{
//some code here
webConfig = WebConfigurationManager.OpenWebConfiguration("MyService");
MyCustomSection mySection = webconfig.GetSection("MyCustomSection") //<--File Not Found: CustomConfigSections.dll
//Update config section and save config
}
}
My config file looks like this
<configuration>
<configSections>
<section name="myCustomSection" type="CustomConfigSections.MyCustomSection, CustomConfigSections" />
</configSections>
<myCustomSection>
<!-- some config here -->
</myCustomSection>
</configuration>
Hope you would understand the answer the way it is intended.
Assuming that you have setup the installer to have your project output. If Not
Right Click on installer Project click add->Project Output->select your project
and then you can continue using your code.
Moreover if you are using dll except the .net Ones make sure to change there
properties to copylocal = true
If You want to read the element Before Installation use BeforeInstall Event
Handler and try reading your file. ihope your problem will be solved
If in case You want to read the element after installation Right Click On
installer project Click view->customActions->On Install Click Add Custom Action
->Select Application Folder -> Select Primary output from your project and click
ok .
Now Click on primary output and press F4 and in Custom Action Data write
/DIR="[TARGETDIR]\"
and after that write your code as follows.
[RunInstaller(true)]
public class ProjectInstaller : Installer
{
public ProjectInstaller()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
this.AfterInstall += new InstallEventHandler(ProjectInstaller_AfterInstall);
}
void ProjectInstaller_AfterInstall(object sender, InstallEventArgs e)
{
string path = this.Context.Parameters["DIR"] + "YourFileName.config";
// make sure you replace your filename with the filename you actually
// want to read
// Then You can read your config using XML to Linq Or you can use
// WebConfigurationManager whilst omitting the .config from the path
}