Introduction
Welcome to the third part of this article series on strategies for designing your application for a multitude of different clients. In the first part of this series, we've had a look at the business case & some general concerns we'll have to tackle.
In the
second part, we made a
choice for a service layer: WCF RIA Services, looked into what this choice offered us and why we made it, and looked into some general guidelines you should keep in mind when designing a service layer which will be used by a multitude of clients.
In this part, we'll start with the development of the first client: a web-based Silverlight client.
You can download the source code for this article here (you can log in to the demo application with pete.campbell@madmen.com / abc@123).
Quick 'n dirty?
As stated, the first client we're going to build will be a Silverlight client.
The easy, quick 'n dirty way would be to simply add a Silverlight application to our solution, add our Views, ViewModels and application app logic to it, and let it communicate with the EmployeeOpportunityService we've created in the second part.
However, as we know we'll have to build multiple clients, that's probably not the best option: reuse of code would be fairly limited, in fact: we would only be able to reuse the service layer (and accompanying client-side model, generated through WCF RIA Services). There's got to be a way to improve on this, right?
The answer lies in modularity. As you might remember from the first parts of this article series, we're only creating one module for demo purposes: the Employee Opportunity module. The main Silverlight client could, before going into production, import multiple modules: a Management Module, a Reporting Module, .... It makes sense to logically separate these out: some clients might offer all modules, some of the modules, … . Separating this out offers an advantage: code reuse. Each module could be reused by a variety of clients (for example: our Silverlight Out of Browser client can reuse the same assemblies).
Add reference…
The first approach is quite similar to what any .NET developer is used to. Instead of adding the Views and ViewModels in our WebClient project, we create a new Silverlight class library. In this class library, we add the Views and ViewModels that logically belong to the Employee Opportunity Module. For other modules, we do the same.
In our WebClient project, we now add a reference to the class librar(y)(ies) of the modules that can be used by this specific client, and we add references to any assemblies that might be used by any of these modules.
Build, run, and we're set.
Or are we?
A better approach.
While the previous approach might be valid (we've got our reusability), it does offer a disadvantage: your final XAP contains all the code from the assemblies from the different modules, in other words: it can get quite big, resulting in slower load times and higher bandwidth consumption. Moreover, in quite a few applications the modules the user can access depends on his rights: why would we want to get an XAP, containing all modules, to a client who only has access to one module?
And what's more: as this is an article series on code reuse, something's not quite right with our modules either: we've still got no way to reuse Views without importing a specific ViewModel implementation, or the other way around: the module contains both. As we're building different clients, it's not unimaginable we'll want to reuse the code in a specific ViewModel, but use it as DataContext to a different View. Or we could have a client which want to reuse a specific View, but has no need for the ViewModel, as it provides its own, client-specific implementation.
So, what we want to do is keep our Views in one assembly, and keep our ViewModels in a different assembly. When we start our Silverlight client, the Employee Opportunity module will be composed by importing what's needed.
What we really need here is composability & on-demand loading: say hello to MEF.
The Managed Extensibility Framework - a short introduction.
MEF, or the Managed Extensibility Framework, was designed to simplify the design of extensible applications and components. Applications can have changing requirements, or need to be able to plug in extra pieces of logic. After some amount of time, it can become difficult to add new functionality easily to your application. MEF solves this problem by making it easy to “plug in” new functionality into your application. In our example, we can use this to "plug in" the modules when needed.
- First of all, there's the concept of Composition: you start by telling MEF where it can find the parts of the application it needs to provide by creating a catalog. From this catalog, you then create a CompositionContainer.
- Next, there's the Export concept: by using the Export attribute on a component, you tell MEF you want to export this component (type, property), so it can be composed via the container.
- Lastly, there's the Import (or ImportMany) concept, which tells MEF what type of components should be imported in a specific part of your application.
These 3 simple concepts are the basics of MEF, and should give a basic understanding of what MEF is and what it does. A lot more information can be found at http://mef.codeplex.com/
Note: in quite a few projects, MEF is used as a replacement for an Inversion of Control container, like Castle or Ninject. In my opinion, this is defendable (depending on your project requirements, of course), but in essence MEF is designed managing unknown dependencies (hence: plugability), while a classic IoC container is used for managing known dependencies.
Some refactoring & custom code
The first thing to do is separate out our Views from our ViewModels: they both need to be in separate assemblies. This results in the following project division:
For now, we'll keep the View assembly as a reference to our WebClient project, and we'll load the ViewModel assembly on-demand when the application starts (more on why I'm making this choice can be found at the end of the article).
… and we've bumped into a problem. In Silverlight, you can load XAP-files on demand with MEF, but you can't load assemblies on demand. Out of the box, there's no way to compose our application with external assemblies - which is what we'd like.
Luckily, Chris Pietschman (http://pietschsoft.com/) made this possible through his SuperDeploymentCatalog implementation. This is actually for about 95% the same code as a regular DeploymentCatalog, but he changed the HandleOpenReadCompleted method so it allows DLL importing. The complete code can be found in the accompanying source code, in Framework.Silverlight, but this is the part that matters:
private void HandleOpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
Exception error = e.Error;
bool cancelled = e.Cancelled;
if (Interlocked.CompareExchange(ref this._state, 0xbb8, 0x7d0) != 0x7d0)
{
cancelled = true;
}
if ((error == null) && !cancelled)
{
try
{
// Check if a XAP or DLL was downloaded, based on file extension
if (this._uri.OriginalString.ToLower().EndsWith(".xap"))
{
// Load Assemblies from XAP
IEnumerable<Assembly> assemblies = Package.LoadPackagedAssemblies(e.Result);
this.DiscoverParts(assemblies);
}
else
{
// Load DLL Assembly
var assemblies = new List<Assembly>();
assemblies.Add((new AssemblyPart()).Load(e.Result));
this.DiscoverParts(assemblies);
}
}
catch (Exception exception2)
{
error = new InvalidOperationException("Strings.InvalidOperationException_ErrorReadingXap", exception2);
}
}
this.OnDownloadCompleted(new AsyncCompletedEventArgs(error, cancelled, this));
}
In the Application_Startup method, we'll load our ViewModel assembly. First, we create a new AggregateCatalog instance. To this type of catalog, other catalogs can be added dynamically (this will also come in useful later on in this article series). Next, we create a new SuperDeploymentCatalog, pointing to the ViewModel assembly. This catalog is added to the Catalogs collection of the AggregateCatalog instance.
We add an event handler to the DownloadCompleted event of our SuperDeploymentCatalog instance. When this event is correctly completed, we set the RootVisual to a new instance of our applications' MainPage. Note that this shouldn't be done earlier, as MainPage contains a View which in turn imports a ViewModel instance: that ViewModel instance is only available after the ViewModel assembly has been loaded.
Next, we'll call the DownloadAsync() method, and we initialize MEF's CompositionHost by passing in our AggregateCatalog instance.
private void Application_Startup(object sender, StartupEventArgs e)
{
this.Resources.Add("WebContext", WebContext.Current);
WebRequest.RegisterPrefix("http://", WebRequestCreator.ClientHttp);
WebRequest.RegisterPrefix("https://", WebRequestCreator.ClientHttp);
var catalog = new AggregateCatalog();
var catalogVM = new SuperDeploymentCatalog("SalesDashboard.SL.Modules.EmpOpp.ViewModels.dll");
catalog.Catalogs.Add(catalogVM);
catalogVM.DownloadCompleted += (senderVM, argsVM) =>
{
if (argsVM.Error != null)
{
MessageBox.Show(argsVM.Error.Message);
}
else
{
this.RootVisual = new MainPage();
}
};
catalogVM.DownloadAsync();
CompositionHost.Initialize(catalog);
CompositionInitializer.SatisfyImports(this);
}
We can now build and run our application. The application is composed on-demand: the ViewModels are not included as a reference, they are loaded when needed. This results in a smaller XAP (faster loading times), composability, and an assembly that can easily be reused. We've now got a technique for code reuse across clients: instead of having to rewrite your ViewModel code, you can simply reuse the existing code.
Note: as we're loading assemblies on-demand, you might want to ensure the correct version is loaded. By default, when you build a project in debug-mode, the resulting assembly is put in the projects' bin/debug-folder (wherefrom it can't be loaded on demand by the Silverlight application, as you won't have rights to access that folder). An easy way to ensure the resulting assembly is always available is by changing its output path to the ClientBin folder of the WebClient.
Conclusion
We've seen a few different techniques to separate out parts of our application: referencing & loading on demand through MEF. This allows us to reuse code across different clients.
Then again, we're not quite there yet. In fact, we're just getting started with this: can we do better?
What's coming up?
You might notice I've carefully avoided a few pretty obvious issues in this article: why are we not loading the Views on demand, but keeping them as a reference instead? And what about those other assemblies, like the model assembly? As we're modularizing the application, it would make sense to only load the model assembly when the ViewModels using that model are necessary, no?
Of course, those are valid concerns, and this is exactly what we'll tackle in the next part of this series. It requires quite a bit of custom coding: we'll need some extensions to the Silverlight Navigation framework (as it's not as straightforward as we would like to navigate to views in assemblies that are loaded on-demand), and we'll need a component loader component (to ensure we can load all necessary assemblies at once, instead of one by one). After that part, we'll end up with a truly composable app, separated out in modules/assemblies that can easily be reused across different clients.
Up until then: happy coding!
About the author
Kevin Dockx lives in Belgium and works at RealDolmen, one of Belgium's biggest ICT companies, where he is a technical specialist/project leader on .NET web applications, mainly Silverlight, and a solution manager for Rich Applications (Silverlight, Windows Phone 7 Series, WPF, Surface, HTML5). His main focus lies on all things Silverlight, but he still keeps an eye on the new developments concerning other products from the Microsoft .NET (Web) Stack. As a Silverlight enthusiast, he's a regular speaker on various national and international events, like Microsoft DevDays in The Netherlands, Microsoft Techdays in Belgium & Portugal, NDC2011, ... Next to that, he also authored a best-selling Silverlight book, Packt Publishing's Silverlight 4 Data and Services Cookbook, together with Gill Cleeren. His blog, which contains various tidbits on Silverlight, .NET, and the occasional rambling, can be found at http://blog.kevindockx.com/, and you can contact him on Twitter via @KevinDockx.