Sunday, June 08, 2008

MAF Gymnastics: Skeletal Solution

To kick off this series of posts, I've put together a skeletal solution that will be used as a starting point for subsequent posts. MAF requires a lot of plumbing code and infrastructure to be in place before even the simplest example will work. As such, it is prudent for me to avoid duplicating much of that plumbing for each post.

If you download the solution, you'll see that it is comprised of quite a few different projects. Those projects are partitioned into solution folders.

image 

Most of the projects are contained in the "Pipeline" solution folder. It is these projects that implement the pipeline that MAF uses to connect the add-in to the host. The "AddIns" solution folder contains any add-in projects (there is one sample add-in in the download). Finally, the "Host" project is a simple console host that activates any add-ins that it finds.

The Pipeline

The pipeline consists of a bunch of projects:

image 

The Contracts project defines the contracts that MAF will load into both sides of the isolation boundary. It consists of a few simple contracts: IHostContract , IAddInContract and IApplicationAddInContract.

IHostContract provides some properties that add-ins can invoke to get information about their host (name and version, to be specific). IAddInContract defines a single Initialize() method that add-ins implement. This method takes an instance of IHostContract as a parameter, so the add-in can invoke members against the host. IApplicationAddInContract extends IAddInContract but does not add any members. In later posts I will have more than one type of add-in. IAddInContract will serve as a base contract for add-ins.

The AddInViews and HostViews projects define the view of the world for the add-in and host respectively. In this case, they mirror the contracts very tightly, but in later posts we'll see views that differ quite drastically from their corresponding contracts.

The AddInSideAdapters and HostSideAdapters projects provide adapters that MAF uses to adapt views to contracts and vice-versa. The adapters allow the view to be ignorant of the contract and vice-versa, which is required for supporting version tolerance.

Note that I have not used any code generation or tools to generate the pipeline. It is written entirely by hand. The reason I did not use Pipeline Builder is because it is simply not mature enough. I ran into many bugs and limitations when I tried to use it, and it does not give enough control over the generated code.

When I originally decided to ditch Pipeline Builder and write my pipeline by hand, I tried factoring out common adapter functionality into generic base classes. Unfortunately, MAF does not allow adapter classes to inherit from generic classes, even if the adapter itself is closed (I consider this an awful bug and have reported it as such). Moreover, common utility code for adapters cannot be placed into a separate assembly and placed into each adapter's folder because MAF will not load it (unless that assembly is placed into the GAC).

For these reasons, I had to jump through some hoops to factor out this code. Basically, if you look under the "Pipeline\Shared Code" solution folder you'll see some source code files. These files are then linked to from the adapter projects. In effect, the code is stored in one place (good for maintenance) but compiled into two separate projects (to appease MAF).

image

The utility code itself isn't as clean as it would be if MAF supported adapters with generic base types. However, it's still pretty straightforward. Adapters implement one of the two interfaces (IContractToViewAdapter<TContract> or IViewToContractAdapter<TView>) according to what type of adapter they are. From there, the static Adapt class can be used to convert views to contract and vice-versa. This cuts down on a lot of code repetition, but - again - not as much as I'd like.

The other option I had was to use a simple code generation tool. In a "real" project I would probably do exactly that, but I didn't want to complicate these examples.

The Add-in

Included in the solution is a very simple add-in that simply outputs some information when it is initialized by the host (via the Initialize() method):

[AddIn("Sample add-in")]
public class AddIn : IApplicationAddIn
{
    public void Initialize(IHost host)
    {
        Console.WriteLine("This is the sample add-in, running inside host with name '{0}', version {1}. AppDomain name is '{2}'.", host.Name, host.Version, AppDomain.CurrentDomain.FriendlyName);
    }
}

As you can see, it outputs the details of the host (name and version) and the name of the AppDomain in which it is running (to prove we're being hosted in a separate AppDomain).

The Host

The Host project is a console application that activates and initializes any add-ins it finds that implement the IApplicationAddIn interface. The most pertinent code is contained in the Host.Run() method. This is the code that updates the add-in store, finds and activates any add-ins, and then initializes them.

When you execute this project, you should see something like this:

image

Deployment

MAF has strict requirements on how the pipeline and add-ins are deployed. If you've read through the MSDN documentation, you'll see recommendations on creating a solution-level output folder and deploying all DLLs within there. I don't like this recommendation because it's non-standard and doesn't deal with multiple configurations (although you could have output\Debug and output\Release folders).

In order to remain somewhat standard and not cause too much confusion, my solution builds into the output folder of the Host project, as you'd expect. Under bin\Debug (or bin\Release) you'll find directories called Pipeline and AddIns. Pipeline segments and add-ins are automatically copied to these directories when you build the solution.

If you're wondering how these files are copied, take a look in one of the project files. For example, crack open the SampleAddIn.csproj file and you'll see this at the end:

<Target Name="AfterBuild">
    <Copy SourceFiles="$(OutputPath)\SampleAddIn.dll" DestinationFolder="..\Host\$(OutputPath)\AddIns\SampleAddIn"/>
</Target>

Conclusion

Well, this has turned into a long post - and we haven't even started yet! MAF is a complex beast - incredibly frustrating at times, and yet very rewarding at others. Hopefully I have set the stage for the subsequent posts, where I will look at how some specific application scenarios can be addressed in MAF.

3 comments:

Anonymous said...

Hi Kent,

Is it ok to use any of this code in our own projects? As I notice it's copyright Credit Swiss.

Thanks,

M

Kent Boogaart said...

Nah, it's fine M. Just a project that was created on my work machine, hence the auto-generated copyright.

Anonymous said...

Cheers Kent,

I've just been going crazy trying to get the March release of Pipeline builder to work... I was starting to think it would be easier to do it all "the old way" and sort out the app domains etc... myself. Either that or take a while (a long while!) to figure MAF out.

Anyway, impressive. A big thanks,

M