Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Dependency Injection

An alternative approach when using the options pattern is to bind an entire configuration or section of it through dependency injection (DI). Dependency injection extensions are provided by the more-di crate.

In the following code, PositionOptions is added to the service container with apply_config and bound to loaded configuration:

use config::prelude::*;
use di::{injectable, Injectable, Ref, ServiceCollection};
use options::prelude::*;
use serde::Deserialize;
use std::error::Error;

#[derive(Default, Deserialize)]
pub struct PositionOptions {
    pub title: String,
    pub name: String,
}

pub struct TestModel {
    options: Ref<PositionOptions>
}

#[injectable]
impl TestModel {
    pub fn new(options: Ref<PositionOptions>) -> Self {
        Self { options }
    }

    pub fn get(&self) -> String {
        format!("Title: {}\nName: {}", self.options.title, self.options.name)
    }
}

fn main() -> Result<(), Box<dyn Error + 'static>> {
    let config = config::builder().add_json_file("appsettings.json").build()?;
    let provider = ServiceCollection::new()
        .add(TestModel::transient())
        .apply_config::<PositionOptions>(config)
        .build_provider()?;
    let model = provider.get_required::<TestModel>();

    println!("{}", model.get());
    Ok(())
}

Options Configuration

Services can be accessed from dependency injection while configuring options in two ways:

  • Pass a configuration function
services.add_options::<MyOptions>()
        .configure(|options| options.count = 1);
services.configure_options::<MyAltOptions>(|options| options.count = 1);
services.add_named_options::<MyOtherOptions>("name")
        .configure5(
            |options,
            s2: di::Ref<Service2>,
            s1: di::Ref<Service1>,
            s3: di::Ref<Service3>,
            s4: di::Ref<Service4>
            s4: di::Ref<Service5>| {
                options.property = do_something_with(s1, s2, s3, s4, s5);
            });
  • Implement the Configure trait and register it as a service

It is recommended to pass a configuration closure to one of the configure functions since creating a struct is more complex. Creating a struct is equivalent to what the framework does when calling any of the configure functions. Calling one of the configure functions registers a transient Configure, which initializes with the specified service types.

FunctionDescription
configureConfigures the options without using any services
configure1Configures the options using a single dependency
configure2Configures the options using 2 dependencies
configure3Configures the options using 3 dependencies
configure4Configures the options using 4 dependencies
configure5Configures the options using 5 dependencies

Options Post-Configuration

Set post-configuration with PostConfigure. Post-configuration runs after all Configure operations occur.

Services can be accessed from dependency injection while configuring options in two ways:

  • Pass a configuration function
services.add_options::<MyOptions>()
        .post_configure(|options| options.count = 1);
services.post_configure_options::<MyAltOptions>(|options| options.count = 1);
services.add_named_options::<MyOtherOptions>("name")
        .post_configure5(
            |options,
            s2: di::Ref<Service2>,
            s1: di::Ref<Service1>,
            s3: di::Ref<Service3>,
            s4: di::Ref<Service4>
            s4: di::Ref<Service5>| {
                options.property = do_something_with(s1, s2, s3, s4, s5);
            });

post_configure_options applies to all instances. To apply a named configuration use post_configure_named_options. It is recommended to pass a configuration closure to one of the post_configure functions since creating a struct is more complex. Creating a struct is equivalent to what the framework does when calling any of the post_configure functions. Calling one of the post_configure functions registers a transient PostConfigure, which initializes with the specified service types.

FunctionDescription
post_configurePost-configures the options without using any services
post_configure1Post-configures the options using a single dependency
post_configure2Post-configures the options using 2 dependencies
post_configure3Post-configures the options using 3 dependencies
post_configure4Post-configures the options using 4 dependencies
post_configure5Post-configures the options using 5 dependencies

Options Validation

Validation is performed with Validate. Validation runs after all Configure and PostConfigure operations occur.

Services can be accessed from dependency injection while validating options in two ways:

  • Pass a validation function
services.add_options::<MyOptions>()
        .configure(|options| options.count = 1)
        .validate(|options| options.count > 0, "Count must be greater than 0.");
services.add_named_options::<MyOtherOptions>("name")
        .configure(|options| options.count = 1)
        .validate5(
            |options,
            s2: di::Ref<Service2>,
            s1: di::Ref<Service1>,
            s3: di::Ref<Service3>,
            s4: di::Ref<Service4>
            s4: di::Ref<Service5>| do_complex_validation(s1, s2, s3, s4, s5));
  • Implement the Validate trait and register it as a service

It is recommended to pass a validation closure to one of the validate functions since creating a struct is more complex. Creating a struct is equivalent to what the framework does when calling any of the validate functions. Calling one of the validate functions registers a transient Validate, which initializes with the specified service types.

FunctionDescription
validateValidates the options without using any services
validate1Validates the options using a single dependency
validate2Validates the options using 2 dependencies
validate3Validates the options using 3 dependencies
validate4Validates the options using 4 dependencies
validate5Validates the options using 5 dependencies