.NET Reflector is a one of the 10 Must-Have utilities for .NET developers. If you need to work with more than one version of the .NET Framework, especially with Silverlight and the Core Framework (3.5 or 4.0) you will find you reconfiguring your Reflector every time you changing the platform. For me this is very annoying tasks for which I was looking for a solution.

 

Different config files

.NET Reflector stores it settings into a config structure which includes all assemblies from the left tree in the application.

Recently I noticed the ability to start .NET Reflector with command line arguments. One command line argument allows to specify a config file path:
"C:\Program Files\Reflector\Reflector.exe" /configuration:”Reflector.cfg”

Unfortunately there seems to be a bug in version 6.1.0.11. When closing Reflector it reports the following error:

clip_image001

When changing the config-path to an absolute syntax then it works fine:
"C:\Program Files\Reflector\Reflector.exe" /configuration:”C:\Program Files\Reflector\Reflector.cfg”

Creating different profiles

First create different .NET Reflector shortcuts for each profile you want to have. In my case these are Silverlight, .NET 3.5 and .NET 4.0.

clip_image002

Edit every shortcut and add the /configuration argument to every shortcut with a different name as config file. The config file must not exists at this time.

My shortcuts are as following:

  • "C:\Program Files\Reflector\Reflector.exe" /configuration:"C:\Program Files\Reflector\DOTNET40.cfg"
  • "C:\Program Files\Reflector\Reflector.exe" /configuration:"C:\Program Files\Reflector\DOTNET35.cfg"
  • "C:\Program Files\Reflector\Reflector.exe" /configuration:"C:\Program Files\Reflector\Silverlight.cfg"

clip_image003

Now start each profile with its shortcut. .NET Reflector will not find a config and asks you which default assembly list you want to use. Select the version matching to your shortcut and repeat this for every shortcut you have created.

clip_image004

 

Existing Add-Ins

.NET Reflector has a clear and simple config file structure. Therefore you can easily copy and paste your favorite Add-Ins from one config file to another config file.

image

 

Improving startup

Working with Vista/Windows 7 makes it very easy to find programs via the start menu. To use the program search, just put a copy of your shortcuts into your start menu folder (or a subfolder in it).

C:\ProgramData\Microsoft\Windows\Start Menu\Programs\MyStartMenuLinks

image

Now you can type in “Reflector” into your start menu search box and you get a selection of different .NET Reflector profiles.

clip_image007

For me these profiles are very helpful during my daily coding life.

Posted by: beatkiener | August 10, 2010

VS 2010 Unit Test Problem with DataSet and Remoting

We ran into problems when using Visual Studio 2010 Unit Tests targeting Framework 3.5.

We use Visual Studio 2010 for our development, but we still use .NET Framework 3.5, because our customers a not ready to run .NET 4.0 applications.

Problem description in detail

When trying to transfer a DataSet instance over .NET Remoting and the executing client is a unit test environment, then it fails with an exception: “AssemblyResolveException…System.Data 4.0.0.0 not found…”

.NET Remoting uses the XmlSerializer to serialize and deserialize DataSets over Remoting boundaries.
The XmlSerializer itself generates a dynamic in-memory assembly for each root-type in the object graph it serializes. This assembly is used to serialize or deserialize the object graph. That means initially the XmlSerializer creates a dynamic assembly with a reference to System.Data 4.0.0.0, because Visual Studio 2010 executes its Unit Tests with the runtime 4.0.

On server side .NET Remoting is trying to deserialize the DataSet of Version 4.0.0.0 (because of the System.Data 4.0.0.0 reference on client side). The deserialization process fails, because the server is running with Framework version 3.5.
Unfortunately VS2010 does not allow setting another Framework version for unit test projects, only version 4.0 is supported (http://connect.microsoft.com/VisualStudio/feedback/details/483939/unable-to-change-target-framework-version-on-unit-test-projects).

Possible solutions so far

  1. Migrate client and server to .NET 4.0
  2. Run unit tests with VS 2008
  3. Implement the AssemblyResolve event on server side and redirect to the System.Data 3.5.0.0
  4. Add an assemblyBinding element to the server’s config file to redirect System.Data 4.0.0.0 to System.Data 3.5.0.0
  5. Waiting for a new Visual Studio Version which supports different target frameworks for unit test projects.

Our solution

We use the assemblyBinding element to redirect the System.Data 4.0.0.0 assembly request to System.Data 3.5.0.0. If we put the following config section into our application server app.config then we are able to use VS2010 for our Unit Tests targeting Framework 3.5.

<runtime>
   <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
        <dependentAssembly>
          <assemblyIdentity name="System.Data" publicKeyToken="b77a5c561934e089"/>
          <bindingRedirect oldVersion="4.0.0.0" newVersion="2.0.0.0"/>
        </dependentAssembly>
   </assemblyBinding>
</runtime>  

 

Resources

Posted by: beatkiener | May 4, 2010

Optimize Data Contracts for better WCF Performance

Out of the box Silverlight provides two message encoding types for sending SOAP messages over the wire. These are TextEncoding and BinaryEncoding. MTOM encoding is still not available within Silverlight.

Binary encoded messages produce a payload that is about half the size of a message encoded with standard text encoding. Please note that binary encoding is a Microsoft proprietary implementation and therefore not interoperable outside the .NET framework.

In our Silverlight applications we use a .NET to .NET messaging scenario (like many other SL apps too), which means Silverlight communicates with a WCF backend. Using then binary encoded messages is obvious.
You will find a lot of blog posts out there in the community when you are interested in binary vs. text-encoding comparison.

What exactly does BinaryEncoding?

When the encoding is set to binary, then the DataContractSerializer still produces SOAP messages as XML but after that the messages get transformed into a binary representation of the XML with help of the XmlDictionaryWriter class.

The following code snippet shows the usage of the XmlDictionaryWriter class (not WCF related).

image

You should not compare the XmlDictionaryWriter with the well-known BinaryFormatter in the mscorlib. The BinaryFormatter produces a binary representation of an object graph. The XmlDictionaryWriter on the other hand translates a textual XML into a binary representation. Serializing a XML string with the BinaryFormatter will not reduce the size, because the XML is just a single string from this point of view.

The textual output form the example above indicates that it is a kind of an optimized XML structure.

image

Optimize Data Contracts

Binary XML still includes the names of the elements and attributes as plain text.

When setting the name of my data contracts to a single character then the SOAP message size will be reduced by additional 30-50 percent (depends on the data structure).

image

 

The SOAP-Envelop then look like this:

image

Rules for setting the name:

  • Each data contract must have a unique name in the scope of its namespace
  • Each data member must have a unique name in the scope of its class.
  • Use character [A-Za-z], you can mix upper and lower case.

After these changes the generated proxy on client side is quite unhandy, because the classes and members are named like they are declared in the contracts. Therefore I created a shared lib and reuse the data contracts on client side.

image

Assemblies built in Silverlight are in general not binary compatible with the .NET Framework, so if you want to share code you need to dual-compile your code. Since Silverlight 4 you will be able to use some Silverlight-based assemblies from within .NET 4. In order to load a Silverlight assembly in .NET, the assembly may only reference the following assemblies: mscorlib.dll, System, System.Core.dll, System.ComponentModel.Composition.dll, Microsoft.VisualBasic.dll.

Well, my shared data contracts dll needs a reference to System.Runtime.Serialization.dll which is currently not binary compatible with the Silverlight runtime.

Therefore I do a dual-compile by adding the same source file with “Add As Link” to my Silverlight library.

clip_image008

As final task I add a reference to SharedDataContract.SL assembly and update my service reference. Please check that the option “Reuse types in referenced assemblies” is checked.

clip_image009

 

Comparison of different configurations

Text encoding / standard contract

image 

 

Text encoding / optimized contracts

image 

 

Binary encoding / standard contracts

image

 

Binary encoding / optimized contracts

image

Summary

When communicating in a .NET to .NET messaging scenario then optimizing the data contracts is an additional way to reduce the SOAP message size. This together with binary encoding will reduce the message size about 4 times. Especially looking to the Windows Phone 7 development this will help when the traffic goes over a low bandwidth network.

 

Demo Project

Download it here

Posted by: beatkiener | April 21, 2010

Opening a Microsoft Office documents with Silverlight

Situation

From our Silverlight application we must be able to view documents. Documents are stored in an existing document management system and are accessible through a WCF REST service. It sounds very easy, but it was quite hard to understand all the stuff going on behind the Internet Explorer and the Microsoft Office.

Simplest solution

Download the content via a WebClient instance and save it with help of the Silverlight SaveFileDialog to the users local disk. Unfortunately this simple solution provides not the expected user experience, because Silverlight is not able to set the file name for the SaveFileDialog and after saving the user must manually navigate to the local folder and open the document by hand. The user experience should like be “Click & View” and not “Click, Safe, Search, Open & View”.

Expected solution

Open a popup from the Silverlight application with the URL pointing to the document.

Via HtmlPage:

Uri docUri = new Uri("http://mydomain/docs/test.docx");
HtmlPage.PopupWindow(docUri, "windwow1", null);

Or via a HyperlinkButton:

<HyperlinkButton Content="open document"
                NavigateUri="http://mydomain/docs/test.docx"
                TargetName="_blank" />

The service part

We deliver documents through a simple WCF REST service from our middle layer infrastructure. We use a custom authentication system that relies on HTTP session cookies for client identification. When the user isn’t authenticated then the service will redirect the request to the logon page (status code 302). The REST service is a self-hosted windows service (existing middle layer). The service is available with the URI pattern: http://<myhost>/docs/<docname>

As example: http://mydomain/docs/test.docx

Here the simplified version of our REST service.

public Stream GetContent(string docId)
{

    WebOperationContext context = WebOperationContext.Current;

    // read session cookie
    string cookies = context.IncomingRequest.Headers[HttpRequestHeader.Cookie];
    string sessionId = "";
    if (cookies != null)
    {
        // parse cookie values
        var cookieItems = from c in cookies.Split(';')
                            let cc = c.Split('=')
                            where cc.Length == 2
                            select new { Name = cc[0].Trim().ToLower(), Value = cc[1].Trim() };

        var items = cookieItems.ToDictionary(c => c.Name);

        if (items.ContainsKey("sessionid"))
            sessionId = items["sessionid"].Value;
    }

    // check session is valid
    bool sessionValid = false;
    sessionValid = sessionId == "1234"; // todo

    if (!sessionValid)
    {
        // redirect uri
        string redirectUri = "http://mydomain/logon.html";
        // ...set the location the logon site should naviagate to after the successful logon
        redirectUri += "?redirect=" + context.IncomingRequest.UriTemplateMatch.RequestUri.ToString();
        // redirect to the logon page 
        context.OutgoingResponse.Location = redirectUri;
        context.OutgoingResponse.StatusCode = HttpStatusCode.Redirect;
        // response has header only
        return null;
    }
    else
    {

        try
        {

            // check if document exists 
            bool docExists = true; // todo

            if (docExists)
            {
                // todo
                FileInfo requestedFile = new FileInfo("todo");

                // set mime type (firefox, chrome and safari requires them)
                context.OutgoingResponse.ContentType = GetMimeType(requestedFile.Extension);
                return new FileStream(requestedFile.FullName, FileMode.Open);
            }
            else
            {
                // doc not found
                context.OutgoingResponse.StatusCode = HttpStatusCode.NotFound;
                context.OutgoingResponse.StatusDescription = "Document not found";
                // response has header only
                return null;
            }

        }
        catch (Exception ex)
        {
            // general error
            context.OutgoingResponse.StatusCode = HttpStatusCode.InternalServerError;
            // response has header only
            return null;
        }
    }
}

At this point everything works fine with Firefox, Google Chrome and Safari on Mac.

Internet Explorer is different

Internet Explorer acts a little bit different as expected. When opening an office document (Word, Excel, PowerPoint) from a web page in Internet Explorer the Fiddler call stack lock like this:

winword:  HEAD    http://mydomain/docs/test.docx HTTP/1.1     405 NotAllowed
winword:  OPTIONS http://mydomain/docs/ HTTP/1.1              405 NotAllowed
winword:  GET     http://mydomain/docs/test.docx HTTP/1.1     302 Redirect

As you can see the iexplore process isn’t involved. What’s going on? IE detects the mime type from the file name extension, if it is preserved in the URL string. Here some examples:

 
http://mydomain/docs/test.docx IE detects mime type as docx
http://mydomain/service.svc/test.docx IE cannot detect mime type (it isn’t svc)
http://mydomain/resource.ashx?file=test.docx IE cannot detect mime type (it isn’t ash)

What happened with iexplore? Office 2007 and 2010 Beta are designed to make a more collaborative workspace. Therefore, several changes have been made to how Office works with web content. These changes provide better authoring features for the following Web servers that support Office:

  • Microsoft Windows SharePoint Services
  • Microsoft SharePoint Portal Server
  • Microsoft Exchange Web Store

IE detects if the web resource is an Office format by analyzing the URL. If so IE will start the corresponding Office program with the URL as process start parameter like the following:

“C:\Program Files\Microsoft Office\Office14\WINWORD.EXE” /n http://mydomain/docs/test.docx”

Office is now downloading the content from the web:

image

The request will end with the status code 302 and a redirect location to our logon page, because session cookies are not shared between Internet Explorer and winword process. Well, Office Word will follow this redirection and tries to display our Silverlight logon page. This isn’t possible because the SL plug-in isn’t available for the Office suite and Word shows the following content.

image 

When we change our service so that it doesn’t make a redirection and returning the status code 404 (Not Found), then word is telling me that it wasn’t able to open the doc.

image

First improvement

Changing the service to get another URL pattern for our documents so that IE cannot detect the mime type through the URL.

Something like this.

  • http://mydomain/docs/id=test.docx
  • http://mydomain/docs.svc/test.docx

What’s happening now. Let’s look at Fiddler’s call stack.

iexplore: GET     http://mydomain/docs/test.docx HTTP/1.1     200 OK
winword:  HEAD    http://mydomain/docs/test.docx HTTP/1.1     405 NotAllowed
winword:  OPTIONS http://mydomain/docs/ HTTP/1.1              405 NotAllowed
winword:  GET     http://mydomain/docs/test.docx HTTP/1.1     302 Redirect

Now IE is downloading the content as expected. But then winword comes into the play again. What’s going on in this situation?

IE cannot detect the mime type from the URL so it will download the web content as any other browser. With the request to our service the browser is sending our session cookie to the server too. Based on the mime type from the response IE decides to open Microsoft Word. IE does that the same way as before: starting the winword process with a startup argument including the URL instead of the downloaded local file.

“C:\Program Files\Microsoft Office\Office14\WINWORD.EXE” /n http://mydomain/docs/test.docx”

Same situation, Office looks like this again:

image 

Why the Office suite is doing that? Because Office lets you edit and author documents on a Web site if the server supports Web authoring and collaboration (Sharepoint, Exchange, etc). First, Office tries to communicate with the Web server with a series of HEAD and OPTIONS requests to discover the possibilities of the webserver (“Microsoft Office Protocol Discovery”, “Microsoft Office Existence Discovery” and “Microsoft Office Core Storage Infrastructure”). Then Office tries to directly bind to the resource with a GET request to the web resource.

Second improvement

Our WCF REST service doesn’t handle the HEAD and OPTIONS request from the “Microsoft Office Protocol Discovery” and “Microsoft Office Existence Discovery”, but it redirects the GET request from the “Microsoft Office Core Storage Infrastructure” to the logon page while winword can’t deliver the session cookie.

What we can do is to detect the caller via the User-Agent header. There are three main User-Agent’s used from the Office suite.

  • Microsoft Office Protocol Discovery
  • Microsoft Office Core Storage Infrastructure
  • Microsoft Office Existence Discovery

In case of one of these 3 user-agents our service returns the status code 401 (Unauthorized) instead of the 302 (Redirect).

Then winword ignores the 401 and opens the document from the cached document which was previously downloaded from IE.

Here the code snippet to do that:

// check user agent for office product suite
bool isOfficeSuite = false;
if (!string.IsNullOrEmpty(WebOperationContext.Current.IncomingRequest.UserAgent))
{
    string[] officeUserAgents = { "Microsoft Office Protocol Discovery",
                                    "Microsoft Office Existence Discovery",
                                    "Microsoft Office Core Storage Infrastructure" };

    string requestUserAgent = WebOperationContext.Current.IncomingRequest.UserAgent.ToLower();

    var q = from userAgent in officeUserAgents
            where requestUserAgent.ToLower().Replace(" ", "")
                               .Contains(userAgent.ToLower().Replace(" ", ""))
            select userAgent;

    isOfficeSuite = q.Count() > 0;
}

if (isOfficeSuite)
{
    // don't redirect when office program want getting the document via 
    // "Microsoft Office Protocol Discovery" or 
    // "Microsoft Office Core Storage Infrastructure" requests.
    // Excel/word whould redirect to the logon page an display the html!
    WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.Unauthorized;
}
else
{
    // redirect to the logon page
    WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.Redirect;
    // TODO
    WebOperationContext.Current.OutgoingResponse.Location = "http://rnd.glauxsoft.ch/evidencenovaweb/";
}

// response has header only
return null;

 

Unfortunately this works well with Office 2007, but first tests with Office 2010 Beta are different again. Excel and PowerPoint 2010 doesn’t ignore the 401 status code and telling us “Could not open the document …”. Maybe this is an error in the beta.

Third improvement

However, this kind of communication doesn’t make me happy. What can we do that IE downloads the content and then simply start the process associated to the mime type as that other browsers still do?

The typical workaround I found is to use the Content-Disposition attachment header in the GET response when returning the file. This header will tell the web browser to treat the file as a download (read-only), so the file will open in Office from the web browser cache location instead of a URL. With that setting, the Office application will treat the file as local, and will therefore not make calls back to the web server.

Content-disposition is an extension to the MIME protocol that instructs a MIME user agent on how it should display an attached file. When Internet Explorer receives the header, it raises a File Download dialog box whose file name box is automatically populated with the file name that is specified in the header

In our service we set this header for all well-known Office formats, because other mime types should still be opened inline within the browser such as PDF, TXT or JPG, GIF, etc.

Here the code snippet to doing that

// well-know office formats
string[] officeMimeTypes =  {  ".doc",".dot",".docx",".dotx",".docm",".dotm",
        ".xls",".xlt",".xla",".xlsx",".xltx",".xlsm",
        ".xltm",".xlam",".xlsb",".ppt",".pot",".pps",
        ".ppa",".pptx",".potx",".ppsx",".ppam",".pptm",
        ".potm",".ppsm"};

// add content-disposition header.This header will tell the web browser 
// to treat the file as a download (read-only), so the file will open 
// in Office from the web browser cache location instead of a URL. 
if (officeMimeTypes.Contains(requestedFile.Extension.ToLower()))
{
    WebOperationContext.Current.OutgoingResponse.Headers.Add("Content-disposition",
                                            "attachment;filename=" + requestedFile.Name);
}

// set mime type (firefox, chrome and safari requires them always)
WebOperationContext.Current.OutgoingResponse.ContentType = GetMimeType(requestedFile.Extension);
return new FileStream(requestedFile.FullName, FileMode.Open);

 

Summary

Internet Explorer handles Microsoft Office formats other than expected. The main reason is to make a more collaborative workspace when working with SharePoint and Exchange. This different behavior let you run into troubles when you have your own service providing the documents.

In brief you should consider the following to get around these troubles:

  • Don’t let the IE detect the mime type from the URL
  • Don’t redirect to the logon page when the User-Agent is “Microsoft Office Core Storage Infrastructure”
  • Set the Content-disposition header for all well-known Office formats when returning the file.

Resources

http://support.microsoft.com/kb/260519/en-us

http://support.microsoft.com/default.aspx?scid=kb;EN-US;899927

http://blogs.msdn.com/vsofficedeveloper/pages/Office-Existence-Discovery-Protocol.aspx

Posted by: beatkiener | March 30, 2010

evidence nova running on Windows Phone 7 Series

Panoramic applications are a part of the core Windows Phone 7 CTP experience. We already have a large web application built with Silverlight 3. The fact that Windows Phone 7 supports Silverlight as its application platform gave me the inspiration to think about a version which is running on the Windows Phone 7 as a panoramic applications. I created a small demo application to demonstrate how our platform “evidence nova” could look like when running on the Windows Phone 7.

Click here to run the showcase application (after the application is started, click into the content to navigate through the application).

NovaOnMobile7

Posted by: beatkiener | March 10, 2010

MCTS Exam .NET Framework Application Development

Today I have passed my second Microsoft Certification Exam 70-536 "Microsoft .NET Application Development" successfully. After working about 6 year with the .NET technology it was quite an easy tasks to pass this exam.

image

Posted by: beatkiener | March 2, 2010

Deep Zoom Collection Downloader

Just for fun I’ve implemented a Deep Zoom Tiles Downloader which recreates the original image from a deep zoom composition. Additionally the downloader can download the original composition from the website to your local disk. The downloader follows the principals of a pure MVVM application.

No, i have not too much time. I’m pretty well experienced with the MVVM pattern in Silverlight but not in the WPF runtime. To get more familiar with MVVM in WPF and to see the difference in a practical manner I decided to put the implementation into a ViewModel and to build a WPF-View on top of them.

The Application

In the Image Rebuilder tab, the application allows to set the deep zoom image settings or discover them automatically from a standard Deep Zoom Composer xml-file.

image

The downloader allows you to set each deep zoom composition parameter manually, because there are deep zoom compositions out there in the internet without having the standard Deep Zoom Composer XML format.

One nice example of such a collection is the “Matterhorn Deep Zoom Collection” created by Ronnie Saurenmann (Microsoft Switzerland).

image

Enter the following values to test the downloader with his collection:

  • URL Pattern: http://ronnie.blob.core.windows.net/zermatt/Tiles/560mmMatterhornSchwarzsee/{0}/{1}_{2}.jpg
  • Levels: 17
  • Tile Width: 254
  • Tile Height: 254
  • Tile Overlap: 1
  • Image Width: 55569
  • Image Height: 30064

 

In the Tiles Downloader tab, the application allows to set the root folder of the deep zoom composition (containing the folder levels 0, 1, 2, 3, 4, 5, etc), specifying the download target folder and to start the download.

image

 

ViewModel implementation

I’m using the Bitmap from the GDI to rebuild the image. A 32 bit RGB formatted bitmap uses 32 bit i.e. 4 bytes to store data for each pixel. So if you try to rebuild a large deep zoom composition with as example of 50000×50000 pixels then the memory the downloader needs to allocate dynamically for the image is very large 50000*50000*4 bytes which is roughly about 9 GB in memory. While this is not possible with GDI the downloader downloads the image in smaller portion so you might get an image portioned as following (example show image in 5 portions):

image

It should be very easy to build the final image with Photoshop or ImageMagick from the portioned tiles. Feel free to extend the downloader with a better approach while using a 3rd party image lib.

A note about the performance: the downloader rebuilds or downloads images tile-by-tile. I did not have enough time to do it parallel in several threads.

Summary

It’s quite the same to implement a ViewModel in WPF and Silverlight for such very small applications. The only difference I can see so far is the missing constraint for asynchron web request in WPF. This makes the implementation a little bit simpler. The binding expressions are identically to Silverlight, because I don’t need any special here. The command binding was easier to write because the Command property is supported from the ButtonBase natively (Silverlight will provide the same in version 4).

I know, this is a quite simple application and things get complicated when the application gets more complex.

Feedbacks are welcome.

Source code

You can download the source code here:

http://files.thekieners.com/blogcontent/2010/DeepZoomDownloader/source.zip

One final note

This downloader was implemented just for fun. Please consider copyrights when downloading deep zoom images.

Posted by: beatkiener | February 24, 2010

The Future of Business Applications

Today the Netzwoche magazine has published a Silverlight/RIA story in cooperation with Microsoft Switzerland. The title of the story is “The Future of Business Applications”. The story contains an interview which I was allowed to give (in German only).

NW10_04_Dossier_Microsoft.pdf

image

Posted by: beatkiener | February 22, 2010

How to make Visifire Chart bindable to ViewModel

The Visifire chart control is very cool and handy, but there is one missing part: it does not support data binding. The Visifire team confirmed that the current release (version 3.0.1.0) is not bindable and they are working on this issue with high priority: (http://www.visifire.com/forums/index.php?showtopic=1631).
Currently I’m working in a large Silverlight project where we use the Visifire chart and doing straight MVVM.

First attempt

In my first attempt I created a BindableDataSeries class inherited from DataSeries like the same concept I did to make the Silverlight Toolkit charts mulit-series bindable. See here: http://blog.thekieners.com/2010/02/07/databinding-multi-series-charts

I added a DataPointSource of type IEnumarable and DataPointTemplate of type DataTemplate to the BindableDataSeries class. Both properties are bindable. When the SeriesSource is set then DataSeries elements are generated based on the data template in the SeriesTemplate property in the same manner as an ItemsControl generates its items. Finally the generated DataSeries are added to the Series collection.

Now I’m able to bind the data point to my ViewModel as following:

    <vc:Chart>
        <vc:Chart.Series>
            <local:BindableDataSeries DataPointSource="{Binding SalesData}" >
                <local:VisifireBindableDataSeries.DataPointTemplate>
                    <DataTemplate>
                        <vc:DataPoint AxisXLabel="{Binding SalesName}"
                                      YValue="{Binding SalesPerformance}"/>
                    </DataTemplate>
                </local:VisifireBindableDataSeries.DataPointTemplate>
            </local:BindableDataSeries>
        </vc:Chart.Series>
    </vc:Chart>

Unfortunately there is still one problem. The DataPoint is an UIElement which is never part of the visual tree and binding gets not applied, because the binding is not executed when the element is not in the visual tree. As a result the DataPoint properties remain empty.

Second attempt

It shows to me that the only way to setup a Visifire Chart with data from a ViewModel is by writing code. How to do this without getting a strong reference between the View and the ViewModel? Remember to MVVM concepts, when creating a strong reference between View and ViewModel (either in one or both ways) you will lose many advantages the MVVM serves you.

In my second attempt I’ve created a Visifire Chart Wrapper which contains the most common properties of the Visifire Chart as bondable dependency properties.

Let me explain it step by step.

The control is inherited from System.Windows.Control and not from ContentControl, because there is no need for a composite control. The VisifireWrapper uses a static template originated from the code and not from the generic.xaml, because I don’t want to allow to re-template it. In the OnApplyTemplate override is a check implemented to guarantee this. Microsoft uses the same concept in the ViewBox control contained in the Silverlight Toolkit (https://silverlight.svn.codeplex.com/svn/Release/Silverlight3/Source/Controls.Toolkit/Viewbox/Viewbox.cs).

    /// <summary>
    /// VisifireWrapper serves as an Visifre Chart Wrapper to get the Chart MVVM bindable.
    /// You must inherit from this class an implement OnUpdateChart method.
    /// </summary>
    public abstract class VisifireWrapper : Control
    {

        public VisifireWrapper()
        {
          // Load the default template
          this.Template = DefaultTemplate = XamlReader.Load(DefaultTemplateMarkup) as ControlTemplate;
          ApplyTemplate();
        }

        /// <summary>
        /// XAML markup used to define the write-once wrapper template.
        /// </summary>
        private const string DefaultTemplateMarkup =
            "<ControlTemplate " +
            "    xmlns='http://schemas.microsoft.com/client/2007'  " +
            "    xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'  " +
            "    xmlns:local='clr-namespace:VisifireMvvmIntegrationDemo;assembly=VisifireMvvmIntegrationDemo' " +
            "    TargetType='local:VisifireWrapper' > " +
            "    <ContentPresenter  " +
            "        Content='{TemplateBinding Chart}'  " +
            "        HorizontalAlignment='Stretch'  " +
            "        VerticalAlignment='Stretch'  /> " +
            "</ControlTemplate> ";

        /// <summary>
        /// Gets or sets the default ControlTemplate of the VisifireWrapper.
        /// </summary>
        private ControlTemplate DefaultTemplate { get; set; }

        public sealed override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            // Ensure the Template property never changes from the
            // DefaultTemplate, and only apply it one time.
            if (Template != DefaultTemplate)
                throw new InvalidOperationException("The template can only be applied one time.");
        }
    }

 

The template contains a ContentPresenter which is data bound to the Chart property. The Chart property is set in the OnUpdateChart method which I’m going to introduce in a minute.

As next I added the most commonly used Visifire properties as dependency properties. Please find the full source code of the beneath dependency properties in the attached source.

/// <summary>
/// Gets or sets a collection used to generate the data point series.
/// </summary>
public IEnumerable ChartSource
{
    get { return (IEnumerable)GetValue(ChartSourceProperty); }
    set { SetValue(ChartSourceProperty, value); }
}

/// <summary>
/// Gets or sets a collection used to generate the chart multiline-title.
/// </summary>
public IEnumerable TitlesSource
{
    get { return (IEnumerable)GetValue(TitlesSourceProperty); }
    set { SetValue(TitlesSourceProperty, value); }
}

/// <summary>
/// Gets or sets a collection used to generate X-Axes
/// </summary>
public IEnumerable AxesXSource
{
    get { return (IEnumerable)GetValue(AxesXSourceProperty); }
    set { SetValue(AxesXSourceProperty, value); }
}

/// <summary>
/// Gets or sets a collection used to generate Y-Axes
/// </summary>
public IEnumerable AxesYSource
{
    get { return (IEnumerable)GetValue(AxesYSourceProperty); }
    set { SetValue(AxesYSourceProperty, value); }
} 

ChartSource will point to a collection of chart points or a collection of multi chart data. Multi chart data is meant to be used when you want to defined the numbers of lines in a line-chart dynamically. The attached demo project contains an example of this.

TitlesSource points to a collection of title information. Visifire Charts allow you to define a title with multiple lines. Each item in the collection is meant to be one line in the chart title.

AxesXSource and AxesYSource points to collections of specific axes-captions or axes-settings.

The VisifireWrapper contains abstract method OnUpdateChart. When overridden in sub class it updates the Visifire Chart wrapped by the VisifireWrapper control. The method is called in the first layout pass after one of the sources ChartSource, TitlesSource, AxesXSource or AxesYSource has changed. This is unlike the ItemsControl where the child items gets generated when the source changes. Recently I discovered that the DataGrid works exactly the same way: it generates its items (rows, etc) in the first layout pass after the ItemsSource has changed. But why I do it that way? The answer is performance, as you can read in the next section.

The charts are very nice animated during the first load (this is quite import for us, because it is part of a bigger aim to serve a new user experience to our customers. But this is another story…). Unfortunately the Visifire Chart does not allow updating DataPoints in every circumstance. Changing existing DataPoints is not a problem until you want to add or remove DataPoints. When you add DataPoints to an existing series the chart does this animated but the output is not correct as you can see in the print screen below:

Before…

clip_image001

…and after adding one DataPoint

clip_image002

Adding or removing DataPoints is not possible so far (version 3.0.1.0). The solution is to remove all DataPoints and re-add it to the chart. In that way we lose the animation, because the chart does animate only on first time load or when updating existing points. Therefore our solution is quite simple: every time any of the chart sources changes a new chart control instance is created with new DataPoints. OnUpdateChart override looks like this:

public class ColumnChart : VisifireWrapper
{
    protected override void OnUpdateChart()
    {

        // init chart control
        Chart chart = new Chart();
        chart.Theme = "Theme2";
        chart.AnimatedUpdate = true;
        chart.AnimationEnabled = true;
        chart.View3D = true;

        // create data series when DataSource is set
        if (this.ChartSource != null)
        {
            // init data point series
            DataSeries series = new DataSeries();
            series.RenderAs = RenderAs.Column;
            series.ShadowEnabled = true;
            series.XValueType = ChartValueTypes.Date;
            series.YValueFormatString = "hh:mm tt";
            series.SelectionEnabled = false;
            chart.Series.Add(series);

            foreach (PointData item in this.ChartSource)
            {
                DataPoint dataPoint = new DataPoint();
                dataPoint.XValue = item.Date;
                dataPoint.YValue = item.Value;
                series.DataPoints.Add(dataPoint);
            }
        }

        // set the chart title
        if (this.TitlesSource != null)
        {
            foreach (string title in this.TitlesSource)
            {
                Title chartTitle = new Title();
                chartTitle.Text = title;
                chart.Titles.Add(chartTitle);
            }
        }

        // completely replace the chart control
        this.Chart = chart;

    }

}

To set the values for your needs, the Visifire Chart samples provide excellent demos as XAML and its very simple to translate it into imperative code.

At the end of the method you must set the Chart property with the new chart. The new chart gets data bound to the ContentPresenter through TemplateBinding.

Internals

Let’s have a look into the internals of the abstract class VisifireWrapper.

Below is the implementation of the ChartSource dependency property, served as a model for the other properties:

/// <summary>
/// Gets or sets a collection used to generate the data point series.
/// </summary>
public IEnumerable ChartSource
{
    get { return (IEnumerable)GetValue(ChartSourceProperty); }
    set { SetValue(ChartSourceProperty, value); }
}
public static readonly DependencyProperty ChartSourceProperty =
    DependencyProperty.Register("ChartSource",
        typeof(IEnumerable),
        typeof(VisifireWrapper),
        new PropertyMetadata(new PropertyChangedCallback(OnChartSourceChanged)));

private static void OnChartSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    IEnumerable oldValue = (IEnumerable)e.OldValue;
    IEnumerable newValue = (IEnumerable)e.NewValue;
    VisifireWrapper source = (VisifireWrapper)d;
    source.OnChartSourceChanged(oldValue, newValue);
}

protected virtual void OnChartSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
    _chartSourceCollectionChanged.SetEventSource(newValue);

    InvalidateChart();
}

 

