简体   繁体   中英

Registering components with metadata in Castle Windsor

I am registering multiple components for a service and I want to provide additional metadata to the components that I can get without resolving the service.

With Microsoft extensibility framework you can provide your exports with additional metadata as seen here MEF Metadata and Metadata Views , is there something similar in Windsor?

I am currently trying to wrap my component in a class with 2 properties, IMetadata and Lazy where the lazy would resolve the component when needed.

public class Program
{
    public static void Main(string[] args)
    {
        var container = new WindsorContainer();

        // register the wrapped object that contains the Component metadata and a lazy to resolve the component
        container.Register(
            Classes.FromThisAssembly()
            .BasedOn<IService>()
            .Configure(config =>
            {
                // create wrapper
                var wrapper = new WrappedComponent();
                wrapper.ComponentImpl = new Lazy<IService>(() => container.Resolve<IService>(config.Name));

                // add metadata to the wrapper 
                if (config.Implementation.IsDefined(typeof(ComponentMetadataAttribute), false))
                {
                    var metadata = (IComponentMetadata)config.Implementation.GetCustomAttributes(typeof(ComponentMetadataAttribute), false)[0];
                    wrapper.Metadata = metadata;
                }

                // set the component to the wrapper
                config.Instance(wrapper);
            })
            // also set service to wrapper
            .WithService.Select((impl, services) => new List<Type>() { typeof(WrappedComponent) }));

        // register the components
        container.Register(
            Classes.FromThisAssembly()
            .BasedOn<IService>().Configure(config =>
            {
                if (config.Implementation.IsDefined(typeof(ComponentMetadataAttribute), false))
                {
                    var metadata = (IComponentMetadata)config.Implementation.GetCustomAttributes(typeof(ComponentMetadataAttribute), false)[0];

                    config.Named(metadata.Name);
                }
            }));
    }
}

public class WrappedComponent
{
    public IComponentMetadata Metadata { get; set; }
    public Lazy<IService> ComponentImpl { get; set; }
}
[ComponentMetadata]
public class MyComponent : IService
{
    public void Operation()
    {
        // Do stuff
    }
}


public interface IService
{
    void Operation();
}

public class ComponentMetadataAttribute : Attribute, IComponentMetadata
{
    public string Name { get; set; }
    public string Description { get; set; }
    public string SomeOtherMetadata { get; set; }
}

public interface IComponentMetadata
{
    string Name { get; set; }
    string Description { get; set; }
    string SomeOtherMetadata { get; set; }
}

You can make your WrappedComponent more generic and simplify your registrations by handling off the extraction of the metadata and lazy loaded component to the WrappedComponent class.

WrappedComponent takes the IKernel (from Castle Windsor) and uses it to determine the implementing type of the TComponent service from which metadata is retrieved. It also uses the IKernel to initialise the Lazy object. Remembering to handle the release of the TComponent as we're explicitly resolving it.

public class WrappedComponent<TComponent> : IDisposable
{
    private readonly IKernel kernel;

    public WrappedComponent(IKernel kernel)
    {
        this.kernel = kernel;

        var componentType = this.kernel.GetHandler(typeof(TComponent)).ComponentModel.Implementation;

        this.Metadata = componentType.GetCustomAttributes(typeof(ComponentMetadataAttribute), false)
                            .Cast<ComponentMetadataAttribute>().FirstOrDefault();

        this.ComponentImpl = new Lazy<TComponent>(() => this.kernel.Resolve<TComponent>());
    }

    public IComponentMetadata Metadata { get; }

    public Lazy<TComponent> ComponentImpl { get; }

    public void Dispose()
    {
        if (this.ComponentImpl.IsValueCreated)
        {
            this.kernel.ReleaseComponent(this.ComponentImpl.Value);
        }
    }
}

Registration becomes as simple as...

container.Register(Component.For(typeof(WrappedComponent<>)).LifestyleTransient());
container.Register(Classes.FromThisAssembly().BasedOn<IService>().WithService.FromInterface().LifestyleTransient());

I've assumed a Transient lifestyle, but whichever lifestyle you use, it'll be best to use the same for the WrappedComponent and IService .

IService , MyComponent , ComponentMetadataAttribute and IComponentMetadata remain unchanged.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM