如何使用IronPython与App.Config?

21

我有一个类库,通常从.NET控制台或Web应用程序中调用。它集成了各种组件,并依赖于app.config或web.config。

如果我想要从脚本(例如IronPython)利用这个类库,我该如何让脚本使用config文件呢?理想情况下,我希望能在运行脚本时选择配置文件,或者按照约定(将配置文件放在脚本文件旁边)。

如果可能的话,我不想更改ipy.exe.config,因为这样做在多个配置情况下无法扩展,必须拥有IronPython的多个副本。有没有其他替代方案?

6个回答

4
我有一个带有代码示例的可行解决方案。请参阅我的博客: http://technomosh.blogspot.com/2012/01/using-appconfig-in-ironpython.html 它需要一个特殊的代理类,该类被注入到ConfigurationManager中。
以下是ConfigurationProxy库的源代码:
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;
        }
    }
}

以下是Python中如何加载它的方法:

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

from IronPythonUtilities import ConfigurationProxy

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

最后,提供一个使用示例:

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)

1
我根据你的回答和相关博客文章,使用纯IronPython创建了一个ConfigProxy实现。它可以在http://www.software-architects.com/devblog/2012/10/29/appconfig-in-IronPython-without-additional-assemblies上获取。 - Simon Opelt
Simon - 纯IronPython实现真是太棒了。谢谢! - Moshe
我知道我可能听起来很荒谬,但在我的情况下,“注入”我们自己的配置文件的上述解决方案对使用pyc.py编译的IronPython脚本版本无效。好吧,实际上没有理由覆盖配置文件,因为我们已经有了自己的应用程序,并且可以拥有单独的配置文件 - 只是我自己搞混了,我使用ipy.exe进行开发,并且必须使用上述解决方案,而在编译版本和上述解决方案不起作用时(?),只有当我根据那些.NET命名标准命名我的配置文件时,设置才会被读取,例如myapp.exe.config。 - hello_earth

2
您可以查看 System.Configuration.ConfigurationManager 类。更具体地说,OpenMappedExeConfiguration 方法将允许您加载任何 .config 文件。这将为您提供一个 Configuration 对象,该对象公开了标准的 AppSettins、ConnectionStrings、SectionGroups 和 Sections 属性。
这种方法要求您将配置文件的名称作为命令行参数传递给脚本,或者在运行时具有选择 .config 文件的代码逻辑。
我不熟悉 Python,所以我会避免尝试发布示例代码。 :-)

1
我之前已经调查过这个问题,但是业务层由许多不同的第三方模块组成,它们独立地访问web.config。因此,我真的需要确保整个应用程序都能访问正确的数据。 - Andrew Rimmer

0

您可以在配置文件中始终包含其他部分。在您的ipy.exe.config文件中,您可以添加一个包含以导入外部配置设置; 比如myApp.config。

在批处理/命令文件中,您可以始终将特定的.config集合复制到myApp.config中,因此可以根据需要使用不同的配置文件运行。

请查看此博客以了解如何实现此目标; http://weblogs.asp.net/pwilson/archive/2003/04/09/5261.aspx


0

我尝试按照上面的答案操作,但发现太过复杂。如果你确切知道你需要从App.config文件中获取哪个属性,那么你可以直接将其放在代码中。例如,我导入的一个dll需要知道我的App.Config文件中的AssemblyPath属性。

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

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

这就是我所需要的,而且我连接的类库能够看到它运行所需的AssemblyPath属性。


0

为了解决问题,我所做的工作是“手动”填充ConfigurationManager静态类的AppSettings集合。所以我创建了一个PY脚本,在IronPython上运行“import”命令,然后设置将可用于类库。然而,我无法将值分配给ConnectionStrings集合 :(

我的脚本看起来像这样

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"

不过,知道一种将自定义的 .config 文件“加载”到 ConfigurationManager 类中的方法会很好。


1
这个方法不能用在 ConnectionStrings 集合上,因为该集合是只读的,所以无法通过代码进行修改。 - Moshe

0

这篇博客文章翻译成Python,应该可以这样做:

import clr
import System.AppDomain
System.AppDomain.CurrentDomain.SetData(“APP_CONFIG_FILE”, r”c:\your\app.config”)

1
这似乎没有任何区别。 - Andrew Rimmer
你确定在库尝试加载其设置之前调用了这个吗?最简单的方法是在clr.AddReference库之前执行它。 - oefe
1
是的,它在脚本顶部,但并没有产生任何影响。 - Andrew Rimmer
顺便说一句,这在Python.NET中运行得非常好! :) (尽管使用import System而不是import System.AppDomain - Macke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接