In the changed handler are two things to do:

  • Set the event source for the WeakEventSource wrapper. The wrapper serves a weak event pattern to listen for CollectionChanged event in a non-memory-leaking way. In two previous post I explain in detail what the WeakEventSource and WeakEventListener serves you.
  • Secondly set an internal flag to invalidate the chart so it gets updated the next time the control renders its child.

You can easily implement additional dependency properties. When using the WeakEventSource you do not have to think about possible memory leaks when listen for source-events.

Summary

The VisifireWrapper is a very helpful base class to implement custom Visifire charts which are bindable to a ViewModel without having a strong reference between the View and ViewModel. The control supports the Silverlight content model except data-templating and encapsulates the weak-event-pattern. In addition the implementation shows the usage of the WeakEventSource implementation I did from a previous post.

Any feedbacks are welcome.

Demo & Source

Below you can find a demo project and the full source code of the VisifireWrapper and WeakEventSource. The demo contains a Single-Series and Multi-Series chart and a demo implementation for common chart types.

clip_image002

Posted by: beatkiener | February 17, 2010

WeakEventSource implementation

The Situation

Implementing custom controls with dependency properties binding to an IEnumarable object collection is not a developer’s daily task. Most of the time one would use the ItemsControl which already provides the ItemsSource property together with the ItemsTemplate property. But what when the functionality provided with the ItemsControl does not meet your requirement? Then you can implement your own dependency property for the source collection like the following:

