Create new unified configuration API #1999

Closed
thecodejunkie opened this Issue Aug 10, 2015 · 3 comments

Projects

None yet

2 participants

@thecodejunkie
Member

THIS WILL BE A NANCY v2.x FEATURE ONLY DUE TO THE BREAKING CHANGES

Overtime we have gotten a bit of fragmentation when it comes to configuring various parts of Nancy. We need to re-evaluate this and create a unified approach. I have created a pull-request with a design that we've talked about in the team.

The base for the new approach is a new method, on the bootstrapper, called Configure()

public virtual void Configure(INancyEnvironment environment)
{
}

The INancyEnvironment instance, that is passed in, is essentially an IReadOnlyDictionary<string, object> with a couple of more methods added. In short it's a property bag, more or less. You can take a dependency on this interface wherever you want to read configuration values.

To provide a configuration API you simply create extension methods on INancyEnvironment which sticks a config (your own type, tailored to your needs), in environment.

Here is what it could look (just sample code) like when configuring Nancy diagnostics, using the new approach

public virtual void Configure(INancyEnvironment environment)
{
    environment.Diagnostics("password", "the/path/to/use");
}

The Diagnostics extension method could manufacture an instance of a class that looks something like this

public class DiagnosticsConfiguration
{
    public DiagnosticsConfiguration(string password, string path)
    {
        this.Password = password;
        this.Path = path;
    }

    public string Password { get; private set; }

    public string Path { get; private set; }
}

A very basic, immutable, object that contains the configuration value. To get the value back out you take a dependency on INancyEnvironment and use one of the GetValue extension methods.

var config = environment.GetValue<DiagnosticsConfiguration>();

// Do something with config.Password and config.Path

There are a couple of more bits such as

If you want to have a look at the various default implementations, of the configuration interfaces, then have a look in the Nancy.Configuration namespace

Once this is in place, we'll start replacing the various configuration approaches that are currently available (StaticConfiguration, XmlSettings, JsonSettings, NancyConventions, RazorConfiguration, DiagnosticsConfiugration, CryptographicConfiguration and so on). See #2000 for discussions on migration work

Update 2015-08-15 - The INancyDefaultConfigurationProvider interface

Added the INancyDefaultConfigurationProvider interface. The interface is used to let Nancy know that you can provide default configurations for something. By providing a default configuration you can ensure that there will always be an instance, of your configuration object, in the INancyEnvironment even though the user has not explicitly provided a configuration.

/// <summary>
/// Defines the functionality for providing default configuration values to the <see cref="INancyEnvironment"/>.
/// </summary>
public interface INancyDefaultConfigurationProvider
{
    /// <summary>
    /// Gets the default configuration instance to register in the <see cref="INancyEnvironment"/>.
    /// </summary>
    /// <returns>The configuration instance</returns>
    object GetDefaultConfiguration();

    /// <summary>
    /// Gets the key that will be used to store the configuration object in the <see cref="INancyEnvironment"/>.
    /// </summary>
    /// <returns>A <see cref="string"/> containing the key.</returns>
    string Key { get; }
}

Nancy will scan for all INancyDefaultConfigurationProvider implementations and call the GetDefaultConfiguration()-method to get the default configuration instance. This will happen after the Configure() has been executed, and only configurations that have not been supplied by user code will be inserted into the environment. This all happens inside the DefaultNancyEnvironmentConfigurator (the default implementation of the INancyEnvironmentConfigurator interface)

The DefaultNancyEnvironmentConfigurator performs the following steps

  1. Invokes the Action<INancyEnvironment> that is supplied by NancyBootstrapperBase.Configure()
  2. Iterated over all INancyDefaultConfigurationProvider instances and, unless the returned type is already, registered in the environment it will register the default configuration.

This means that it will never override user defined configurations, but it will ensure that all configurations, that should be present in the environment will be there even if the user has not explicitly configured it.

Update 2015-11-09

Added string Key property on INancyDefaultConfigurationProvider interface, to enable you to explicitly specify the key that should be used when storing a configuration object in the INancyEnvironment. Also added the abstract NancyDefaultConfigurationProvider class

/// <summary>
/// Default (abstract) implementation of <see cref="INancyDefaultConfigurationProvider" /> interface.
/// </summary>
/// <typeparam name="T">The type of the configuration object.</typeparam>
public abstract class NancyDefaultConfigurationProvider<T> : INancyDefaultConfigurationProvider
{
    /// <summary>
    /// Gets the default configuration instance to register in the <see cref="INancyEnvironment"/>.
    /// </summary>
    /// <returns>The configuration instance</returns>
    public abstract T GetDefaultConfiguration();

    /// <summary>
    /// Gets the default configuration instance to register in the <see cref="INancyEnvironment"/>.
    /// </summary>
    /// <returns>The configuration instance</returns>
    object INancyDefaultConfigurationProvider.GetDefaultConfiguration()
    {
        return this.GetDefaultConfiguration();
    }

    /// <summary>
    /// Gets the full type name of <typeparamref name="T"/>.
    /// </summary>
    /// <returns>A <see cref="string"/> containing the key.</returns>
    public virtual string Key
    {
        get { return typeof(T).FullName; }
    }
}

This can be used to make use of the default behavior of using Type.FullName as the key. The Key-property was made virtual in case you ever should need to customize the key generation.

Update 2015-08-15 - The ConfigurableBootstrapperConfigurator.Configure-method

Provided a way to configure the INancyEnvironment using the ConfigurableBootstrapper for tests.

Example of what this could look like

var bootstrapper = new ConfigurableBootstrapper(with =>
{
   with.Configure(env =>
   {
       env.Diagnostics(
           password: null,
           cryptographyConfiguration: this.cryptoConfig);
   });
});

Update 2015-08-15 - The DefaultConfigurationProvider methods on ConfigurableBootstrapperConfigurator for testing

Also added various overloads of the DefaultConfigurationProvider (and DefaultConfigurationProviders) methods, on ConfigurableBootstrapperConfigurator , to help define which INancyDefaultConfigurationProviders instances that should be available during test execution

Updated 2015-11-16 - The INancyBootstrapper.GetEnvironment() method

We updated the INancyBoostrapper interface to expose a new GetEnvironment() method to get the current configured INancyEnvironment instance. This method should only be called after Initialise() have been called.

@thecodejunkie thecodejunkie self-assigned this Aug 10, 2015
@thecodejunkie thecodejunkie added this to the 2.0 milestone Aug 10, 2015
@thecodejunkie thecodejunkie changed the title from Create new unified configuration approach to Create new unified configuration API Aug 10, 2015
@thecodejunkie
Member

Just to give people and idea on how various kinds of configuration API's could look like. Here are three approaches to the diagnostics configuration. It is all going to come down to what you are configuring (required/optional values etc)

public void Configure(INancyEnvironment environment)
{
    environment.Diagnostics("password", "path");

    environment.Diagnostics(
        password: "Password",
        path: "some/path",
        requireSecureConnection: true);

    environment.Diagnostics(
        password: "Password",
        path: "some/path",
        options: with =>
        {
            with.RequireSecureConnection();
        });
}
@jchannon
Member

👀 👍

@thecodejunkie
Member

Updated initial post with information about the INancyDefaultConfigurationProvider-interface, the ConfigurableBootstrapperConfigurator.Configure()-method and ConfigurableBootstrapperConfigurator.DefaultConfigurationProvider()-methods

All the changes are reflected in the pull-request #2000

ping @NancyFx/most-valued-minions @NancyFx/nancy-core-contributors

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment