Sunday, January 4, 2009

Adventures while building a Silverlight Enterprise application part #3

So here we are again. Last time we looked at organizing the project and how it is important to address specifics in the teams culture. I promised some code for this episode, so a more technical subject is on the table today.

In this episode we will have a look at the intricacies of using multiple XAP files for our application. As you may remember from the first post, we expect this technique to help us reach some of the requirement goals we have, but we also identified some potential pitfalls with it.

As we implemented the basics of this we obviously ran into some issues that need solving. Below follow some topics to think about.

Silverlight will handle only one connection at any time
This may not seem obvious at first. If you haven't loaded a XAP file at runtime, you won't know this, so I figured I would give this some explanation. Whenever you load a XAP file you use the WebClient class to get the XAP file from the webserver. As this is done asynchronously, as any other communication, any other code you may invoke will simply execute will the XAP file is being downloaded. However, Silverlight will only allow you to have one connection at any time, so downloading multiple XAP files, or invoking any service calls for that mather, will throw an exception (as documented here).

As we load multiple XAP files at startup, one XAP file has to be downloaded after another. There are a couple of ways to solve this and it realy depends on the application design and requirements, but one thing is for sure: you don't want to bload this.

We chose to have an approach where one load completion leads to another load. In other words, one XAP files load is triggered by another XAP files completion event. We can do this, because one class, and one class only, is responsible for loading any XAP files and we were able to determine an order to load things in that will always work.

Another aproach you may want to consider if the above don't apply to your application (e.g. you can't determine a fixed order to load modules) is to have a queue for loading modules, where the completion of loading a module from the queue can trigger the loading of the next module, etc..

Loading UIElements from XAP files
But getting the XAP file is obviously only the first step in the process. The next step is to actually load the assemblies inside and, in our case at least, get a UIElement from one of these assemblies. To achieve this, we wrote a XapHelper class, that contains a static method to assist in the loading. The code looks like this:

public class XapHelper
{
private const string AppManifestFileName = "AppManifest.xaml";
private const string AssemblySourceAttributeName = "Source";
private const string ApplicationBinaryContentTypeName = "application/binary";

internal static UIElement LoadUIElementFromXap(Stream xapStream, string assemblyName, string className)
{
string applicationManifest = ReadApplicationManifest(xapStream);

XElement deploymentElement = XDocument.Parse(applicationManifest).Root;
IEnumerable<XElement> deploymentParts = from assemblyParts in deploymentElement.Elements().Elements()
select assemblyParts;

Assembly assembly = LoadAssemblies(xapStream, assemblyName, deploymentParts);

UIElement element = assembly.CreateInstance(className) as UIElement;
return element;
}

private static Assembly LoadAssemblies(Stream xapStream, string assemblyName, IEnumerable<XElement> deploymentParts)
{
Assembly assembly = null;
foreach (XElement assemblyPartElement in deploymentParts)
{
string name = assemblyPartElement.Attribute(AssemblySourceAttributeName).Value;

AssemblyPart assemblyPart = new AssemblyPart();

StreamResourceInfo assemblyResourceInfo = new StreamResourceInfo(xapStream, ApplicationBinaryContentTypeName);
Uri assemblyUri = new Uri(name, UriKind.Relative);
StreamResourceInfo streamInfo = Application.GetResourceStream(assemblyResourceInfo, assemblyUri);

if (name == assemblyName)
{
assembly = assemblyPart.Load(streamInfo.Stream);
}
else
{
assemblyPart.Load(streamInfo.Stream);
}
}
return assembly;
}

private static string ReadApplicationManifest(Stream xapStream)
{
StreamResourceInfo manifestResourceInfo = new StreamResourceInfo(xapStream, null);

Uri appManifestUri = new Uri(AppManifestFileName, UriKind.Relative);
StreamResourceInfo applicationResourceStream = Application.GetResourceStream(manifestResourceInfo, appManifestUri);
Stream resourceStream = applicationResourceStream.Stream;
StreamReader streamReader = new StreamReader(resourceStream);
string applicationManifest = streamReader.ReadToEnd();

return applicationManifest;
}
}

As you can see, the main method here is LoadUIElementFromXap and you have to provide it with a stream that contains the XAP file. This stream would be the result of loading it trough the WebClient class. The second parameter is the name of the assembly to load the UIElement from and the third paremeter is the classname for the UIElement.

Also you may have noticed that the code is a little more verbose than some of the samples online. This is done to improve readability and maintainability of the code.

Identify XAP file split-ups
Another obvious question you will run into when using this aproach is, how will I split up my XAP files?

One way to look at it is to have a good look at your general screen design. What parts are always, or at least usually, the same? What parts change a lot?

Another important factor is size. After all, it all started out because of performance. So ask yourself, what parts of the interface are likely to become large and are not all needed at the same time?

Whatever rules you apply to this, one fact is very important. You should identify responsibilities that you need to host in the XAP files. What I mean by that is, you need to make sure you don't split up code that handles a single responsibility, breaking your OO design. Posible subjects for this are:

- Security
- Loading XAP files
- Data connections (altough this one is not likely)
- User settings

And obviously there could be many more, depending on the application your building.

In our case, loading XAP files and security were assigned to the outer XAP file as a responsibility. As we choose to have some navigation assigned to another XAP file, that resulted in loading the first, we created an interface, called ILoadModules, that identifies UserControls that want to load XAP files and supply the main XAP file with a delegate that it can set, to provide a method of doing so.

As I promised dsoltesz earlier I've uploaded a sample project.


I hope you liked the more technical side of this artice and I'm happy to have actually squezed in some code ;-). Please let me know if you have any questions, comments, etc..

7 comments:

  1. A sample project would be extremely helpful that demonstrates whats being discussed in each of the "Adventures while building a Silverlight Enterprise application" articles". How your setting up your projects. How your loading your xap files etc.

    ReplyDelete
  2. Thanks dsoltesz, for your comment. As this code is all part of a product that will be commercially available, you probably understand I can't actually post the code I wrote that got me to write the articles.
    As for setting up your projects, I can know that this can be a challange at times. I'm thinking about actually doing a post about this (and yes, I will also look at a sample project :-)).
    I figured that loading a XAP file dynamically was already pretty well documented on silverlight.net, so I didn't bother, but I will put up a small sample project for you on that one.

    ReplyDelete
  3. Yeah, I was more interested on how you guys did your common security stuff to share between your different silverlight modules and navigation betweeen the different pages and modules.

    ReplyDelete
  4. Ok, we only have one page and one central UserControl. All the modules are UserControls loaded by this central control.

    For our security we use a token, that is a result of the user being authenticated with our security service. Whenever a call is made to some service, the token is send with it, so the service can then authenticate the service call.

    To pass this token to any of the loaded modules, these modules have to implement an interface, which defines a property to allow setting a token for a module.

    A sample of passing information between modules is actually in the sample project i've posted above. I hope this clears things up, but if you have any questions, again, I'm happy to help.

    ReplyDelete
  5. Hi
    This article is really useful. I am not able to see the sample project that I can download? Has it been removed? If so, could you please post it again. I am kind of new to silverlight and have been researching ways to effectively build a large enterprise application we are going to start soon.

    ReplyDelete
  6. Hi tsr,

    Good to hear the article is helpful to you. The
    sample project is there. Just click where it says MultipleXapFiles.zip. It will open a Windows Live page where on the left side it has a link saying Download. If you click that the zip file will download.

    Greets,
    Jonathan

    ReplyDelete