#region DataPointsSource (DependencyProperty)

public IEnumerable DataPointsSource
{
    get { return (IEnumerable)GetValue(DataPointsSourceProperty); }
    set { SetValue(DataPointsSourceProperty, value); }
}
public static readonly DependencyProperty DataPointsSourceProperty =
    DependencyProperty.Register("DataPointsSource",
                        typeof(IEnumerable),
                        typeof(CustomControl),
                        new PropertyMetadata(new PropertyChangedCallback(OnDataPointsSourceChanged)));

private static void OnDataPointsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}  

#endregion

 

To fully support the Silverlight content model you should check whether the source collection implements INotifyCollectionChanged interface or not. If the source implements INotifyCollectionChanged the custom control code should response to this event and Add/Remove/Replace items accordingly. Attaching the necessary event as following can produce a serious memory leaking problem:

protected virtual void OnDataPointsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
    // implements INotifyCollectionChanged?
    var sourceCollectionChanged = newValue as INotifyCollectionChanged;
    if (sourceCollectionChanged != null)
    {
        // attach the event
        sourceCollectionChanged.CollectionChanged += OnCollectionChanged;
    }
}

 

In a previous post I explained in detail why there can be a memory leak and how we prevent it in our SL controls. The solution is quite the same as Microsoft uses in the Silverlight Toolkit. Additionally David Anson wrote an excellent article about leaking controls and the solution they use in the Silverlight Toolkit.

