How to use IronPython with App.Config?
Asked Answered
U

6

21

I have a class library that is usually called from a .net console or web application. It integrates with various components, and relies on an app.config or web.config.

If I want to utilise the class library from script (i.e. IronPython), how can I get the script to utilise the config file? Ideally I want to be able to choose the config file when I run the script, or by convention (config file sitting alongside the script file).

I don't want to change the ipy.exe.config if possible as this wouldn't scale for multiple configurations without having multiple copies of IronPython?

Any alternatives?

Unruh answered 15/2, 2009 at 11:15 Comment(0)
T
4

I have a working solution with code sample. See my blog: http://technomosh.blogspot.com/2012/01/using-appconfig-in-ironpython.html

It requires a special proxy class which is injected to the ConfigurationManager.

Here is the source for the ConfigurationProxy library:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Configuration.Internal;
using System.Xml;
using System.Collections.Specialized;
using System.Reflection;
using System.IO;

namespace IronPythonUtilities
{
    /// <summary>
    /// A custom app.config injector for use with IronPython code that needs configuration files.
    /// The code was taken and modified from the great work by Tom E Stephens:
    /// http://tomestephens.com/2011/02/making-ironpython-work-overriding-the-configurationmanager/
    /// </summary>
    public sealed class ConfigurationProxy : IInternalConfigSystem
    {
        Configuration config;
        Dictionary<string, IConfigurationSectionHandler> customSections;

        // this is called filename but really it's the path as needed...
        // it defaults to checking the directory you're running in.
        public ConfigurationProxy(string fileName)
        {
            customSections = new Dictionary<string, IConfigurationSectionHandler>();

            if (!Load(fileName))
                throw new ConfigurationErrorsException(string.Format(
                    "File: {0} could not be found or was not a valid cofiguration file.",
                    config.FilePath));
        }

        private bool Load(string file)
        {
            var map = new ExeConfigurationFileMap { ExeConfigFilename = file };
            config = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);

            var xml = new XmlDocument();
            using (var stream = new FileStream(file, FileMode.Open, FileAccess.Read))
                xml.Load(stream);

            //var cfgSections = xml.GetElementsByTagName("configSections");

            //if (cfgSections.Count > 0)
            //{
            //    foreach (XmlNode node in cfgSections[0].ChildNodes)
            //    {
            //        var type = System.Activator.CreateInstance(
            //                             Type.GetType(node.Attributes["type"].Value))
            //                             as IConfigurationSectionHandler;

            //        if (type == null) continue;

            //        customSections.Add(node.Attributes["name"].Value, type);
            //    }
            //}

            return config.HasFile;
        }

        public Configuration Configuration
        {
            get { return config; }
        }

        #region IInternalConfigSystem Members

        public object GetSection(string configKey)
        {
            if (configKey == "appSettings")
                return BuildAppSettings();

            object sect = config.GetSection(configKey);

            if (customSections.ContainsKey(configKey) && sect != null)
            {
                var xml = new XmlDocument();

                xml.LoadXml(((ConfigurationSection)sect).SectionInformation.GetRawXml());
                // I have no idea what I should normally be passing through in the first
                // two params, but I never use them in my confighandlers so I opted not to
                // worry about it and just pass through something...
                sect = customSections[configKey].Create(config,
                                       config.EvaluationContext,
                                       xml.FirstChild);
            }

            return sect;
        }

        public void RefreshConfig(string sectionName)
        {
            // I suppose this will work. Reload the whole file?
            Load(config.FilePath);
        }

        public bool SupportsUserConfig
        {
            get { return false; }
        }

        #endregion

        private NameValueCollection BuildAppSettings()
        {
            var coll = new NameValueCollection();

            foreach (var key in config.AppSettings.Settings.AllKeys)
                coll.Add(key, config.AppSettings.Settings[key].Value);

            return coll;
        }

        public bool InjectToConfigurationManager()
        {
            // inject self into ConfigurationManager
            var configSystem = typeof(ConfigurationManager).GetField("s_configSystem",
                                            BindingFlags.Static | BindingFlags.NonPublic);
            configSystem.SetValue(null, this);

            // lame check, but it's something
            if (ConfigurationManager.AppSettings.Count == config.AppSettings.Settings.Count)
                return true;

            return false;
        }
    }
}

and here is how it can be loaded from Python:

import clr
clr.AddReferenceToFile('ConfigurationProxy.dll')

