Giter Club home page Giter Club logo

configinjector's Introduction

ConfigInjector

Build Status

How application settings should look:

Here's a class that needs some configuration settings:

public class EmailSender : IEmailSender
{
    private readonly SmtpHostConfigurationSetting _smtpHost;
    private readonly SmtpPortConfigurationSetting _smtpPort;

    public EmailSender(SmtpHostConfigurationSetting smtpHost,
                       SmtpPortConfigurationSetting smtpPort)
    {
        _smtpHost = smtpHost;
        _smtpPort = smtpPort;
    }

    public void Send(MailMessage message)
    {
        // NOTE the way we can use our strongly-typed settings directly as a string and int respectively
        using (var client = new SmtpClient(_smtpHost, _smtpPort))
        {
            client.Send(message);
        }
    }
}

Here's how we declare the settings:

// This will give us a strongly-typed string setting.
public class SmtpHostConfigurationSetting : ConfigurationSetting<string>
{
}

// This will give us a strongly-typed int setting.
public class SmtpPortConfigurationSetting : ConfigurationSetting<int>
{
    protected override IEnumerable<string> ValidationErrors(int value)
    {
        if (value <= 0) yield return "TCP port numbers cannot be negative.";
        if (value > 65535) yield return "TCP port numbers cannot be greater than 65535.";
    }
}

Here's how we set them in our [web|app].config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="SmtpHostConfigurationSetting" value="localhost" />
    <add key="SmtpPortConfigurationSetting" value="25" />
  </appSettings>
</configuration>

... and here's how we provide mock values for them in our unit tests:

var smtpHost = new SmtpHostConfigurationSetting {Value = "smtp.example.com"};
var smtpPort = new SmtpPortConfigurationSetting {Value = 25};

var emailSender = new EmailSender(smtpHost, smtpPort);

emailSender.Send(someTestMessage);

Getting started

In the NuGet Package Manager Console, type:

Install-Package ConfigInjector

then run up the configurator like this:

ConfigurationConfigurator.RegisterConfigurationSettings()
                         .FromAssemblies(/* TODO: Provide a list of assemblies to scan for configuration settings here  */)
                         .RegisterWithContainer(configSetting => /* TODO: Register this instance with your container here */ )
                         .DoYourThing();

You can pick your favourite container from the list below or roll your own.

Getting started with Autofac

var builder = new ContainerBuilder();

builder.RegisterType<DeepThought>();

ConfigurationConfigurator.RegisterConfigurationSettings()
                         .FromAssemblies(ThisAssembly)
                         .RegisterWithContainer(configSetting => builder.RegisterInstance(configSetting)
                                                                        .AsSelf()
                                                                        .SingleInstance())
                         .DoYourThing();

return builder.Build();

Getting started with Castle Windsor

var container = new WindsorContainer();

container.Register(Component.For<DeepThought>());

ConfigurationConfigurator.RegisterConfigurationSettings()
                         .FromAssemblies(typeof (DeepThought).Assembly)
                         .RegisterWithContainer(configSetting => container.Register(Component.For(configSetting.GetType())
                                                                                             .Instance(configSetting)
                                                                                             .LifestyleSingleton()))
                         .DoYourThing();

return container;

Getting started with Ninject

var kernel = new StandardKernel();

kernel.Bind<DeepThought>().ToSelf();

ConfigurationConfigurator.RegisterConfigurationSettings()
                         .FromAssemblies(typeof (DeepThought).Assembly)
                         .RegisterWithContainer(configSetting => kernel.Bind(configSetting.GetType())
                                                                       .ToConstant(configSetting))
                         .DoYourThing();

return kernel;

FAQ

What naming conventions should I use for the configuration settings?

There are two naming conventions that are applied by default. With the first, DefaultSettingKeyConvention, the name of the configuration setting class should match the key used in the appSettings section of your configuration file. So given a key called StorageConnectionString the corresponding configuration setting class should be called StorageConnectionString. The second, WithSuffixSettingKeyConvention strips 'Setting' from the name of the configuration class, so given a key called StorageConnectionString the corresponding configuration setting class could be called StorageConnectionStringSetting.

Additional naming conventions can be configured when registering ConfigInjector by providing your own implementations of ISettingKeyConvention, using the WithSettingKeyConventions() method before calling DoYourThing():