So, writing code using the WeakEventListener pattern described in the blog posts above it still a tedious task. First you have to detach the previous listener and secondly you have to create a new weak-listener for the new event source. This result in these lines of code:

protected virtual void OnDataPointsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
    // detach previous event source
    if (_weakEventListener != null)
    {
        //  detach WeakEventListener
        _weakEventListener.Detach();
        _weakEventListener = null;
    }

    // attach new WeakEventListener when new source implements INotifyCollectionChanged
    var sourceCollectionChanged = newValue as INotifyCollectionChanged;
    if ( sourceCollectionChanged != null)
    {
        // create WeakEventListener. Pass Listener and EventSource instance to it.
        var weakListener = new WeakEventListener<VisifireMvvmWrapperDemo,
                                      INotifyCollectionChanged,
                                      NotifyCollectionChangedEventArgs>(this, sourceCollectionChanged);
        // register handler
        sourceCollectionChanged.CollectionChanged += weakListener.OnEvent;
        // define event handler to handle the event.
        weakListener.OnEventAction = (instance, source, e) =>
        {
            // event handling code
            instance.OnCollectionChanged(instance, e);
        };
        // define action to detach the handler
        weakListener.OnDetachAction = (listener, source) =>
        {
            // unregister handler
            source.CollectionChanged -= listener.OnEvent;
        };
        // ensure there is no reference from the listener to the weakListener
        weakListener = null;
    }

}

