autofac/Autofac — pull request #519 by kcuzner (2014-05-02 04:56:17+00:00)
Title: Attributed Metadata with grouping of same-keyed values of T into IEnumerable(T)

I found in a project I was working on that I wanted to have sequences of groupings of data defined by metadata attributes.

For example, let's say we had an object which always represents a specific group of People. We could have the object manually say that it represents those people using a Property, but then each instance would essentially be providing the exact same data which says to me that it needs to use some sort of Metadata facility. I really like attributed metadata, so I make an attribute called `PersonAttribute` which has a `Name` and `Age` property and is allowed to be applied multiple times to a class. However, when I register this object, an `ArgumentException` results since two Name values and two Age values were defined and it fails when they are added to the Metadata `IDictionary`.

What I have done is modified the Autofac.Extras.Attributed library to do the following:
* If Metadata has been supplied with a specific Key via attributes multiple times (i.e. giving multiple values for the same key), the type stored in the Metadata `IDictionary` will be an `IEnumerable` where T is the type of the Metadata Value. Values with the same Key but different Types will cause an `ArgumentException` to be thrown. Order is preserved among groups of attributes (as will be illustrated in my example).
* If Metadata has been supplied with a Key only once (giving just one value), the type stored in the Metadata `IDictionary` will be T where T is the type of the Metadata Value (just like it always has been).

**Example to illustrate how this could be used**

```csharp
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple=true)]
public class PeopleAttribute : Attribute
{
public PeopleAttribute(string name, int age)
{
Name = name;
Age = age;
}

public string Name { get; private set; }

public int Age { get; private set; }
}

class PeopleMetadata
{
public IEnumerable Name { get; set; }
public IEnumerable Age { get; set; }
}

[Person("Alice", 42)]
[Person("Bob", 27)]
class PeopleGroup { }
```

When `PeopleGroup` is resolved with its metadata (represented by `PeopleMetadata` here to illustrate the type that the metadata values are resolved to), it will place the values for `Name` and `Age` into the two IEnumerables. So, `Age` would contain either `[42,27]` or `[27,42]` and `Name` would contain either `["Alice","Bob"]` or `["Bob","Alice"]`. Now, the problem is keeping the association between which `Name` goes with which `Age`. To solve this, the metadata values coming out of the attributes are kept in groups according to which attribute they came from until they are "consolidated" into either `KeyValuePair` or `KeyValuePair>` which both are stored in the Metadata `IDictionary` as `KeyValuePair` (as it always has been done). So long as the number of values in each key of a group is the same, when the groups of `KeyValuePair`s are consolidated into `IEnumerable` the ordering will be preserved as the various `IEnumerable`s are constructed. So, if `Name[0] == "Bob"` then `Age[0] == 27`. Similarly, if `Name[0] == "Alice"` then `Age[0] == 42`.

I use Autofac on a daily basis and I think something like this could be useful for people who use (or perhaps overuse :stuck_out_tongue:, like myself) Metadata via attributes. I know this new feature likely wouldn't be compatible with other libraries that use `MetadataAttributeAttribute` stuff to define their metadata since I don't know how they would handle multiple decorations with the same metadata attribute, but I wrote this mainly with Autofac in mind. It doesn't change any existing functionality as far as I can tell and those who need to have compatibility for other libraries can simply not use `AllowMultiple`-enabled metadata. I hope this will be considered for inclusion and all existing tests pass as well as some additional tests I added for this new functionality.

Suggestions for improvement and comments are appreciated