.. a journey getting to the contents
The last time I have been getting familiar with Silverlight. As a .NET developer with a big focus on the web I am interested in the possibilities of this plugin. (Eventhough, I also keep a close eye on the interesting stuff around HTML5.)
Image via Wikipedia
I always wanted to add a little more to web applications and sites that are maybe possible using clever Javascript, but are probably easier to achieve using a plugin like Flash. Being quite fluent in .NET and C# and reasonably agile in Javascript I didn't want to learn yet another language and different IDE. So, when Silverlight came on stage I was immediatelly interested. I could be using the familiar Visual Studio, familiar C#, familiar (though slightly limited implementation of) .NET framework to do some exciting new stuff. XAML came in as a new aspect, but that was already part of the WPF stuff, so not completely new. Expression Blend to create all sorts of horrible interfaces and well, the sky is not even the limit anymore.
After some useless experimentation (extending "Hello world!") I decided to stop right there and drop everything and make a good start right from the beginning. Fundamentals first.
A framework
Within our company we use a clever framework that implements the MVC pattern. We use the same framework for Windows Forms application and ASP.NET applications. By completely separating the View logic from the Model and Controller (as it should be done) we can use the same Model and Controller logic in both types of applications.
The framework has a Mediator and all functionality is handled through that using Commands. All View controls are registered with the Mediator and are signalled to update the view when "things" change in the Model. The controls then update themselves and show or hide bits of themselves.
At first there was a bit of getting used to this way of programming, but now it strikes me again and again how little code is needed to implement logic.
All logic is handled in commands. When a button is clicked the Mediator is called to execute a command with some optional parameters. These Commands are implemented in various classes and are decorated with a Command attribute and implement an ICommand interface. The ICommand interface makes that the methods that a Command needs are implemented. The Command also has access the Model through ApplicationData object. I made a SilverlightApplicationData type with some small changes to the WebApplicationData type. For instance in the WebApplicationData we grab the buildnumber from the Web.Config and there is no such thing in Silverlight, so it is dropped for this first implementation.
Anyway, just add the attribute and we are good to go.
[Command("COMMANDNAME")]
The code the ICommand interface has among others a Do() method that can be called by the Mediator. A typical Button Click EventHandler is really simple.
Mediator.Execute("COMMANDNAME", "string parameter");
Or ..
Mediator.Execute("COMMANDNAME", "string parameter", intParameter, someOtherClassParameter);
Every parameter after the command name is passed to the actual Command as argument for its constructor.
The Mediator then executes the Command. However, the framework doesn't know which Commands will be implemented. Neither do I. What is needed is a Dictionary with all available Commands.
Dictionary<string, Command> commands
How to can the framework know which commands there are? Do we need to maintain a list in a config file? A bit nasty really. But the backbone of .NET Framework comes to the rescue: Relfection.
Reflection
Using reflection we can iterate all application assemblies and scan all types and make a Dictionary with all types that have the Command attribute. These can then be stored in the Dictionary.
Sounds easy, but is it?
Windows and web is simple
For Windows and Web applications iterating the assemblies is quite simple. We just look at the content of the Bin folder and load all *.dll (and *.exe) files. Then using reflection we scan all Types for the Command attribute. When found we add them to the Dictionary.
foreach (string file in Directory.GetFiles( path, searchPattern, SearchOption.AllDirectories ))
{
Assembly assembly = Assembly.LoadFrom( file );
Type[] types = assembly.GetTypes();
foreach (Type type in types)
{
object[] attr = type.GetCustomAttributes(typeof(CommandAttribute), false);
if (attr.Length == 1)
{
CommandAttribute attribute = (CommandAttribute)attr[0];
Command command = (Command)assembly.CreateInstance( type.FullName );
commands.Add( attribute.CommandName, command );
}
}
}
This is all done in a static CommandManager so we only need to scan the files once.
Silverlight is slightly different
With Silverlight the situation is a bit more complicated. There is no Bin folder that can be scanned. There is a ClientBin folder, but that's on the web server and even then the files are inside the Xap file!
Inside that renamed Zip file we find the assemblies and AppManifest.xaml file in which these files are listed. Well how do we get there?
Doing quite a bit of Googling I finally got on the right track. The above code sample needs quite a bit of rework. Whe need to do a number things:
- Open the Xap file
- Read the AppManifest.xaml.
- Parse the names of the assemblies
- Load the assemblies from the Xap file
Eventhough the Silverlight application is downloaded by the browser to the client computer we can not really get at the Xap file directly. That's the sandboxing security model working against us. So we need a clever workaround.
Luckily there is a way to get there. We can download the file to Local Storage. Since we are on the web this happens asynchronously. Using the OpenReadCompletedEventHandler we can continue the process.
Note that we also check for the existence of the Xap file in LocalStorage, just to prevent repeated downloads when you refresh the web page.
Uri xapUri = Application.Current.Host.Source;
string[] parts = xapUri.AbsolutePath.Split('/');
xapFile = parts[parts.Length - 1]; // the filename is the last part of the path
using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
{
// check if the file is available
if (!store.FileExists(xapFile))
{
// when not available download it
WebClient webClient = new WebClient();
webClient.OpenReadCompleted += new OpenReadCompletedEventHandler(WebClientOpenReadCompleted);
webClient.OpenReadAsync(new Uri(xapFile, UriKind.Relative));
}
else
{
LoadAssembliesFromIsolatedStorage();
}
}
When WebClient has completed download the stream is copied to IsolatedStorage
private static void WebClientOpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
using (IsolatedStorageFile storageFile = IsolatedStorageFile.GetUserStoreForApplication())
{
IsolatedStorageFileStream fileStream = storageFile.CreateFile(xapFile);
WriteStream(e.Result, fileStream);
fileStream.Close();
}
LoadAssembliesFromIsolatedStorage();
}
Now the assembly processing can begin.
private static void LoadAssembliesFromIsolatedStorage()
{
using (IsolatedStorageFile storageFile = IsolatedStorageFile.GetUserStoreForApplication())
{
IsolatedStorageFileStream fileStream = storageFile.OpenFile(xapFile, FileMode.Open, FileAccess.Read);
// parse the AppManifest.xaml file
Stream manifestStream = Application.GetResourceStream(new StreamResourceInfo(fileStream, "application/binary"), new Uri("AppManifest.xaml", UriKind.Relative)).Stream;
string appManifest = new StreamReader(manifestStream).ReadToEnd();
XElement deploymentRoot = XDocument.Parse(appManifest).Root;
List deploymentParts = (from assemblyParts in deploymentRoot.Elements().Elements()
select assemblyParts).ToList();
// now load assemblies one by one
foreach (XElement xElement in deploymentParts)
{
string source = xElement.Attribute("Source").Value;
AssemblyPart asmPart = new AssemblyPart();
fileStream = storageFile.OpenFile(xapFile, FileMode.Open, FileAccess.Read);
StreamResourceInfo streamInfo = Application.GetResourceStream(new StreamResourceInfo(fileStream, "application/binary"), new Uri(source, UriKind.Relative));
Assembly assembly = asmPart.Load(streamInfo.Stream);
// from here the code is unchanged
Type[] types = assembly.GetTypes();
foreach (Type type in types)
{
object[] attr = type.GetCustomAttributes(typeof(CommandAttribute), false);
if (attr.Length == 1)
{
CommandAttribute attribute = (CommandAttribute)attr[0];
Command command = (Command)assembly.CreateInstance(type.FullName);
commands.Add(attribute.CommandName, command);
}
}
}
}
}
Well, it took quite some googling around and advice from a friend to get this working, but in the end the steps are quite easy to understand.
Almost there ..
So now we have everything we need. Almost that is.
As I mentioned earlier Silverlight has a slightly limited implementation of .NET framework in the sense that not all namespaces are completely implemented in the same way. And that threw up another hurdle to take.
To create an instance of Command we use an overload of the CreateInstance method in which we can pass the parameters for the command in the args parameter.
ICommand command = (ICommand)assembly.CreateInstance( commandType.FullName, false, BindingFlags.Default, null, args, CultureInfo.InvariantCulture, null );
Unfortunately Silverlight does not have this overload. We cannot pass any parameters to the instance using CreateInstance. In effect only the default constructor is called. Why the overload is omitted in Silverlight strikes me as odd.
Yet, more research was needed to solve this problem. More Reflection was needed to find the correct constructor for the type.
Type[] parameterTypes = new Type[args.Length];
// Initialize a ParameterModifier with the number of parameterTypes.
ParameterModifier parameterModifiers = new ParameterModifier(args.Length);
for (int i = 0; i < args.Length; i++)
{
parameterTypes[i] = args[i].GetType();
parameterModifiers[i] = true;
}
// The ParameterModifier must be passed as the single element of an array.
ParameterModifier[] paramMods = { parameterModifiers };
// Get the constructor that takes an integer as a parameter.
ConstructorInfo constructorInfo = commandType.GetConstructor(BindingFlags.Instance | BindingFlags.Public,
Type.DefaultBinder, parameterTypes, paramMods);
ICommand command = (ICommand)constructorInfo.Invoke(args);
This made it all work.
Mediator.Execute("COMMANDNAME", "string parameter");
The Mediator can now make a deep copy of the Command referenced by the name "COMMANDNAME" (using the constructor that takes string parameter) and then call that Command's Do() method to actually execute it.
A really interesting journey it was and now I can start to make something useful using this framework.