Registering decorators with primitive configuratio

2020-04-11 10:26发布

问题:

I have an IAppSettingsLoader interface that abstracts away the file IO for loading my app.config file.

public interface IAppSettingsLoader
{
    IEnumerable<KeyValuePair<string, string>> LoadAppSettings();
}

I have a class that loads the actual file:

public class FileAppSettignsLoader : IAppSettingsLoader
{
    public IEnumerable<KeyValuePair<string, string>> LoadAppSettings()
    {
        // Perform actual loading through ConfigurationManager.AppSettings
    }
}

Then I have a caching decorator that tries to monitor file changes to app.config

public class CachedAppSettingsLoader : IAppSettingsLoader
{
    private readonly ObjectCache _cache;
    private readonly string _cacheKey;
    private readonly CacheItemPolicy _cacheItemPolicy;
    private readonly IAppSettingsLoader _innerAppSettingsLoader;

    public CachedAppSettingsLoader(ObjectCache cache,
                                   string cacheKey, 
                                   CacheItemPolicy cacheItemPolicy, 
                                   IAppSettingsLoader innerAppSettingsLoader)
    {
        _cacheKey = cacheKey;
        _cacheItemPolicy = cacheItemPolicy;
        _cache = cache;
        _innerAppSettingsLoader = innerAppSettingsLoader;
    }

    public IEnumerable<KeyValuePair<string, string>> LoadAppSettings()
    {
        object cached = _cache[_cacheKey];
        if (cached != null)
        {
            return (IEnumerable<KeyValuePair<string, string>>)cached;
        }

        var keyValuePairs = _innerAppSettingsLoader.LoadAppSettings();

        // _cacheItemPolicy will contain a HostFileChangeMonitor
        _cache.Add(_cacheKey, keyValuePairs, _cacheItemPolicy);
        return keyValuePairs;
    }
}

I was trying to register this caching decorator with Simple Injector without success. This is what I tried:

private void RegisterDependencies(Container container)
{
    container.RegisterSingleton(() =>
        ResolveCachedAppSettingsLoader(container));
    container.Register<IAppSettingsLoader, FileAppSettingsLoader>(
        Lifestyle.Singleton);
    container.RegisterDecorator<IAppSettingsLoader, CachedAppSettingsLoader>(
        Lifestyle.Singleton);
}

private CachedAppSettingsLoader ResolveCachedAppSettingsLoader(Container container)
{
    var cacheItemPolicy = new CacheItemPolicy();
    cacheItemPolicy.ChangeMonitors.Add(new HostFileChangeMonitor(new[] { "app.config" }));

    var innerAppSettingsLoader = container.GetInstance<IAppSettingsLoader>();
    return new CachedAppSettingsLoader(
        "AuthorizationRecords", 
        cacheItemPolicy, 
        MemoryCache.Default, 
        innerAppSettingsLoader);
}

This fails because Simple Injector fails to recognise my custom ResolveCachedAppSettingsLoader as an instance factory to CachedAppSettingsLoader.

The constructor of type CachedAppSettingsLoader contains parameter 'cacheKey' of type String which can not be used for constructor injection. Parameter name: decoratorType

My question is how can I supply a custom Func<CachedAppSettingsLoader> to construct this caching decorator (with dependencies) in Simple Injector?

回答1:

Simple Injector does not easily allow registering decorators that contain primitive configuration values. There are multiple ways to extend Simple Injector to enable such behavior, but I would say that there are easier solutions.

Except for extending Simple Injector, you've basically got two options here. Either you fall back to manual construction of this part of the object graph, or you extract the group of primitive configuration values into its own configuration object, which you can register as singleton in the container.

You can manually construct the decorator and its decoratee as follows:

container.RegisterSingleton<IAppSettingsLoader>(
    new CachedAppSettingsLoader(
        "AuthorizationRecords", 
        cacheItemPolicy, 
        MemoryCache.Default, 
        new FileAppSettingsLoader()));

The other option is to extract the configuration values into its own class, which might be good idea anyway:

public class CachedAppSettingsLoaderSettings
{
    public ObjectCache Cache;
    public CacheItemPolicy Policy;
    public string CacheKey;
}

public class CachedAppSettingsLoader : IAppSettingsLoader
{
    private readonly CachedAppSettingsLoaderSettings settings;
    private readonly IAppSettingsLoader decoratee;

    public CachedAppSettingsLoader(
        CachedAppSettingsLoaderSettings settings, IAppSettingsLoader decoratee)
    {
        ...
    }
}

After this refactoring, you can register your types as follows:

container.Register<IAppSettingsLoader, FileAppSettingsLoader>(Lifestyle.Singleton);
container.RegisterDecorator<IAppSettingsLoader, CachedAppSettingsLoader>(
    Lifestyle.Singleton);
container.RegisterInstance(new CachedAppSettingsLoaderSettings
{
    Cache = MemoryCache.Default,
    Policy = new CacheItemPolicy { ... },
    CacheKey = "AuthorizationRecords" 
});