Archive for the ‘MEF’ Category

Using MEF scopes to support service overrides

December 28th, 2011

Warning: file(http://svn.wp-plugins.org/devformatter/branches/langs/cs.php): failed to open stream: HTTP request failed! HTTP/1.1 404 Not Found in C:\websites\castle\blogs\hammett_castleblog\wp-content\plugins\devformatter\devgeshi.php on line 100 Warning: implode(): Invalid arguments passed in C:\websites\castle\blogs\hammett_castleblog\wp-content\plugins\devformatter\devgeshi.php on line 100 Warning: file(http://svn.wp-plugins.org/devformatter/branches/langs/cs.php): failed to open stream: HTTP request failed! HTTP/1.1 404 Not Found in C:\websites\castle\blogs\hammett_castleblog\wp-content\plugins\devformatter\devgeshi.php on line 100 Warning: implode(): Invalid arguments passed in C:\websites\castle\blogs\hammett_castleblog\wp-content\plugins\devformatter\devgeshi.php on line 100 Warning: file(http://svn.wp-plugins.org/devformatter/branches/langs/cs.php): failed to open stream: HTTP request failed! HTTP/1.1 404 Not Found in C:\websites\castle\blogs\hammett_castleblog\wp-content\plugins\devformatter\devgeshi.php on line 100 Warning: implode(): Invalid arguments passed in C:\websites\castle\blogs\hammett_castleblog\wp-content\plugins\devformatter\devgeshi.php on line 100

One of the coolest thing in the newest version of MEF is scoping support. Not because I’ve participated in designing and eventually patented some aspects of it – not at all – but because it’s actually quite helpful.

Scopes, as you can imagine, allow you to define isolated “bubbles” of composition.

On the components side, we use an ExportFactory (should have been ScopeFactory, but dont get me started on this one) with CreationPolicy.NewScope (ughh) to achieve this behavior. On the hosting side, you need to define an hierarchy of CompositionScopeDefinition and give it to the container.

One thing that is particularly handy in IoC containers is supporting default services, but allow for overrides. Windsor even use this taxonomy. The following is a trick that uses scopes to achieve the same behavior.

Before saying that it’s easier, simpler, faster to achieve that using Container X, Y or Z, remember that MEF is all about federated systems, and that comes with challenges and constraints.

Here’s the trick: I define three scopes: Default, Overrides and App. All default services are bound to the Default scope. The app level components live in the App scope. In the middle, the Override scopes will be used to replace default services.

 cs |  copy code |? 
01

02
    using System.ComponentModel.Composition;
03
    using System.ComponentModel.Composition.Hosting;
04
05
    class Program
06
    {
07
        static void Main(string[] args)
08
        {
09
            var ubber = new AssemblyCatalog(typeof (Program).Assembly);
10
11
            var defLevelCat = ubber.Filter(cpd => cpd.ContainsPartMetadata("Scope", "Default"));
12
            var overLevelCat = defLevelCat.Complement.Filter(cpd => cpd.ContainsPartMetadata("Scope", "Override"));
13
            var appLevelCat = overLevelCat.Complement.Filter(cpd => cpd.ContainsPartMetadata("Scope", "App"));
14
15
            var appDef = new CompositionScopeDefinition(appLevelCat, new CompositionScopeDefinition[0],
16
                                                        appLevelCat.Parts.SelectMany(p => p.ExportDefinitions));
17
            var overDef = new CompositionScopeDefinition(overLevelCat, new [] { appDef },
18
                                                         overLevelCat.Parts.SelectMany(p => p.ExportDefinitions));
19
            var defDef = new CompositionScopeDefinition(defLevelCat, new[] { overDef },
20
                                                        defLevelCat.Parts.SelectMany(p => p.ExportDefinitions));
21
22
            var cont = new CompositionContainer(defDef);
23
24
            var entryPoint = cont.GetExportedValue<EntryPoint>();
25
            var sndLevel = entryPoint.Creator.CreateExport().Value;
26
            var lastLevel = sndLevel.Creator.CreateExport().Value;
27
            var component = lastLevel.com;
28
29
        }
30
    }
31
32
    [Export]
33
    [PartMetadata("Scope", "Default")]
34
    public class EntryPoint
35
    {
36
        [Import(RequiredCreationPolicy = CreationPolicy.NewScope, AllowDefault = true)]
37
        public ExportFactory<OverrideLevelScopeCreator> Creator { get; set; }
38
    }
39
40
    [Export]
41
    [PartMetadata("Scope", "Override")]
42
    public class OverrideLevelScopeCreator
43
    {
44
        [Import(RequiredCreationPolicy = CreationPolicy.NewScope, AllowDefault = true)]
45
        public ExportFactory<AppLevelScopeCreator> Creator { get; set; }        
46
    }
47
48
    [Export]
49
    [PartMetadata("Scope", "App")]
50
    public class AppLevelScopeCreator
51
    {
52
        [Import]
53
        public Component com { get; set; }
54
    }
55
56
    public interface IService
57
    {
58
    }
59
60
    [Export(typeof(IService))]
61
    [PartMetadata("Scope", "Default")]
62
    public class DefaultService : IService
63
    {
64
    }
65
66
    [Export(typeof(IService))]
67
    [PartMetadata("Scope", "Override")]
68
    public class OverrideService : IService
69
    {
70
        [Import(Source = ImportSource.NonLocal)]
71
        public IService ServParent { get; set; }
72
    }
73
    
74
    [Export, PartMetadata("Scope", "App")]
75
    public class Component
76
    {
77
        [Import]
78
        public IService Serv { get; set; }
79
    }
80

Have fun!

Categories: .NET, MEF | Top Of Page | 2 Comments » |

A “decent” DirectoryCatalog implementation

December 27th, 2011

Warning: file(http://svn.wp-plugins.org/devformatter/branches/langs/cs.php): failed to open stream: HTTP request failed! HTTP/1.1 404 Not Found in C:\websites\castle\blogs\hammett_castleblog\wp-content\plugins\devformatter\devgeshi.php on line 100 Warning: implode(): Invalid arguments passed in C:\websites\castle\blogs\hammett_castleblog\wp-content\plugins\devformatter\devgeshi.php on line 100 Warning: file(http://svn.wp-plugins.org/devformatter/branches/langs/cs.php): failed to open stream: HTTP request failed! HTTP/1.1 404 Not Found in C:\websites\castle\blogs\hammett_castleblog\wp-content\plugins\devformatter\devgeshi.php on line 100 Warning: implode(): Invalid arguments passed in C:\websites\castle\blogs\hammett_castleblog\wp-content\plugins\devformatter\devgeshi.php on line 100 Warning: file(http://svn.wp-plugins.org/devformatter/branches/langs/cs.php): failed to open stream: HTTP request failed! HTTP/1.1 404 Not Found in C:\websites\castle\blogs\hammett_castleblog\wp-content\plugins\devformatter\devgeshi.php on line 100 Warning: implode(): Invalid arguments passed in C:\websites\castle\blogs\hammett_castleblog\wp-content\plugins\devformatter\devgeshi.php on line 100

Yes, I’ve raised this with the MEF team many times. It’s easier to go and do it yourself.

The issue is that DirectoryCatalog does not guard against failures on loading types (which is quite common as soon as you start working on real projects as opposed to samples – hint hint!). So here’s a better version (in F#):

 cs |  copy code |? 
01
02
    type DirectoryCatalogGuarded(folder) = 
03
        inherit ComposablePartCatalog()
04
05
        let _catalogs = List()
06
        let mutable _parts : ComposablePartDefinition seq = null
07
08
        let load_assembly_guarded (file:string) = 
09
            try
10
                let name = AssemblyName.GetAssemblyName(file);
11
                Assembly.Load name
12
            with | ex -> null
13
14
        let guard_load_types (asm:Assembly) =
15
          try
16
              asm.GetTypes()
17
          with | :? ReflectionTypeLoadException as exn -> exn.Types
18
19
        do 
20
            let files = Directory.GetFiles(folder, "*.dll")
21
            for file in files do
22
                let asm = load_assembly_guarded file
23
                if asm <> null then
24
                    let types = guard_load_types(asm) |> Seq.filter (fun t -> t <> null) 
25
                    if not (Seq.isEmpty types) then
26
                        _catalogs.Add (new TypeCatalog(types))
27
            _parts <- _catalogs |> Seq.collect (fun c -> c.Parts) 
28
29
        override x.Parts = _parts.AsQueryable()
30
31
        override x.GetExports(definition) = 
32
            _catalogs |> Seq.collect (fun c -> c.GetExports(definition))
33

The “trick” is to catch ReflectionTypeLoadException – something I learned from David. This exception gives you a list of types it was able to load. Mind you, nulls may exists in the list hence the filtering.

The shortcoming here is that by bypassing the AssemblyCatalog, you wont be able to use CatalogReflectionContextAttribute – which I believe I’m one of the ten people on earth that knows what it’s for, so no biggy I guess?

Categories: MEF | Top Of Page | 2 Comments » |

MEF and ASP.NET MVC sample

April 23rd, 2009

Just blogged it on the other blog.

Categories: MEF, MS | Top Of Page | No Comments » |

Workshop on Inversion of Control Containers

February 18th, 2009

Next Friday (Feb-27) I’ll be in Seattle doing a workshop on one of my favorite topics: Inversion of Control Containers. Expect some digressions on the story of ioc containers, how they evolved, what problems they solve and what problems they introduce. Also why I think the DI term should be banished – call me a purist. Windsor, MEF and maybe others will be on my radar.

If you want to attend, keep an eye on this page. Currently registrations are closed, but should open in a few days.

I truly hope I dont have a cold next week :-)

MEF: Exporting and importing methods

February 17th, 2009

This is an interesting feature of MEF, usually overseen (maybe because it’s not documented anywhere). You can export methods with a specific shape and import them to delegates:

public class Validation
{
    [Export("validator")]
    public bool ValidateEmail(string value)
    {
        return true;
    }

    [Export("validator")]
    public bool ValidateNonEmpty(string value)
    {
        return true;
    }
}

[Export]
public class FormToValidate
{
    [Import("validator")]
    public IEnumerable<Func<string, bool>> Validators { get; set; }

    public void ValidateName(string username)
    {
        foreach(var validator in Validators)
        {
            if (!validator(username))
            {
                throw new Exception("validation error");
            }
        }
    }
}

Before you ask, yes this feature came from real world scenarios and we have plenty of customers using it. So far, we are constrained by the maximum number of arguments Func/Action supports, but Wes is in the process of fixing that.

Categories: MEF | Top Of Page | 4 Comments » |

Defaulting to Transient parts

February 8th, 2009

David has a nice blog post on having a custom container to default MEF parts to NonShared creation policy. Go MEF team! :-)

Categories: MEF | Top Of Page | 3 Comments » |

MEF and lifetime

January 30th, 2009

It has been interesting three months working on the lifetime story for MEF. Turned out to be bigger and more complex than any one could anticipate. As I walk you through what has been decided and implemented you may find things too trivial, but actually it was huge as key members in our team wanted something way different. Under this light I really considered the MEF/Lifetime a success as we – the feature crew (Wes and Daniel) – were able to demonstrate clearly the impacts of their proposed changes and how our proposal solved key scenarios.

Here is a summary of the top level decisions:

  • We will continue to support singletons and transients (we renamed them to Shared and NonShared)
  • We will continue to support disposable types (even when IDisposable is not part of the public contract of the service)
  • The container will have the ownership of parts it instantiates (we will not transfer ownership to parts)

You can get the bits from CodePlex, Preview 4 has all the lifetime features implemented and documented.

 

CreationPolicy

We now support three creation policies. They can be specified on the part level – using the CompositionOptionsAttribute – or on the import level, using the ImportAttribute. Both sides need to permissible in order to the dependency be satisfied.

  • Shared: former Singleton. A single instance per lifetime domain (ie, container)
  • NonShared: former Factory. Whenever an export is requested, a new instance is created.
  • Any: defines that the part can function as Shared or NonShared, it would be up to the import side to define. If “Any” is specified on both sides, it will default to Shared.

Container and parts references

The CompositionContainer will not hold references to parts it creates, unless for the following cases:

1. The ComposablePart.RequiresDisposal returns true – which for our default programming will be true only when it wraps an object instance that implements IDisposable

2. The part has one or more imports marked with AllowRecomposition=true. In this case, though, we hold conditional references, which means that while there’s an alive export instance the GC won’t collect the part instance.

3. Shared parts

For all other situations, parts are created, exports gathered and we drop the references leaving everything else to the GC.

One thing to note: if have NonShared disposable parts you might incur in memory bloat. There are two ways to avoid that, described below.

 

Disposing the container

Disposing the container will shutdown parts held (possibly disposing them, and disabling recomposition). Lazy exports wont work anymore. As any disposable type, we haven’t engineered the container to be reusable after being disposed.

 

Scoping containers

You can use container hierarchies in order to create scoped lifetime. For example, a web request is a great candidate to be mapped that way. However, as the container depends on Catalogs you will either have to filter the global catalog – the one that the parent container has access to – or handcraft a new catalog. Using the same catalog on the parent and on the child container won’t make much sense IMHO as then you start to have short lived Singletons.. and this doesn’t feel right to me.

Check the Filtering Catalogs for more information on this.

 

Early release of resources

If you don’t want to use container hierarchies, you can also release an object graph. This is a great solution for scoped operations that you control when it starts and ends.

Just use the method ReleaseExport exposed on the CompositionContainer class. It traverses the parts and its imports releases NonShared parts. It stops in Shared parts as we cannot touch them.

 

Lifetime of parts manually added to the container

When you add an object instance directly to the container you do not transfer its ownership to it – at least not now, we’re discussing and reviewing this behavior. However, adding a part manually will cause composition, so new parts may be created to satisfy your object dependencies. Who own them? The container. MEF is smart enough to shutdown them when you remove that object from the container.

 

Ordering

Due to the fact that we support – to some extend – cycled object graphs, we cannot create a 100% deterministic disposal algorithm. Hence, we do not guarantee disposal ordering and urge you to follow a simple guideline:

“Do not use imports on your dispose method.”

That means, do not use the things your part imported on your implementation of the Dispose method. Why? Because as we do not guarantee ordering, they might have been disposed when you try to use them.

 

Conclusion

It’s been a fun ride. Given the fact that we considered removing support for NonShared, transfer ownership of exports to imports and all sorts of options, I’m absolutely positive that we have made the right choices. I’ve learned that there’s no such thing as the perfect design for complex problems. In that case you have to strive for the right trade-offs and make sure you provide solutions for the key scenarios.

Categories: MEF, MS | Top Of Page | 1 Comment » |

MEFGrid

January 15th, 2009

Daniel managed to create a sample that goes over virtually all features of MEF. Mind blowing, I know. That’s why he’s know as “da man” in the team.

If you’re following MEF you should check it. Make sure you also subscribe to his blog.

Categories: MEF | Top Of Page | No Comments » |

On internals

November 13th, 2008

I hate internals and privates and lack of extension points. Really! Many times I’ve wondered ‘what’s wrong with these people that hide all this stuff from me? are they concerned I wouldn’t know how to use it?’

Being on the other side now, and trying to slowly change the culture, I have to say that I understand why MS is so reluctant to make things more open by default. And all comes down to the fact that we don’t make breaking changes.

If we were to open a poll and ask developers what they would prefer: more open and extensible API vs no breaking changes, I’d guess you would go with open/extensible. And later act surprised when a new version of the framework broke your app, forced you to make changes on your code in order to get it compiling, and so on.

That doesn’t mean that openness is forbidden and extension points are evil. Just that we think very – and I mean _very_ – carefully about those. Once we ship it, we can’t remove it. We can’t change it. Ever!

That being said, I think we are going slowly but surely in the direction of open-closed principle and composition over inheritance, at least on my team, and this is enough to make me happy (for now).

Categories: MEF, MS | Top Of Page | 9 Comments » |

MEF/Lifetime: NonShared support

November 10th, 2008

MEF currently support two creation policies: Singleton and Factory. You can think of them as Singleton and Transient or even better: Shared and NonShared. Supporting NonShared is very convenient to users, but comes with costs. So we are considering supporting only Singletons in V1.

In the end, all these control your choice of how state should be shared or not between consumers. If you expose an IEmailSender implementation, whether it should be shared between users or not will depend on how much you care about sharing or not the state – if any – in the service implementation.

It doesn’t mean that you can’t mimic NonShared behavior. It just means that you have to write a bit more code in order to do that.

Say that you have a Foo component, and you want that each “consumer” of Foo to have their own exclusive, non-shared copy of it:

public class MyObject
{
    [Import]
    public FooFactory Factory { get; set; }
    
    private Foo _foo;
    public Foo Foo 
    { 
        get 
        { 
            if (_foo == null) 
            {
                _foo = Factory.CreateFoo();
            } 
            return _foo;
        }
    }

    public void DoWork()
    {
        // Do work with Foo
    }
}

So MyObject cannot simply import Foo. It needs to import something that carries the semantic of creating always a new instance. So it imports a Factory that you created.

Now you probably have a Foo that has dependencies. Fair enough. You can support that using at least two approaches.

In the first scenario, you know before hand the dependencies:

public class Foo
{
    public Foo(Baz baz)
    {
       Baz = baz;
    }

    public Baz Baz { get; set; }

    public Bar Bar { get; set; }
}


public class FooFactory
{
    [Import]
    public Bar Bar { get; set; }

    [Import]
    public Baz Baz { get; set; }

    public Foo CreateFoo()
    {
        var foo = new Foo(Baz);

        foo.Bar = Bar;

        return foo;
    }
}

The second situation you do not know the dependencies:

public class Foo
{
    // Assume Foo has unknown dependencies
}

public class FooFactory
{
    [Import]
    public ICompositionService CompositionService { get; set; }

    public Foo CreateFoo()
    {
        var foo = new Foo();

        CompositionService.SatisfyImports( CompositionServices.CreateAttributedPart(foo));

        return foo;
    }
}

We are really looking for feedback on this one. Do you think that’s reasonable? Would you be angry or frustrated if you had to write this code? And before you ask, we are not considering ripping off the feature because we don’t have anything better do to. :-) It all comes down to the fact that supporting factories lead to situations that we are unable to provide good solutions for.

Categories: MEF, MS | Top Of Page | 13 Comments » |