autofac / Documentation

Usage and API documentation for Autofac and integration libraries
http://autofac.readthedocs.org
MIT License
71 stars 121 forks source link

Additional Guidance for Custom Metadata Usage #144

Open hawkerm opened 3 years ago

hawkerm commented 3 years ago

Problem Statement

It's unclear from the metadata docs on how to use an attribute that's created outside the filtering scenario and instead to lookup values on the metadata either during or before resolution.

For instance modifying the WeakTypedAttributeScenarioTestFixture to demonstrate what I was trying:

            // arrange
            var builder = new ContainerBuilder();
            builder.RegisterModule<AttributedMetadataModule>();
            builder.RegisterType<CombinationalWeakTypedScenario>().As<ICombinationalWeakTypedScenario>();

            // act
            var items = builder.Build().Resolve<IEnumerable<Lazy<ICombinationalWeakTypedScenario, CombinationalWeakAgeMetadataAttribute>>>();

            // assert
            Assert.Single(items);
            Assert.Single(items.Where(p => p.Metadata.Age == 42));

But this leads to a Autofac.Core.DependencyResolutionException : The type 'CombinationalWeakAgeMetadataAttribute' cannot be used as a metadata view. A metadata view must be a concrete class with a parameterless or dictionary constructor. error.

If I add a parameterless constructor to the type, then it appears to work in the test project (though I'm having trouble making this work in my own project with an enum instead of an int/string).

Anyway, it'd be great to see more examples for different setups in the documentation for using these types of more strongly-typed custom attributes.

Desired Solution

More guidance on using strongly-typed custom metadata within autofac. Especially around metadata lookup before resolution, if possible?

tillig commented 3 years ago

I think we could definitely improve on the examples in the docs. Looks like it's pretty light in there.

For your immediate issue, you'll notice in the CombinationalWeakTypedAttributeScenarioTestFixture that the thing being resolved is an interface, not a class/attribute:

[Fact]
public void Validate_wireup_of_generic_attributes_to_strongly_typed_metadata_on_resolve()
{
    // arrange
    var builder = new ContainerBuilder();
    builder.RegisterMetadataRegistrationSources();

    builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
        .As<ICombinationalWeakTypedScenario>()
        .WithAttributedMetadata();

    // act
    var items = builder.Build().Resolve<IEnumerable<Lazy<ICombinationalWeakTypedScenario, ICombinationalWeakTypedScenarioMetadata>>>();

    // assert
    Assert.Single(items);
    Assert.Single(items.Where(p => p.Metadata.Name == "Hello"));
    Assert.Single(items.Where(p => p.Metadata.Age == 42));
}

...and that interface looks like...

public interface ICombinationalWeakTypedScenarioMetadata
{
    int Age { get; }

    string Name { get; }
}

The interface is pretty important because it means Autofac can use the magic of MEF to get a strongly typed view over the metadata.

If you use a class rather than an interface, the metadata registration sources won't handle it and it falls back to the default Autofac view provider which is where you're getting that exception. The default provider basically calls new on the type and tries to set properties, which is why adding the parameterless constructor fixes things up.

But, I agree, we could probably clean up the metadata docs a bit to bring it together better. There's nothing in there about metadata views and I think in general the doc could probably just be tightened up a bit to tie the concepts all together. Trying to pick up various bits from each section and put them all together is a challenge.

hawkerm commented 3 years ago

Thanks for the info @tillig, that's helpful to know. The error messages from autofac or elsewhere in the stack are sometimes a bit obtuse on the underlying issue or solution, so seeing it laid out in your example is helpful.

I was definitely trying to avoid having to create an interface for the metadata class in my scenario if possible, so that's probably leading me to some of the troubles. The parameterless constructor helped in my test scenario, but didn't seem to work in my practical example using a similar setup for some reason. Still wasn't able to figure that out, depending on tweaks I did it seemed to either look for the dictionary based constructor or throw some other exception outside of autofac I believe.