This project has moved. For the latest updates, please go here.
This documentation page describes features currently in development and is a subject to change.

Managed Extensibility Framework integration

Managed Extensibility Framework (MEF) is an advanced IoC container and add-in framework available in .NET 4.0. Server-side MEF integration allows to set up server application declaratively by decorating public services with special attributes. Client-side integration provides abstraction layer over remote services: you use them just like local services.

Server-side: ZyanComponentHost + MEF

MEF infrastructure discovers and assembles server components, and ZyanComponentHost publishes them automatically. ZyanComponentHost and ComponentCatalog pull components out of MEF's CompositionContainer or ComposablePartCatalog instances.

Component lifetime is managed by MEF, as specified by PartCreationPolicyAttribute. Shared parts act like singletons (single instance per root CompositionContainer), NonShared parts are created and disposed of within nested CompositionContainers on every remote call. NonShared part creation policy should be applied to single-call components, and Shared policy — to singletons.

Server-side setup code example:

// MEF integration resides in a separate namespace
using Zyan.Communication.Composition;

// create any kind of MEF catalogs and/or containers
var catalog = new AggregateCatalog(...);
var rootContainer = new CompositionContainer(catalog);

// instantiate component host
using (var host = new ZyanComponentHost(...))
{
    // register all public components from the container
    host.RegisterComponents(rootContainer);

    // ... or catalog itself
    host.RegisterComponents(catalog);
}

Server components need to be decorated with special attributes.
There are three options available, each with its own pros and contras:
  1. Zyan built-in attributes (ZyanComponent, ZyanInterface)
  2. Standard MEF attributes (Export, InheritedExport, ExportMetadata)
  3. Custom attributes derived from MEF's ExportAttribute

1.1 Using Zyan built-in attributes

Pros: short, clear, ready to use out-of-the-box
Contras: server components need to reference Zyan.Communication.dll

1.1.1 ZyanComponent attribute

Decorate a class with ZyanComponent attribute, specify component interface and optional unique name. More than one ZyanComponent attribute can be applied to a class.
Sample code:

// Shared assembly
interface ISampleService
{
    ...
}

// Server implementation assembly
[ZyanComponent(typeof(ISampleService))]
class SampleService : ISampleService
{
    ...
}

// This component is published twice, under different names
[ZyanComponent("SomeUniqueName", typeof(ISampleService))]
[ZyanComponent(typeof(ISomeAnotherService))]
class AnotherService : ISampleService, ISomeAnotherService, INonPublicService
{
    ...
}

1.1.2 ZyanInterface attribute (not recommended)

Decorate an interface with ZyanInterface attribute, specify interface type and optional unique name. Server component implementing this interface will be published automatically.

This method of registration is not recommended for two reasons:
  1. Attribute constructor requires interface type parameter to export ComponentInterface metadata. It's inevitable because the attribute don't have access to the object it is applied to. This is obviously redundant, error-prone and simply doesn't look nice.
  2. If more than one component implements interface decorated with ZyanInterface attribute, Zyan will not be able to decide which component to choose, which will result in ImportCardinalityMismatchException thrown at runtime.

Sample code:

// Shared assembly will also need to reference Zyan.Communication.dll
[ZyanInterface(typeof(ISampleService))]
interface ISampleService
{
    ...
}

// Component implementation don't need any attributes
class SampleService : ISampleService
{
    ...
}

1.1.3 Disabling automatic publication of the component

Remove ZyanComponent / ZyanInterface attributes or set IsPublished parameter to false:

[ZyanComponent(typeof(ISampleService), IsPublished = false)]
class SampleService : ISampleService
{
    ...
}

1.2 Using MEF metadata attributes

Pros: server components don't need to reference Zyan.Communication.dll
Contras: verbose (more attributes), error-prone (hardcoded string constants)

1.2.1 Using MEF metadata with Export attribute

Sample code:

interface ISampleService
{
    ...
}

[Export(typeof(ISampleService))]
[ExportMetadata("ComponentInterface", typeof(ISampleService))]
class SampleService : ISampleService
{
    ...
}

1.2.2 Using MEF metadata with InheritedExport attribute (not recommended)

This registration option is not recommended: if more than one class implements interface marked with InheritedExport, Zyan will not be able to choose which component to register and ImportCardinalityMismatchException will be thrown.

Sample code:

[InheritedExport]
[ExportMetadata("ComponentInterface", typeof(ISampleService))]
interface ISampleService
{
    ...
}