Due to the fact that we use this pattern quite often for custom control development, I was looking for a solution which is easier to use.

Our solution

With an additional wrapper encapsulating the WeakEventListener our developers can now write just the following lines of code to get the same result:

private CollectionChangedWeakEventSource _sourceChanged;

public CustomControl()
{
    // setup wrapper for CollectionChanged
    _sourceChanged = new CollectionChangedWeakEventSource();
    _sourceChanged.CollectionChanged += new NotifyCollectionChangedEventHandler(sourceChanged_CollectionChanged);
}

protected virtual void OnDataPointsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
    // just set the event source to the wrapper
    _sourceChanged.SetEventSource(newValue);
}

void sourceChanged_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{

}
 

In the control constructor (or when I use the wrapper the first time) I set up an instance of the CollectionChangedWeakEventSource and attach the CollectionChanged event. In the OnDataPointsSourceChanged handler I just set the source object for the wrapper. A previous source collection (oldValue) gets detached from the wrapper automatically when you call SetEventSource with the new value. The object passed to the method SetEventSource gets attached when the object implements INotifyCollectionChanged.

The class CollectionChangedWeakEventSource is quite easy to understand. It wraps just one event and uses the WeakEventListener class for the weak event pattern.

public class CollectionChangedWeakEventSource : WeakEventSourceBase<INotifyCollectionChanged>
{
  protected override WeakEventListenerBase CreateWeakEventListener(INotifyCollectionChanged eventObject)
  {
        // create a WeakEventListener
        var weakListener = new WeakEventListener<CollectionChangedWeakEventSource,
                                                 INotifyCollectionChanged,
                                                 NotifyCollectionChangedEventArgs>(this, eventObject);
        weakListener.OnDetachAction = (listener, source) =>
        {
            source.CollectionChanged -= listener.OnEvent;
        };
        weakListener.OnEventAction = (instance, source, e) =>
        {
            // fire event
            if (instance.CollectionChanged != null)
                instance.CollectionChanged(source, e);
        };
        eventObject.CollectionChanged += weakListener.OnEvent;

        return weakListener;
  }

