In this entry, I would be talking about the issues that I have encountered when developing a Silverlight application structured with PRISM principles and that is driven by a WCF service. Some of the issues that I mention here are applicable even when making just Silverlight applications (like Data Binding Hello World!).
Gotcha 1 : Working with Data Binding
Shown below is the code snippet for startup xaml page – XAML code on the top and code behind at the bottom half.
If you observe the XAML, it simply contains a textblock and a textbox which both binds to the same property called “Debug”.
So in the codebehind, I created a property called “Debug” and in the constructor for the page (my page is called Shell), I have set the DataContext to itself. So the Binding Source should be the DataContext of the UserControl which points to itself (the instance of Shell). So Debug property should be taken from the property listed in the Shell class.
While this setup works well in a WPF application, running in Silverlight terminates the application. I have noticed that when “this.DataContext = this” is placed in a Silverlight page, the application would terminate with an exception [AG_E_PARSER_BAD_PROPERTY_VALUE(Line: 8 Position: 42)].
Apparently, this.DataContext = this is not being liked by the Silverlight engine. You can comment the XAML inside StackPanel and then try running the application again. You would still notice that the application fails to execute.
So, the lesson learnt here is “unlike in WPF, Silverlight does not like DataContext of a UserControl to be set to itself”.
So how am I going to make it work?
Shown below is the fixed version. We have a ShellViewModel which is instantiated within the constructor and then the data context is set to this instance.
Gotcha 2: Missing Event Handler Methods can terminate your application
Sometimes it so happens that you specify an EventHandler method in XAML but forget to implement the method in code. In that case, the compiler does not throw an error and instead a runtime exception would terminate the application. If you would like to experiment, remove the empty “private void Text….” method inside Shell class and run the application.
Gotcha 3 : Data Binding makes no sense without INotifyPropertyChanged.
If you look at the last code snippet, in the XAML section, both TextBlock and TextBox bind to the same “Debug” property. Now in the TextBlock LeftMouseButtonUp event handler, lets add a line which changes the value of the “Debug” property, like shown below.
Now, if you run the application, and left click on the TextBlock, the event handler would be executed, the value of Debug would be updated but the UI would still show the same “Click Me!” (the default value for Debug) since it has no idea that the Debug property has been modified.
To fix this and to make the UI thread aware of any changes made to the properties it binds to, the properties should either be made as Depdendency Properties or the Data Context should implement INotifyPropertyChanged and the setter of the properties should raise the PropertyChanged event.
Using INotifyPropertyChanged
Shown below is the modified ShellViewModel which implements INofityPropertyChanged.
Using INotifyPropertyChanged is probably a better way to do things and in fact much simpler to use. In every property, the setter should raise the PropertyChanged event. Thats it!
Now the application works as expected. When you click on the textblock, both the TextBlock and TextBox changes.
Gotcha 4 : Be aware of Data Binding Default Mode.
If you come from a WPF background, like me, then the same set up (XAML + code as shown until now) would behave differently in Silverlight. In WPF, if you change the text inside the textbox and tab out, you would notice the text in the text block change as well. But this does not happen in case of Silverlight. Proof ?? Try it or believe what is shown in the picture below.
Notice that the text block still shows the old text in spite of the text changed in the textbox (which also binds to the same property as the text block). The reason for this not to work is that in WPF the default Binding Mode is TwoWay, while in Silverlight its OneWay. For those who do not know, TwoWay means changes in the source (data) would also update the target (UI) while OneWay only updates the source when the target is changed by the user (at least that is what I understand they mean).
What’s the fix?
Silverlight Dependency Properties
Look at this article : http://blogs.sqlxml.org/bryantlikes/archive/2008/12/15/silverlight-dependency-properties.aspx
Now that I have talked about some fundamental issues one might face when starting Silverlight development, I thought I would dig more into gotchas encountered when working with Composite Silverlight applications driven by WCF services.
Gotcha 5: Composite Silverlight Applications – Bootstrapper, ModuleCatalog using XAML
Assumptions : I assume you have downloaded the Composite WPF/Silverlight (PRISM) and built the CAL. Shown below are the Silverlight libraries that I have on my machine. I also assume you have basic understanding of what a composite application is, what silverlight module is, etc.
In the Silverlight project, add reference to the above libraries. Then the first step is to create a Bootstrapper. The bootstrapper performs all the required initialization and configuration for the application. Shown below is my Bootstrapper that I have used in one of my silverlight prism applications.
The bootstrapper 1) creates the shell, 2) tells how your modules are cataloged and 3) additionally, it adds new RegionAdapters to existing ones.
The fun part here is the ModulesCatalog.xaml. This XAML file is used to configure my modules and the contents would be shown in a while. The package uri used is always annoying to me, so I use this URI as a reference and it works. You are free to use this as a reference. Anyway, lets look at the modules catalog.
Even though each XAP file has only one module, I noticed that for the Module configuration to work properly for both WhenAvailable/OnDemand, ModuleInfoGroup has to be used.
Before we look into how to make each module as a separate XAP file, lets look at how the bootstrapper has been used. The App.xaml.cs has to be modified in the Application_Start method to reflect the following.
Gotcha 6 : Preparing a Silverlight module as a XAP File
When you create a new Silverlight Library, the output of the project would be a silverlight dll which cannot be used for On Demand loading for PRISM applications. So you have to make your modules to be generated as a XAP files (which are just ZIP files). Follow the steps shown below and you would be good.
1. Add a new Silverlight application project to the solution. Stress on “Silverlight Application” not a Silverlight library.
Make sure you link the control but you uncheck “Add a test page that references the application”.
2. Delete the App.xaml file.Build the solution. You should see the .xap file added to the ClientBin along with the shell project. Shown below is my project structure after the build. (Notice ethe maya.sample.module.xap and also missing App.xaml inside the module project).
Sub-Gotcha: What if you already have a silverlight library project and you wish the build to generate a XAP file instead?
You have to right click on the silverlight library project and “Edit Project file”. This unloads the project and opens the project file inside XML editor within Visual Studio. (Or you can open it manually in editor of your choice). The first PropertyGroup section would like shown below. Notice the SilverlightApplication is set to false.
Make changes such that it looks like shown below. You have to add XapOutputs element and set it to true.
Now reload the project. Open the project folder in Windows Explorer and go to the Properties folder. Add a new file called AppManifest.xml with the contents as shown below.
1: <Deployment xmlns="http://schemas.microsoft.com/client/2007/deployment"
2: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3: >
4: <Deployment.Parts>
5: </Deployment.Parts>
6: </Deployment>
Now come back to Visual Studio and click on the “Show All files” icon in Visual Studio Solution Explorer selecting the library project.
You should see AppManifest.xml without any icon associated. Right click on the xml file and click “Include in Project”.
Now go to the properties. The Silverlight tab should look like shown below. Specify the Xap Filename as you wish. Typically, I name it same as the Assembly name.
My modified Silverlight Build options screen is shown below.
Notice that I have also set the Manifest File template to the file that we added previously.
Sub - Gotcha : This generates the XAP file. But it isn’t being copied to the ClientBin
If you build the library project with the changes mentioned above, you can see the XAP file generated in the Debug folder but it would not automagically sit inside the ClientBin for the Web application project. To make this happen automatically, you have to modify the web application project properties. Right click on the web application project that contains your ClientBin. Go to the Silverlight Applications tab. Click on Add. Select your project and uncheck the “add a test page that references the control” since we do not want that. Shown below is that “Add Silverlight Application” screen. Finally click “Add”.
Now build your solution and you should notice the XAP file being copied into the ClientBin folder.
Gotcha 6 : Configuring Modules using XAML – ModuleCatalog : Always place the modules inside ModuleInfoGroup.
Like mentioned previously, modules can be configured via an XAML file. This configuration allows one to specify if the modules be loaded as soon as they are available or when demanded. Lets assume that you have a XAP module which is to be loaded on demand and one which is to be loaded when available. Shown below is segment of my ModulesCatalog.xaml file whose complete version has been presented previously. Somehow, ModuleInfo when placed inside ModuleInfoGroup works where as just a ModuleInfo by itself always seemed not to work (may be I did something wrong).
So how to demand a module?
_moduleManager.LoadModule("PerformanceCounterModule");
Where _moduleManager refers to the current ModuleManager instance. The best way to get this is to add ModuleManager parameter to the constructor and use UnityContainer to resolve the object. For example, shown below is one of the ViewModels that I use which has an instance of ModuleManager passed to its constructor. The ViewModel is not instantiated directly but instead obtained via “container.Resolve<>()” call.
Unless implemented once, these concepts are rather difficult to understand. May be I will do a walkthrough for WPF based Twitter client very soon during which we can look at how it works out. For now, I assume you understand what I am talking about.
Gotcha 7 : WCF Service with Silverlight Applications – Deployment, Libraries, etc….
First of all, lets say you have a WCF service hosted inside the Web application. Lets say you created a Silverlight library which consumes this service, thereby you get to have a ServiceReferences.ClientConfig inside this helper client library but would not be present inside the shell application. Later when you deploy the service, it would be required that you modify the configuration files since the deployed service might have a different URI than the one at development. So to overcome this, shown below is one way that I often use. This simply obtains the service end point address and it assumes that the silverlight application and the service are both hosted within the same web application. All you need to look at is the way “_remoteAddress” is determined using Application.Current.Host.Source.
Now lets look at the application set up. Shown below is the shell project and the helper library which consumes the WCF service. Like I said before the library would be referenced and the WCF service is used instead of consuming the WCF service directly. (highlighted in the picture).
So if you try and consume the service within the shell application you would compile and build without any issues. The namespaces would be figured out nicely (or use Ctrl + . in Visual Studio to resolve the namespace). But as you execute the application, you would face the exception shown below.
The message says “Cannot find ‘ServiceReferences.ClientConfig’ in the .xap application package”. And clearly we did not place one. But if we place one in here, it might be redundant and may later cause conflict issues which is not so easy to identify (since we might forget – remember DRY).
To resolve this, on the shell application (named maya in my case),
1. Right click on the project and click “Add Existing Item”. Navigate to the library which has the actual ClientConfig file.
2. Select the file (ServiceReferences.ClientConfig) and then instead of clicking add, click the arrow next to “add” and click “Add as Link”.
3. Remember we are adding a link. One mistake that I often do is to first click “Add as Link” and then select the file followed by clicking on "Add” button. I assumed the drop down whenever clicked would change the behavior of the “Add” button but this isn’t the case. You have to first select the file and then click on “Add as Link”.
4. You can verify that its a linked copy by opening the shell application folder and you should not see any ClientConfig file. It should only be in the service layer library we added.
Gotcha 8 : Last one … Silverlight XAP File size, Performance settings.
1. Not sure if it matters much but you can actually extract the XAP file and re-zip it with a better utility like WinRar or 7-Zip and gain much smaller sized XAP files.
2. In the ASPX page that hosts the Silverlight content, you can add an attribute “MaxFrameRate” and set it to a lower value like 10. I have to be honest that I do not know if this setting would improve performance for any kind of silverlight applications or just the ones with media in it. Anyway I do it for any application I use.
I hope this post is informative enough and slightly well organized. I am not an expert in any of the technologies – composite silverlight apps, wcf or even Visual Studio. But i thought it would be a nice thing to share my observations with the community. So please be gentle if there is a mistake in my approach or my concepts and I would be glad to rectify them.
Thank you.
3 comments:
Phenomenal post! A note about gotcha 1 though, this.DataContext = this works fine with the Silverlight 3 runtime (even in a SL2 app). I've been troubleshooting why a SL2 app only works with the SL3 runtime and I think you just saved me days of debugging - I went right to that issue after reading this post!
James. I do know how some issues take all the time we have only to realize that the fix was ridiculously simple.
As on SL3, I haven't tested on the SL3 runtime yet. Anyway its not a good design to set the datacontext of a usercontrol to itself. Thanks for the update though.
Hi! It was very complicated (not because of you - just because I'm a little bit slow), but I managed to follow your explanation :) thank you very much!
Post a Comment