ConfigurationConfigurator.RegisterConfigurationSettings()
                         .FromAssemblies(...)
                         .RegisterWithContainer(...)
                         .WithSettingKeyConventions(new MyCustomKeyConvention())
                         .DoYourThing();

What types can I use?

  • Any type that has a public static .Parse(string someValue) method.
    • This includes TimeSpan, DateTime and a bunch of others.
    • Yes, you can use your own custom types here if you wish. Just don't get too tricky...
  • Enums
    • Just remember that they're case-sensitive - and they should be case-sensitive, because SomeEnum.Foo and SomeEnum.FOO are different.
  • Uris
  • Any type that Convert.ChangeType can convert from a string.
  • Any types for which you provide a custom parser.

Can I provide my own type converters?

Yep. Just provide your custom value parsers to the WithCustomValueParsers configuration method.

ConfigurationConfigurator.RegisterConfigurationSettings()
                         .FromAssemblies(/* TODO: Provide a list of assemblies to scan for configuration settings here  */)
                         .RegisterWithContainer(configSetting => /* TODO: Register this instance with your container here */ )
                         .WithCustomValueParsers(new PersonNameValueParser())
                         .DoYourThing();

Each of your parsers should implement the IValueParser interface:

public class EnumValueParser : IValueParser
{
    public int SortOrder
    {
        get { return int.MaxValue - 2; }
    }

    public bool CanParse(Type settingValueType)
    {
        return settingValueType.BaseType == typeof (Enum);
    }

    public object Parse(Type settingValueType, string settingValueString)
    {
        return Enum.Parse(settingValueType, settingValueString);
    }
}

How can I exclude certain settings that are causing an ExtraneousSettingsException?

Some external packages add configuration that your application doesn't care about, such as Microsoft.AspNet.Mvc. These will cause an ExtraneousSettingsException because you have not created classes for them. This is desired behaviour. We want to discourage any sneaky references to ConfigurationManager.AppSettings[...] and an easy way to do that is to simply not permit any settings that we haven't wrapped in setting classes.

If you do not want to create classes for them since you will not be using them, while running up the Configurator, just call ExcludeSettingKeys, passing in the string array of keys to ignore. E.g:

ConfigurationConfigurator.RegisterConfigurationSettings()
                         .FromAssemblies(/* TODO: Provide a list of assemblies to scan for configuration settings here  */)
                         .RegisterWithContainer(configSetting => /* TODO: Register this instance with your container here */ )
                         .ExcludeSettingKeys(new[] { "ExampleSettingKey1", "webpages:Version", "webpages:Enabled" })
                         .DoYourThing();

If you genuinely don't want to assert that nobody is using silly/dangerous inline configuration settings, you can use the following:

ConfigurationConfigurator.RegisterConfigurationSettings()
                         .FromAssemblies(/* TODO: Provide a list of assemblies to scan for configuration settings here  */)
                         .RegisterWithContainer(configSetting => /* TODO: Register this instance with your container here */ )
                         .AllowConfigurationEntriesThatDoNotHaveSettingsClasses(true)
                         .DoYourThing();

This approach is also useful for environments like Windows Azure, where 1) enumerating all configuration settings is not supported and 2) the hosting environment may randomly add unexpected settings to your application's configuration.

Can I load settings directly without configuring my container first?

You can do this but I'd give some serious thought to whether it's a good idea in your particular case.

var setting = DefaultSettingsReader.Get<SimpleIntSetting>();

If you genuinely need access to settings before your container is wired up, go ahead. If you're using ConfigInjector as a settings service locator across your entire app, you're holding it wrong :)

One scenario in which you might like to use this pattern is when you'd like to configure your logger first, then pass that logger to ConfigInjector.

ConfigInjector will make an intelligent guess at defaults. It will, for instance, scan the current app domain and look for assemblies that contain settings and value parsers. If you have custom value parsers it will pick those up, too, provided that they're not off in an as-yet-unloaded satellite assembly somewhere.

If you need to globally change the default behaviour, create a class that implements IStaticSettingReaderStrategy:

public class MyCustomSettingsReaderStrategy : IStaticSettingReaderStrategy
{
    // ...
}

and use use this to wire it up:

DefaultSettingsReader.SetStrategy(new MyCustomSettingsReaderStrategy());