  public event NotifyCollectionChangedEventHandler CollectionChanged;

}
 

The base class WeakEventSourceBase contains the general code to make the custom wrapper as simple as possible. As a result it is quite easy to implement an event wrapper for another event like the PropertyChanged event. Additionally I wrote a code snippet which is available here.

At the end of this post you can find the source code of WeakEventSourceBase.

Some explanation to the code:

  • WeakEventSourceBase contains a check to verify whether the implementation is correct or not. This check is executed only if a debugger is attached. Please note: I don’t use the Conditional(“DEBUG”) attribute to enable/disable this check, because we put the WeakEventSourceBase class into a common framework lib and this lib is RELEASE compiled.
  • Calling SetEventSource(null) is the same as calling Detach()
  • When setting a new event source, the previous source gets detached
  • When setting an event source which does not implement the event, it does not get attached, but the EventSource property is set to this instance and an previous event source gets detached.
  • The class WeakEventListenerBase is new due to the refactoring that was needed to get a non-generic declaration for the CreateWeakEventListener method return value.

Summary

The WeakEventSource implementation is a weak-event-pattern implementation based on the WeakEventListener. The class helps us to reduce writing tedious code in custom control development, reduces memory leaks which are hard to find and last but not least it is easier to review custom control written that way, because a Reviewer must not have full knowledge about the weak-event pattern.