from IronPythonUtilities import ConfigurationProxy

def override(filename):
    proxy = ConfigurationProxy(filename)
    return proxy.InjectToConfigurationManager()

Finally, a usage sample:

import configproxy
import sys

if not configproxy.override('blogsample.config'):
    print "could not load configuration file"
    sys.exit(1)

import clr
clr.AddReference('System.Configuration')
from System.Configuration import *
connstr = ConfigurationManager.ConnectionStrings['TestConnStr']
print "The configuration string is {0}".format(connstr)
Thermotherapy answered 9/1, 2012 at 9:8 Comment(3)
I created a ConfigProxy implementation in pure IronPython based on your answer and the related blog posts. It is available at software-architects.com/devblog/2012/10/29/… .Shogun
Simon - the pure IronPython implementation is brilliant. Thanks!Thermotherapy
i know that i may sound absurd, but in my case, the above solution of "injecting" our own configuration file, didn't work for a compiled (using pyc.py) version of the IronPython script. okay, actually there is no reason to override configuration file, since we have our own application already and can have a separate config - it is just i myself got mixed up, i developed with ipy.exe and had to use the above solution, while with the compiled version and the above not working (?) the settings were read only when i named my config according to those .net naming standards, e.g. myapp.exe.configTernan
S
2

You can look at the System.Configuration.ConfigurationManager class. More specifically, OpenMappedExeConfiguration method will allow you to load any .config file of your choice. This will give you a Configuration object which exposes the standard AppSettins, ConnectionStrings, SectionGroups and Sections properties.

This approach requires you to pass the name of the config file to your script as a command line argument or to have a code logic to choose the .config file at run-time.

I know no Python, so I would refrain from attempts to post sample code. :-)

Silicify answered 26/2, 2009 at 4:15 Comment(1)
I have looked into this previously, but the business layer is made up of a lot of different 3rd party modules that access the web.config independantly. So I really need to make sure the whole app is accessing the right data.Unruh
N
0

Translating this blog post into Python, this should work:

import clr
import System.AppDomain
System.AppDomain.CurrentDomain.SetData(“APP_CONFIG_FILE”, r”c:\your\app.config”)
Newt answered 15/2, 2009 at 11:36 Comment(4)
This didn't seem to make any differenceUnruh
Did you make sure to call this before the library tries to load its settings? The easiest way to ensure this would be to do it before you clr.AddReference the library.Newt
Yeah its at the top of the script, but doesn't make any difference.Unruh
FWIW, this works excellently with Python.NET! :) (albeit with import System instead of import System.AppDomain)Riser
L
0

You can always include additional sections within config files. In your ipy.exe.config file you can add an include to import external config settings; say myApp.config.

In a batch/command file you can always copy over a specific .config set into myApp.config and therefore run with different config files on demand.

Have a peek at this blog on how to achieve this; http://weblogs.asp.net/pwilson/archive/2003/04/09/5261.aspx

Levitation answered 16/2, 2009 at 16:36 Comment(0)
M
0

For a workaround what I did was fill the AppSettings collection for the ConfigurationManager static class "manually", so I created a PY Script and run an "import" of it on IronPython and the settings then will be available for the class library. However I couldn assing values to the ConnectionStrings collection :(

my script looks like this

import clr
clr.AddReferenceToFileAndPath(r'c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.configuration.dll')
from System.Configuration import *
ConfigurationManager.AppSettings["settingA"] = "setting A value here"
ConfigurationManager.AppSettings["settingB"] = "setting B value here"

It would be nice though to know a way to "load" a custom .config file to the ConfigurationManager class.

Maseru answered 8/1, 2010 at 21:59 Comment(1)
The reason this wouldn't work on the ConnectionStrings collection is because this collection is read-only, thus cannot be modified via code.Thermotherapy
H
0

I attempted to follow the answers above, but found it too complex. If you know exactly what attribute you need from your App.config file, then you can place it directly in the code. For instance, a dll I had imported needed to know the AssemblyPath attribute in my App.Config file.

import clr
import System.Configuration
clr.AddReference("System.Configuration")
from System.Configuration import ConfigurationManager

ConfigurationManager.AppSettings["AssemblyPath"] = 'C:/Program Files (X86)/...

This was all I needed, and the class library I was connecting to was able to see the AssemblyPath attribute it needed to run.

Highbinder answered 10/8, 2017 at 22:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.