3

I am using Unity and have a Model tagged with data annotations:

public class SomeModel
{    
   [SlackDisplayName("ED0CAD76-263E-496F-ABB1-A4DFE6DEC5C2")]
   public String SomeProperty { get; set; }    
}

This SlackDisplayName property is a child class of DisplayName, which resolves a static Display Name for the property. I just wanted to make that dynamically by having this criteria fulfilled:

  1. The use of this annotation is possible.
  2. I can implement multilingual applications using that annotation.
  3. Language templates get identified by a GUID
  4. I must not pass a culture id to the annotation

So furthermore, my SlackDisplayName annotation looks like this:

/// <summary>
/// Annotation for non-fixed display names
/// </summary>
public class SlackDisplayNameAttribute : DisplayNameAttribute
{
    /// <summary>
    /// TODO
    /// </summary>
    /// <param name="identifierGUID"></param>
    public SlackDisplayNameAttribute(String identifierGUID)
        : this(Guid.Parse(identifierGUID))
    {
    }

    /// <summary>
    /// TODO
    /// </summary>
    /// <param name="identifier"></param>
    public SlackDisplayNameAttribute(Guid identifier)
        : base()
    { 

    }

    /// <summary>
    /// The culture context to use.
    /// </summary>
    [Dependency]
    public ICultureContext Context { get; set; }

    /// <summary>
    /// Gets the display name for the given GUID.
    /// </summary>
    public override string DisplayName
    {
        get
        {
            return "NOT_DEFINED";
            //return Context.GetLanguageTemplate(new Guid()); 
        }
    }
}

And now the question is: How to get the ICultureContext from my Unity Container:

[Dependency]
public ICultureContext Context { get; set; }

It is registered, but I have no clue about how to get that property injected.

tereško
  • 58,060
  • 25
  • 98
  • 150
Marco Klein
  • 683
  • 5
  • 19

1 Answers1

3

I have solved it myself!

First of all, you need the following Unity Extension and Strategy:

Info: Found here: UnityContainer.BuildUp() - Can I make it inject new instances into properties only if these are null?

public class RecursiveBuildUpContainerExtension : UnityContainerExtension {
    protected override void Initialize(){
        Context.Strategies.Add( new RecursiveBuildUpBuilderStrategy( Context.Container ), UnityBuildStage.PreCreation );
    }
}

public class RecursiveBuildUpBuilderStrategy : BuilderStrategy {
    readonly IUnityContainer container;
    public RecursiveBuildUpBuilderStrategy( IUnityContainer container ) {
        this.container = container;
    }

    public override void PreBuildUp( IBuilderContext context ) {

        if( context.Existing == null ) return;

        foreach( var prop in context.Existing.GetType( ).GetProperties( ) ) {

            if( ContainsType<DependencyAttribute>( prop.GetCustomAttributes( true ) ) ) {

                if( prop.GetValue( context.Existing, null ) == null ) {
                    var value = container.Resolve( prop.PropertyType );
                    prop.GetSetMethod( ).Invoke( context.Existing, new[] { value } );
                }
                else {
                    var value = container.BuildUp( prop.PropertyType, prop.GetValue( context.Existing, null ) );
                    prop.GetSetMethod( ).Invoke( context.Existing, new[] { value } );
                }
            }
        }

        foreach (var method in context.Existing.GetType().GetMethods() ){
            if( ContainsType<InjectionMethodAttribute>( method.GetCustomAttributes( true ))){
                var argsInfo = method.GetParameters( );
                var args = new object[argsInfo.Length];

                for( int i = 0; i < argsInfo.Length; i++ ) {
                    args[i] = container.Resolve( argsInfo[i].ParameterType );
                }

                method.Invoke( context.Existing, args );
            }
        }

        context.BuildComplete = true;
    }

    private static bool ContainsType<T>( IEnumerable<object> objects ){
        foreach (var o in objects){
            if( o is T ) return true;
        }
        return false;
    }

}

You need this, because it is responsible for injecting the properties on "BuildUp". Next to this, you need to register your extension

container.AddNewExtension<RecursiveBuildUpContainerExtension>();

Furthermore, you need to override the default DataAnnotationsModelMetadataProvider, because the default ModelMetaDataProvider does not use Unity to inject properties to annotations. To do this, implement this class:

public class DynamicModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
    private IUnityContainer _context;

    protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
    {           
        foreach (Attribute attribute in attributes)
            _context.BuildUp(attribute);

        return base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
    }

    public DynamicModelMetadataProvider(IUnityContainer context)
        : base()
    {
        this._context = context;
    }
}

After that, edit your bootstrapper and set the new ModelMetadataProvider, to make it clear to the MVC Framework that it has to use it:

ModelMetadataProviders.Current = new DynamicModelMetadataProvider(container);

Where container is your set up IUnityContainer. You should now have instances in your Annotations Instance when having set the [DependencyAttribute] and your Methods marked with the [InjectionMethod] should get called.

[Dependency]
public ICultureContext Context { get; set; }

Hope you could use this if you had a similar problem ;)

Community
  • 1
  • 1
Marco Klein
  • 683
  • 5
  • 19
  • 1
    The AddNewExtension takes a single parameter which should me the RecursiveBuilUpContainerExtension. Do you have a strategy to unit test your class that use this Data Annotation attribute because as far as I can see, you are now dependent of Mvc and from the IoC to unit test now. – Patrick Desjardins Apr 17 '14 at 12:49
  • @PatrickDesjardins, I just got it and can see your point. Did you find any solution that didn't require as to use the IoC container in our unit tests? – Fabio Milheiro Aug 31 '16 at 16:43