tags:

views:

58

answers:

1

Media Browser has a provider model, this is basically a chain of classes that get called in a particular order for each entity.

So for example we have:

        providers = new List<IMetadataProvider>();
        providers.Add(new ImageFromMediaLocationProvider());
        providers.Add(new ImageByNameProvider());
        providers.Add(new MovieProviderFromXml());
        providers.Add(new MovieDbProvider());
        providers.Add(new TVProviderFromXmlFiles());
        providers.Add(new TvDbProvider());
        providers.Add(new VirtualFolderProvider());
        providers.Add(new FrameGrabProvider());
        providers.Add(new MediaInfoProvider());

The order of the providers in the list is significant higher order providers take precedence over lower order ones.

Recently, I have tried to make this portion extensible. So a third party DLL can define its own providers that will get injected in our chain.

The problem is that once you allow for 3rd parties to inject themselves in the chain you lose a central place to define this order.

My current solution that I am a little uncomfortable with is to define a optional priority attribute with each provider and then order by the priority.

So for example I now have:

[ProviderPriority(20)]
class ImageByNameProvider{}

This allows 3rd parties to define their position in the chain.

Another solutions I thought about were before and after attribute Eg.

[Before(typeof(ImageByNameProvider))]
class ImageFromMediaLocationProvider {}

But, I am not sure if this is easier or harder to program against.

Are there any other solutions to this problem? Which solution would you go with?

Perhaps, I should just keep the list for the core providers and add the Before/After attribs for third party providers ...

+1  A: 

It seems like there are actually a few different issues here that should be addressed. The fundamental problem is trying to come up with a mechanism that allows an arbitrary object to be inserted in to an existing list at some point in the middle of that list.

You don't describe what the IMetadataProvider interface actually looks like, but it should have some way to uniquely identify a provider (best option would be to use a Guid). The benefit over using the class name is that it allows you to rename the classes as needed during refactoring, etc. without affecting custom (3rd party) providers as long as you keep the Guid the same.

Rather than using a simple List you should probably derive your own list:

class ProviderList : List<IMetadataProvider { }

which exposes a way for a custom (3rd party) provider to install/deinstall itself from that list. These mechanisms need to be smart enough to know how to insert the new provider in to the middle of the chain but also smart enough to know how to handle multiple custom providers which have been inserted. Likewise, the removal process needs to be smart as well to deal with similar concerns and also ensure that someone doesn't try to remove one of your "core" providers.

A good approach here would probably be to pass the Guid of the provider you want to be inserted after as a parameter to the Install() method. The Remove() method would likewise take the Guid of the provider to be removed.

For example, say I insert a new provider after MovieProviderFromXml. Then another 3rd party also installs a new provider after MovieProviderFromXml. What should the new chain order be? Does the second provider always insert immediately after MovieProviderFromXml or does it start there and then skip past any custom providers and insert after the last custom provider installed (so just before the next "core" provider?

Related to that question is the idea that you need to have some way to distinguish between your "core" providers and a custom provider.

Finally, you need to make sure there is a way to handle failures in the chain, particularly when a custom provider is inserted in the wrong location.

You do want to always maintain a base ("master") list of your default chain. When a new provider is installed in the middle of that chain, a new chain should be created but you don't want to loose the base chain. This gives you the ability to reset the chain back to the default state.

Chaining based on priority is problematic in that you then have to determine how to handle priority collisions. As far as a Before/After attribute set, would you allow both on the same provider? Probably not, so it might make more sense to create a ProviderChainAttribute that has a ChainInsert enum property, where ChainInsert defines Before and After as enum values. This allows you to force the custom provider to make a decision as to whether it installs before or after the specified provider. I would still use a Guid rather than the type.

Hopefully this gives you some other ideas on how to approach this problem.

Scott Dorman
Scott, thanks for your time and your thoughtful reply, I will try to expand my question in the next day or so to cover some of your ideas, this is a fairly complex problem. +1
Sam Saffron