In a blog post later this week I’m going to demonstrate a complete custom control implementation using this WeakEventSource implementation.

 

Source Code

Here you can find the full source code.

image

WeakEventSourceBase class:

/// <summary>
/// Base class to wrap a specific event with the WeakEventListener.
/// </summary>
/// <typeparam name="TSource">The type of the event source.</typeparam>
public abstract class WeakEventSourceBase<TSource>
     where TSource : class
{

    /// <summary>
    /// A weak reference to the event source
    /// </summary>
    private WeakReference _weakEventSource;

    /// <summary>
    ///  A weak reference to the WeakEventListener instance.
    /// </summary>
    private WeakReference _weakListener;

    /// <summary>
    /// Gets the event source instance which this listener is using.
    /// </summary>
    /// <remarks>
    /// The reference to the event source is weak.
    /// </remarks>
    public object EventSource
    {
        get
        {
            if (_weakEventSource == null)
                return null;

            return this._weakEventSource.Target;
        }
    }

    /// <summary>
    /// Set the event source for this instance. 
    /// When passing a new event source it replaces the event source the 
    /// listener is listen for an event. When passing null/nothing is detaches 
    /// the previous event source from this event listener. 
    /// </summary>
    /// <param name="eventSource">The event source instance.</param>
    public void SetEventSource(object eventSource)
    {
        // the listener can just listen for one event source. 
        // Detach the previous event source
        this.Detach();

        // keep weak-reference to the the event source
        this._weakEventSource = new WeakReference(eventSource);

        TSource eventObject = eventSource as TSource;
        if (eventObject != null)
        {
           var weakListener = CreateWeakEventListenerInternal(eventObject);

           if (weakListener == null)
              throw new InvalidOperationException("The method CreateWeakEventListener must return a value.");

           // store the weak-listener as weak reference (for Detach method only)
           _weakListener = new WeakReference(weakListener);
        }
    }

    /// <summary>
    /// Does some debug-time checks and creates the weak event listener.
    /// </summary>
    /// <param name="eventObject">The event source instance</param>
    /// <returns>Return the weak event listener instance</returns>
    private WeakEventListenerBase CreateWeakEventListenerInternal(TSource eventObject)
    {
        #region Debug time checks

        // do some implementation checks when a debugger is attached
        if (Debugger.IsAttached)
        {
            // search in each type separately unitl we reach the type WeakEventSourceBase 
            //(because Reflection can not return private members in FlattenHierarchy.
            Type type = this.GetType();
            while ((!type.IsGenericType || type.GetGenericTypeDefinition()                                         != typeof(WeakEventSourceBase<>)) && type != typeof(object))
            {
                BindingFlags bindingFlags = BindingFlags.Public |
                                            BindingFlags.NonPublic |
                                            BindingFlags.Instance |
                                            BindingFlags.Static |
                                            BindingFlags.DeclaredOnly;

                // get fields expect fields marked with CompilerGeneratedAttribute or derived                 // from Delegate (events are delegate fields)
                var queryFields = from f in type.GetFields(bindingFlags)
                    where f.GetCustomAttributes(typeof(CompilerGeneratedAttribute),true).Count()==0 &&
                          !f.FieldType.IsSubclassOf(typeof(Delegate))
                          select f.Name;

                // get properties
                var queryProperties = from f in type.GetProperties(bindingFlags)
                                      select f.Name;

                var query = queryFields.Union(queryProperties);

                // The EventWrapper is intended to be used as a weak-event-wrapper. One should not add 
                // additional members to this class, because of the possibilty to store the 
                // WeakEventListener reference to a member.                // Is this the case the memory leak can still occur. 
                // Therefore, if any field or property is implemented, throw an exception as warning.
                if (query.Count() > 0)
                {
                   // note: MessageBox.Show blocks unit tests
                   throw new InvalidOperationException(string.Format("You should not add any other “ +                             “implementation than overriding methods in the class {0}, because of “ +
                             “possible memory you can get within your application.", type.Name));
                }

                // continue search in base type
                type = type.BaseType;
            }
        }

        #endregion

        // create weak event listener
        return CreateWeakEventListener(eventObject);
    }

    /// <summary>
    /// When overridden in a derived class, it creates the weak event     /// listener for the given event source.
    /// </summary>
    /// <param name="eventObject">The event source instance to listen for an event</param>
    /// <returns>Return the weak event listener instance</returns>
    protected abstract WeakEventListenerBase CreateWeakEventListener(TSource eventObject);

    /// <summary>
    /// Detaches the event from the event source.
    /// </summary>
    public void Detach()
    {
        if (_weakListener != null)
        {
            // do it the GC safe way, because an object could potentially be reclaimed 
            // for garbage collection immediately after the IsAlive property returns true
            WeakEventListenerBase target = _weakListener.Target as WeakEventListenerBase;
            if (target != null)
                target.Detach();
        }

        _weakEventSource = null;
        _weakListener = null;
    }
}

« Newer Posts - Older Posts »

Categories

Follow

Get every new post delivered to your Inbox.