A while back, I had started playing with the Managed Extensibility Framework (MEF) from Big Mike – an extensibility framework that lets you discover components that implements your interface, thus allowing you to do Software Composition. For close to a year now, I have also been playing with ServiceStack – a clean and elegant Open Source solution for Web Services.
I’ve wondered for quite a while now if the two could be combined, i.e. if it was possible to do Service Composition by using MEF and ServiceStack. I figured it could be done, but could never find the time to verify my assumptions…until now.
“Why would you ever want to do such a thing?”, you may ask. Outside of the obvious “because you can”, there is value in making your service layer extensible, especially on a large project in which multiple teams are involved. So as to protect code boundaries and prevent a big mess in which multiple teams would have access to the same code repository, each team could simply supply their DLLs containing their services and our Service Layer would pick them up and configure them.
We first need to define an interface. The IComposobaleBootstrapper is a simple interface with two methods: Compose and GetAssembly.
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
using System.Text; | |
using ServiceStack.ServiceHost; | |
namespace Common | |
{ | |
public interface IComposobaleBootstrapper | |
{ | |
void Compose(Funq.Container container, IServiceRoutes routes); | |
Assembly GetAssembly(); | |
} | |
} |
- Compose: this method lets our implementors define what objects they need injected, as well as the routes they want for their service
- GetAssembly: this method is necessary for our Application Host. At launch time, ServiceStack loads up all the services in your application based on the Assemblies you pass in. Because we do not know in which assemblies our implementors have defined their Services, we put the Onus on them to give us that information
Now that we have an interface, we can implement it in our sample service below.
using System; | |
using System.Collections.Generic; | |
using System.ComponentModel.Composition; | |
using System.Linq; | |
using System.Reflection; | |
using System.Text; | |
using Common; | |
using Funq; | |
using ServiceStack.ServiceHost; | |
using ServiceStack.ServiceInterface; | |
namespace ComposingService | |
{ | |
public class AwesomeService : Service | |
{ | |
public object Get(AwesomeRequest request) | |
{ | |
return "Yes, you're awesome " + request.Name; | |
} | |
} | |
public class AwesomeRequest | |
{ | |
public string Name { get; set; } | |
} | |
[Export(typeof(IComposobaleBootstrapper))] | |
public class Composer:IComposobaleBootstrapper | |
{ | |
public void Compose(Container container, IServiceRoutes routes) | |
{ | |
routes.Add<AwesomeRequest>("/awesome/{Name}"); | |
} | |
public Assembly GetAssembly() | |
{ | |
return typeof (AwesomeService).Assembly; | |
} | |
} | |
} |
The last piece is the Application host
using System; | |
using System.ComponentModel.Composition; | |
using System.ComponentModel.Composition.Hosting; | |
using System.IO; | |
using System.Linq; | |
using System.Configuration; | |
using System.Collections.Generic; | |
using System.Web.Mvc; | |
using Common; | |
using ServiceStack.Mvc; | |
using ServiceStack.WebHost.Endpoints; | |
[assembly: WebActivator.PreApplicationStartMethod(typeof(Spike.AppHost), "Start")] | |
namespace Spike { | |
public class AppHost | |
: AppHostBase { | |
[ImportMany] | |
List<IComposobaleBootstrapper> m_composers; | |
private CompositionContainer m_container; | |
public AppHost() //Tell ServiceStack the name and where to find your web services | |
: base("Making it Happen", typeof(HelloService).Assembly) { | |
var catalog = new AggregateCatalog(); | |
var servicesFolder = @"C:\Spike\bin"; | |
catalog.Catalogs.Add(new DirectoryCatalog(servicesFolder)); | |
//TODO: to load based on folder and sub folders | |
//var folders = Directory.GetDirectories(servicesFolder, "*", SearchOption.AllDirectories); | |
//foreach (string folder in folders) { | |
// var directoryCatalog = new DirectoryCatalog(folder); | |
// catalog.Catalogs.Add(directoryCatalog); | |
//} | |
m_container = new CompositionContainer(catalog); | |
m_container.ComposeParts(this); | |
if (m_composers.Count > 0) { | |
EndpointHost.ConfigureHost(this, "Composed Services", CreateServiceManager(m_composers.Select(e => e.GetAssembly()).ToArray()));//add distince statement top prevent dups | |
} | |
} | |
public override void Configure(Funq.Container container) { | |
ServiceStack.Text.JsConfig.EmitCamelCaseNames = true; | |
Routes.Add<Hello>("/hello") | |
.Add<Hello>("/hello/{Name*}"); | |
foreach (var composobaleBootstrapper in m_composers) { | |
composobaleBootstrapper.Compose(container, Routes); | |
} | |
//Enable Authentication | |
ConfigureAuth(container); | |
//Register all your dependencies | |
container.Register(new TodoRepository()); | |
container.Register(new UserRepository()); | |
//Set MVC to use the same Funq IOC as ServiceStack | |
ControllerBuilder.Current.SetControllerFactory(new FunqControllerFactory(container)); | |
} | |
private void ConfigureAuth(Funq.Container container) { | |
//Auth stuff | |
} | |
public static void Start() { | |
new AppHost().Init(); | |
} | |
} | |
} |
The most important line in the Application Host is line #43, which is where we tell ServiceStack to load up the Assemblies containing our Composable Service Definitions.
Voila! I hope this helps!
[…] while ago I had blogged about Service Composition using ServiceStack and MEF. With ServiceStack no longer free, I figured I’d try the same thing using ASP.NET Web Api […]