What if I want to programmatically mess with settings values after I've read them?

There are a couple of cases where you'd want to do this. Let's compare SQL Server versus Windows Service Bus as examples.

With SQL Server, you can happily use a connection string containing a . for localhost:

<add key="DatabaseConnectionString" value="Data Source=.\SQLEXPRESS;Initial Catalog=SecretDatabase-Dev;Integrated Security=SSPI;" />

With something like Windows/Azure Service Bus, however, it will have an opinion about this because it uses TLS and the certificate CN won't match, so something like this won't work:

<add key="ServiceBusConnectionString" value="Endpoint=sb://localhost/SecretServiceBus-Dev;StsEndpoint=https://localhost:9355/SecretServiceBus-Dev;RuntimePort=9354;ManagementPort=9355" />

What we can do instead is this:

<add key="ServiceBusConnectionString" value="Endpoint=sb://{MachineName}/SecretServiceBus-Dev;StsEndpoint=https://{MachineName}:9355/SecretServiceBus-Dev;RuntimePort=9354;ManagementPort=9355" />

and then override that configuration setting's value on the way out:

public class ServiceBusConnectionString : ConfigurationSetting<string>
{
    public override string Value
    {
        get { return base.Value.Replace("{MachineName}", Environment.MachineName); }
        set { base.Value = value; }
    }
}

Overriding settings from environment variables (or anywhere else)

Why would I want to do this?

Scenario: We want to be able to run integration tests locally and in multiple deployed environments.

  • Locally (and in our [web|app].config files) we use localhost.
  • We transform these using Octopus or our tool of choice when we deploy to the CI environment.
  • We run integration tests using settings from our app.config file in the CI environment, and currently they would fail because the test endpoints would still be configured to point at localhost.

The issue is that the build agent and the deployment target will (should!) be different machines.

