Property Value Substitution

Mar 7, 2009 at 6:39 AM

I am new to Unity, but have been using Spring.NET for a couple years. In Spring.NET, it is possible to define a property value as something like "${MyKey}", and during the configuration stage, have this resolve through a name/value source of some kind, into something like "MyValue". I am trying to do something similar in Unity and ObjectBuilder2.

I can see various ways to achieve something like this using alternative configuration sources, but this is not a solution for me. Let me explain:

I have an application deployed by ClickOnce which uses DI to define and wire-up multiple objects. For the sake of simplicity, let's assume one of those objects has a property called "Timeout". It is quite easy to configure this in a unity configuration section in App.config, or in some other file. However, in a ClickOnce application, all files in the deployment are referenced in the manifests with their hash, and the manifests are then signed. This has the effect of making configuration files essentially hard-coded. If I want to change the value of the "Timeout" property above from say 10 to 20, I can't simply modify the config file on the web server; I must modify the config file, and then generate a new deployment, so the manifests and the config file(s) are compatible.

I have gotten around this limitation for simple cases like the "Timeout" property by resolving "${MyKey}" against a file of name/value pairs which is accessed from the web sever via an http URL. The URL is "hard-coded" into the config file, but the content can be changed on the server by simply editing the file, because it is outside of the ClickOnce deployment itself. In case you're interested, I do this in Spring.NET using a VariablePlaceholderConfigurer and a PropertyFileVariableSource with the location property set to the URL.

I am trying to figure out how to do something similar with Unity. Anything which relies on the System.Configuration namespace is a non-starter, because it always assumes config files are referenced with relative or absolute file paths, not URL's.

Is there some way of accomplishing something like this in Unity? I have looked at providing a custom type converter which would take "${MyKey}" and resolve it against the file from the URL, but this has a slight problem in that I would rather not hard-code (for real) the URL in the converter class, but inject it with DI itself, and I don't see how to make the typeconverter attribute reference a configured instance in the container. I have also been looking at a custom build strategy, but I'm not finding much documentation on this.

Am I missing a simple way of accomplishing this? Or is there some way other than what I've mentioned to go about it?

Dave

Mar 10, 2009 at 8:30 AM

OK, that was easy. Create a new class inheriting from InjectionParameterValueElement (basically cloning InstanceValueElement) called KeyedValueElement, with a "key" instead of a "value" attribute, then use it to lookup the value before creating the InjectionParameterValue for the looked-up value. Put it in the config file:

<keyedValue elementType="UnityExtensions.KeyedValueElement, UnityExtensions" key="MyKey" [type="..."] [typeConverter="..."] />

Works like a charm. Another question: Is there a mechanism whereby I can register something like a "dictionary service" accessible inside the container? I could use this to map different name/value providers under a name, which I could then make an additional attribute of the element. I suppose I could make the appSettings section of the config file the default provider if none was specified.

Dave

Feb 5, 2010 at 7:15 PM

Dave. Thanks for this, I've been digging around trying to achieve the same thing having moved over from Spring. Is it possible to post the code for KeyedValueElement? I follow the clone of InstanceValueElement, but am not sure on the best approach for the lookup of the value for the key, plus it's usage in the configuration.

Incidentally, related to the lookup of the value, I'm looking to use this in a Prism application where we have a number of modules with separate unity configurations. Do you think the above solution work where I want to have all the environment specific variables defined in a single configuration file in entry point project of the application and have all the modules' unity configurations lookup the substitution variables?

Pero

Feb 5, 2010 at 8:54 PM

I've implemented a solution which looks up the key values from the appSettings section of the application's app.config, through the ConfigurationManager, and this works fine for variable replacement in unity configurations of Prism modules. Code below. Thanks for the pointers Dave!

Imports System
Imports System.ComponentModel
Imports System.Configuration

Imports Microsoft.Practices.Unity
Imports Microsoft.Practices.Unity.Configuration

Public Class KeyedValueElement
    Inherits InjectionParameterValueElement

    <ConfigurationProperty("type", DefaultValue:=Nothing)> _
    Public Property TypeName() As String
        Get
            Return DirectCast(Me("type"), String)
        End Get
        Set(ByVal value As String)
            Me("type") = value
        End Set
    End Property

    <ConfigurationProperty("key", IsRequired:=True)> _
    Public Property [Key]() As String
        Get
            Return DirectCast(Me("key"), String)
        End Get
        Set(ByVal key As String)
            Me("key") = key
        End Set
    End Property

    <ConfigurationProperty("typeConverter", IsRequired:=False, DefaultValue:=Nothing)> _
    Public Property TypeConverterName() As String
        Get
            Return DirectCast(Me("typeConverter"), String)
        End Get
        Set(ByVal value As String)
            Me("typeConverter") = value
        End Set
    End Property

    Public Function CreateInstance() As Object
        'lookup the value from the app settings
        Dim replacementValue As String = ConfigurationManager.AppSettings([Key])

        If String.IsNullOrEmpty(replacementValue) Then
            Throw New ConfigurationErrorsException("The key '" & [Key] & "' has no value in the appSettings configuration section")
        End If

        If TypeToCreate Is GetType(String) Then
            Return replacementValue
        End If

        Dim converter As TypeConverter = GetTypeConverter(TypeToCreate, TypeConverterName, TypeResolver)
        Return converter.ConvertFromString(replacementValue)
    End Function

    Private Shared Function GetTypeConverter(ByVal typeToCreate As Type, ByVal typeConverterName As String, ByVal typeResolver As UnityTypeResolver) As TypeConverter
        If Not String.IsNullOrEmpty(typeConverterName) Then
            ' return the type converter override
            Dim converterType As Type = typeResolver.ResolveType(typeConverterName)
            Return DirectCast(Activator.CreateInstance(converterType), TypeConverter)
        Else
            ' return the default type converter
            Return TypeDescriptor.GetConverter(typeToCreate)
        End If
    End Function

    Public Overloads Overrides Function CreateParameterValue(ByVal targetType As Type) As InjectionParameterValue
        Dim typeToCreate As Type
        If String.IsNullOrEmpty(TypeName) Then
            typeToCreate = targetType
        Else
            typeToCreate = TypeResolver.ResolveType(TypeName)
        End If
        Return New InjectionParameter(typeToCreate, CreateInstance())
    End Function

    Public ReadOnly Property TypeToCreate() As Type
        Get
            Return TypeResolver.ResolveWithDefault(TypeName, GetType(String))
        End Get
    End Property
End Class