// Component implementation don't need attributes
class SampleService : ISampleService
{
    ...
}

1.1.3 Disabling automatic publication

Remove ComponentInterface metadata or add IsPublished metadata attribute set to false:

[Export(typeof(ISampleService))]
[ExportMetadata("ComponentInterface", typeof(ISampleService)]
[ExportMetadata("IsPublished", false)]
class SampleService : ISampleService
{
    ...
}

1.3 Using custom MEF metadata attributes

Pros: short, no hardcoded strings, no reference to Zyan.Communication.dll needed
Contras: require manual coding

Write your own attribute similar to ZyanComponent or ZyanInterface. Inherit from MEF's ExportAttribute or InheritedExportAttribute, decorate with MetadataAttribute as follows:

[MetadataAttribute, AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class MyComponentAttribute : ExportAttribute
{
    public MyComponentAttribute(Type componentInterface) : base(componentInterface)
    {
        if (!componentInterface.IsInterface)
        {
            throw new InvalidOperationException("Interface type required: " + componentInterface);
        }

        ComponentInterface = componentInterface;
    }

    // This property is required
    public Type ComponentInterface { get; private set; }
}

// Server component implementation
[MyComponent(typeof(ISampleService))]
class SampleService : ISampleService
{
    ...
}

1.4 Queryable components registration

Zyan can automatically discover queryable components (i. e., server classes you can query via Linq). Since Zyan 2.1 release queryable components don't need any special handling. To use Linq, register a component with one or more parameterless methods returning either IEnumerable<T> or IQueryable<T> as follows:

// Shared interfaces assembly
public interface ISampleService
{
	IEnumerable<T> GetList<T>() where T : class;

	IQueryable<T> Query<T>() where T : class;
}

// Server implementation assembly
[ZyanComponent(typeof(ISampleService))]
public class SampleService : ISampleService
{
	// implementation details are omitted
}

// Client assembly
using (var connection = new ZyanConnection(url))
{
	var proxy = connection.CreateProxy<ISampleService>();
	var query =
	    from c in proxy.Query<Customer>()
	    where c.BirthDate > DateTime.Today.AddYears(-18)
	    select c;
	...
}

The following section only applies to Zyan 2.0 release and is subject for removal.

Queryable component should implement either IObjectSource or IEntitySource interface and be decorated with ZyanComponent attribute, as usual:

// Server implementation assembly
[ZyanComponent(typeof(IEntitySource))]
class SampleService : IEntitySource
{
    public IQueryable<T> Get<T>() where T : class
    {
        return MyDataContext.GetTable<T>();
    }
    ....
}

// Client assembly
var proxy = connection.CreateQueryableComponent();
var query =
    from c in proxy.Get<User>()
    where c.Login == "yallie"
    select new
    {
        c.FirstName,
        c.LastName
    };

1.5 Specifying component creation policy

Part creation policy is a strategy MEF uses to instantiate composable parts. Shared creation policy means that there will be not more than one instance per CompositionContainer, NonShared policy indicates that every export operation should create a new instance. Default policy is CreationPolicy.Any, which acts like CreationPolicy.Shared if import is not constrained to some specific creation policy.

Zyan host delegates part lifetime management to MEF container, but it prefers NonShared creation policy to emulate default SingleCall component behavior. If part creation policy is not specified or specified as CreationPolicy.Any, Zyan will assume that the component can be instantiated as a NonShared part.

You may wish to override part creation policy via PartCreationPolicy attribute. It can be freely mixed with ZyanComponentAttribute, ExportAttribute, or your own export attributes:

// This component will act like singleton
[MyComponent(typeof(ISampleService)), PartCreationPolicy(CreationPolicy.Shared)]
class SampleService : ISampleService
{
    ...
}

Client-side: ZyanConnection + MEF

Client-side integration will be implemented as custom MEF catalog linked to ZyanConnection instance or factory.

Further ideas

The goal is to make distributed application truly modular and loosely coupled. Zyan bootstrapper uses MEF to assemble application on both sides and provides communication infrastructure. Client and server components can be made totally independent of communication layer.

TODO:
  1. Server-side
    1. Server loader class
    2. Declarative hosts (endpoints)
    3. Declarative serialization handlers
    4. Component categories (to filter components for the certain host)
  2. Client-side
    1. Client bootstrapper
    2. Declarative connections
    3. Declarative call interceptors
    4. Declarative serialization handlers

Last edited Jan 30, 2012 at 10:31 PM by yallie, version 23

Comments

No comments yet.