If we allow overriding of settings (provided they're otherwise valid) we can then set environment variables on the build agent to tell it which servers to hit for integration tests.

How do I do it?

Out of the box, ConfigInjector will load settings using ConfigurationManager.AppSettings[...] and then override any settings it finds there with any matching environment variable it finds.

If we have an app setting named Foo in our [web|app.config] then ConfigInjector will look for an environment variable named AppSetting_Foo and override the setting if it finds one.

We can now configure our build server to set different environment variables for different test environments.

What about sensitive values?

You can mark configuration values as sensitive by overriding the IsSensitive property to return true.

You can also override the SanitizedValue property to return a sanitized version of the value in case it's going to be written to a log file or other insecure storage.

public class FooApiKey: ConfigurationSetting<string>
{
	public override bool IsSensitive => true;

	public override string SanitizedValue => "********";
}

It's worth noting that these properties do not change the behaviour of ConfigInjector; they simply allow us to be a bit more judicious when we're dealing with these settings.

Package feeds

Stable versions

Stable versions of the packages are available via nuget.org.

Pre-release versions

Be careful! The pre-release feeds may contain builds from feature branches and pull requests as well as from the master branch.

Contributing

The official repository for ConfigInjector is https://github.com/ConfigInjector/ConfigInjector.

Public build information is available at https://ci.appveyor.com/project/ConfigInjector/configinjector.

Build Status

configinjector's People

Contributors

configinjectorteam avatar gertjvr avatar michael-wolfenden avatar nootn avatar uglybugger avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

configinjector's Issues

Allow static (imperative) loading of settings.

Sometimes we simply have to load settings before our container is configured.

Specifically, in some cases it's so that we can build components to pass to our container and we can't do it in a factory delegate because the dependencies need to be materialised before we've finished configuring the container.

Allow Nullable types

We often have configuration settings that are optional (have a blank setting value), however ConfigInjector fails when trying to cast from a string to a nullable.

For example:

<add key="NullableIntSetting" value=""/>
<add key="NullableGuidSetting" value=""/>
public class NullableGuidSetting : ConfigurationSetting<Guid?>
{
}

public class NullableIntSetting : ConfigurationSetting<int?>
{
}

Seeking maintainers - I need help

Hi all,

I'm exhausted and can't keep up. I'm far too slow in replying to people and given my day-job workload my commit velocity on my open-source work is currently pretty pitiful. While I can do sporadic work here and there, that's about all I can do at the moment.

I've transferred ownership of the ConfigInjector repository from my own account to a ConfigInjector GitHub organisation and would love some help in maintaining it. You'll only need an AppVeyor account (free) so I can add you to the team and a MyGet account (also free) so that you can push packages to nuget.org.

If you have time to reply (reasonably quickly, unlike me) to issues and review pull requests, I'd be very happy to hear from you.

-Andrew H

Non-string settings reader

In my situation I already had correctly typed values I wanted to feed in, but had to convert them to string just to have them parsed back out. To fix this we could add new interface ITypedSettingReader that returned objects instead.

It would also easily handle complex types. The work around is to serialise and write a de-serializer value converter.

Simple injector

Any Idea how to use this with simple injector I'm racking my brain!

.NET / ASP.NET Core

Can this be used with .NET / ASP.NET Core?

It has a new config mechanism, but doesn't look as sophisticated as this lib.

Support overriding of settings via environment variables

This would be useful in the case where we need to run integration test suites in different environments.

Scenario: We want to be able to run integration tests locally and in multiple deployed environments.

  • Locally (and in our [web|app].config files) we use localhost.
  • We transform these using Octopus or our tool of choice when we deploy to the CI environment.
  • We run integration tests using settings from our app.config file in the CI environment, and currently they would fail because the test endpoints would still be configured to point at localhost.

The issue is that the build agent and the deployment target will (should!) be different machines.

If we allow overriding of settings (provided they're otherwise valid) we can then set environment variables on the build agent to tell it which servers to hit for integration tests.

Consider renaming AllowEntriesInWebConfigThatDoNotHaveSettingsClasses

Given that ConfigInjector also reads app.config files and given that .WithAppSettingsReader() allows the reader implementation to be replaced to read a completely different configuration source, perhaps this method should be named more generically, eg:

AllowConfigurationEntriesThatDoNotHaveSettingsClasses

One parse error stops all settings from parsing

` private IConfigurationSetting[] LoadConfigurationSettings()
{
var configurationSettings = _typeProvider.Get()
.Where(t => t.IsAssignableTo())
.Where(t => t.IsInstantiable())
.Select(GetConfigSettingFor)
.ToArray();

        return configurationSettings;
    }`

If GetConfigSettingFor throws an exception (because a user wrote a TimeSpan that doesn't parse, for example), any other settings that haven't yet been applied will not be applied, even if they would otherwise successfully parse.

This means that correct settings (such as the URL for the logging service, or settings for the diagnostic nancy endpoint) will fail to resolve.

This in turn makes it harder for a site to report configuration errors on non-critical settings.

A couple of ideas for possible improvements are:

  • if GetConfigSettingFor throws, catch and retain it to be thrown at the end, and continue parsing other setting values
  • provide a way of settings to prioritize themselves such that critical and basic (string) settings are parsed first

Not a critical issue as ExcludeSettingsKeys for example should allow two passes of the settings.

Consider interface segregation of ISettingsReader

Implementing an alternative version of ISettingsReader requires implementing both ReadValue and AllKeys members.

However, in some situations, eg wrapping the Azure CloudConfigurationManager class with an ISettingsReader adaptor, it is not possible to implement AllKeys as a list of all settings is not exposed by the underlying configuration source.

Furthermore, the AllKeys member is currently only used for the purposes of enforcing the AllowEntriesInWebConfigThatDoNotHaveSettingsClasses rule and does not impact the runtime usage of ConfigInjector.

I understand that returning an empty array from AllKeys is a valid workaround.

Read values from XML files that aren't app.config or web.config

Windows 8/8.1 Store apps don't have app.configs or an equivalent. A common workaround is an embedded XML file that gets read using something similar to:

private static string LoadSetting(string settingKey)
{
    var assembly = typeof(AppConfig).GetTypeInfo().Assembly;

    using (var stream = assembly.GetManifestResourceStream("ABC.ConfigSettings.xml"))
    {
        var xmlDoc = XDocument.Load(stream);

        var appSettings = xmlDoc.Element("appSettings");

        var node = appSettings.Elements().Single(e => (string)e.Attribute("key") == settingKey);

        return (string)node.Attribute("value");
    }
}

It would be great if we could point ConfigInjector at an arbitrary embedded resource or string containing the properly formed XML.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.