Silverlight: Product Maintenance Application ( Part 6 – Porting to WPF )

Following on from this post.

As expected, porting my Utilities library to WPF wasn’t too difficult. It was a matter of creating a WPFUtilities class library, adding the existing source from Utilities as linked code files and then referencing PresentationCore and PresentationFramework and hitting build.

image

then again, there were only a couple of very basic classes in there so it’s not too surprising. Next, I thought I’d try and port my Security project and so created another Class Library project WPFSecurity and added the existing source files as linked files again.

This project though is a bit more complex in that it has 2 sets of WCF service references in order to call the Authentication and Role services made available by ASP.NET. I need to add service references to my WPFSecurity project and try and ensure as few code changes as possible by doing that.

In adding the references, I made sure to use the same proxy namespace names;

image

and also all references in Silverlight are asynchronous and so my code already works that way and I want to be able to leave the code alone so I made sure to tick the asynchronous operation box on the Advanced… dialog;

image

I still ended up with a slight namespace clash ( I’m sure there’s an option somewhere to fix this but I ended up fixing it with a tiny bit of conditional code );

#if SILVERLIGHTusing AuthServiceProxyCode;#elseusing WPFSecurity.AuthServiceProxyCode;#endif
#if SILVERLIGHT using RoleServiceProxyCode;#elseusing WPFSecurity.RoleServiceProxyCode;#endif

 

 

and so that was a fairly easy port ( not sure if the code works of course as I lack a whole bunch of testing code to try it out ).

Next…time for my Data project, this one worried me a little more because it has more service references. Again, I created a WPFData data project and added in my linked source files and re-created the service reference to my data service.

The first problem I hit here turned out to be that I needed to bring in windowsbase.dll alongside PresentationFramework.dll and PresentationCore.dll but that was easy enough.

Also, because I hadn’t referenced windowsbase when I added my service reference, I didn’t get the option of using ObservableCollection<T> as the type for collections passed to/from my service layer and so my existing Silverlight code which was based around ObservableCollection<T> wasn’t going to build. Once I added windowsbase.dll and re-added the service reference with the right option;

image

then things were better and my WPFData project was building ok.

Next it was time to move on to the Controls project which is where I was expecting more of a battle because it’s the first project that contains XAML plus it makes use of DataGrid and also the charts from the Silverlight Toolkit.

Around about this point I realised that calling my new projects WPFXXXX was a mistake – I should have preserved all the original names so that there were no difference in assembly names and so on as that would have saved me a bunch of conditional XAML work and so I revisited the projects and changed the namespaces and output assembly names to take away the preceding WPF to make things easier.

I used the same PreProcessXaml.targets file as referenced in my post here in order that I can conditionally pre-process XAML and I added this to my WPFControls project and my Controls project so that the XAML ( which is the same files in both projects as they are just linked ) could be cross-compiled like the code could be cross-compiled.

In terms of controls – the BusyDisplayControl, LoginControl, SearchControl all ported without any changes. My WpfComboBox doesn’t really make any sense when we’re using WPF but I left it in the source file and just turned it into a ComboBox as in;

namespace Controls{#if WPF    // In WPF, we don't need WpfComboBox! but in order to preserve the XAML  // we leave it  public class WpfComboBox : ComboBox  {  }#else  public class WpfComboBox : ComboBox  {

and that means that I don’t have re-work the XAML for the my ProductsDisplayGridControl. In that control, I make use of the DataGrid and so I referenced the WPFToolkit and had to make a couple of changes to the XAML definition of my control;

  1. One was to make sure that a resource that I had given an x:Name in Silverlight actually had an x:Key for both Silverlight and WPF.
  2. Another was to conditionally compile a namespace declaration for the DataGrid. In Silverlight it lives in System.Windows.Controls and in the WPF Toolkit it lives in Microsoft.Windows.Controls. The assembly names are also different ( System.Windows.Controls.Data versus WPFToolkit ) so I needed a bit of conditional compilation;
#if SILVERLIGHT    xmlns:dg="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data">#else    xmlns:dg="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit">#endif

With that in place, it was time to tackle the remaining chart control. There’s a “preview” of the Silverlight charting source code cross-compiled to WPF but it’s not an official preview, it’s more of an experiment from the Silverlight Toolkit team but I took down the source and had a look at it and pulled out the System.Windows.Controls.DataVizualiation.Toolkit.dll and referenced it from my WPF project and that let me compile all of my controls for WPF. It’ll be interesting to see what happens at runtime 🙂

With all of my sub-projects reworked I then looked to the main project itself. I created an application WPFApplication and referenced my WPFSecurity, WPFData, WPFControls, WPFData projects from it.

My Silverlight project was originally called SilverlightApplication6 which was a bad idea so I renamed the assembly it produced to be ProductsApplication and renamed the default namespace and the namespaces of any classes. I made sure that my WPF project had the same namings for things.

I had a problem with GridSplitter in my main XAML file because in Silverlight it lives in System.Windows.Controls.dll whereas it’s already kicking around in WPF so I had to do a little conditional compilation here which meant adding the import of my PreProcessXaml.targets file to both of these projects in order to do a little conditional compilation.

The other problem I had was with my App.xaml. I don’t think my PreProcessXaml.targets file deals with App.xaml ( the original post here said it wouldn’t ) and I had content inside of my app.xaml which needed to show up in both the Silverlight application and the WPF application and yet App.xaml needs to look pretty different for WPF versus Silverlight.

The content of my App.xaml looks like this;

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

             x:Class="ProductsApplication.App"

             xmlns:sec="clr-namespace:Security;assembly=Security"

             xmlns:ut="clr-namespace:Utilities;assembly=Utilities">

    <Application.Resources>

        <sec:DynamicLoginStatus

            x:Key="logonStatus" />

        <sec:DynamicRoleStatus

            x:Key="roleStatus" />

        <ut:BooleanToVisibilityConverter

            x:Key="converter" />

        <ut:BooleanToVisibilityInverter

            x:Key="visibilityInverter" />

        <ut:BooleanInverter

            x:Key="booleanInverter" />

    </Application.Resources>

</Application>

What I tried to do is factor the content out of this into a separate file AppXamlResources.xaml in my Silverlight project and then made use of it as;

<Application

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    x:Class="ProductsApplication.App"

    xmlns:sec="clr-namespace:Security;assembly=Security"

    xmlns:ut="clr-namespace:Utilities;assembly=Utilities">

    <Application.Resources>

        <ResourceDictionary>

            <ResourceDictionary.MergedDictionaries>

                <ResourceDictionary

                    Source="/AppXamlResources.xaml" />

            </ResourceDictionary.MergedDictionaries>

        </ResourceDictionary>

    </Application.Resources>

</Application>

and then I linked the same AppXamlResources.xaml into my WPF project and so I can have a different App.xaml and App.xaml.cs in Silverlight to the one in WPF but they can both reference a common file that’s shared between them.

I then came to running my WPF application and came across an odd problem. An error from WCF ( somewhere ) saying;

{"Unable to find an entry point named 'EnumerateSecurityPackagesW' in DLL 'security.dll'.":""}

Now…I suspect that this is because I’ve produced a DLL called Security. Mistake. I went around and made sure I renamed it so it’s not called Security.dll any more, it’s called SecurityLibrary.dll and that got me around some unmanaged code that I didn’t write trying to find its entry point in my DLL rather than it’s DLL ( c:\windows\security.dll ).

Phew.

This got me to the point where I could run my WPF application and use it to login ( or not if I supplied invalid credentials ). However, any further subsequent calls to my back-end services were coming back as unauthenticated and that’s because Silverlight automatically propagates ASP.NET authentication cookies over its network stack but WPF service calls are separate things with no shared cookies ( much as you’d expect 🙂 ).

So, for WPF I need to ensure that a consistent cookie container is used on its web service calls. This means a little extra code conditionally compiled for WPF to manage those cookies and those need to span across all calls to web services. I knew this was coming as I mentioned it back in my first post when I started the Silverlight version of the application.

I ended up doing this by not changing any of my existing code that called back-end services but, instead, by implementing an IClientMessageInspector which gets to look at the message before it gets sent from the client and after a response is retrieved. I wrote some fairly basic code to propagate what I am assuming here is the only cookie being returned which I’ve changed via ASP.NET config on the server side to be called “ASPAUTH”. The code looks like this and I added it to my WPFApplication project itself right now as no other code needs to use it;

  public class MyInspector : IClientMessageInspector  {    public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)    {      HttpResponseMessageProperty httpResponse =        reply.Properties[HttpResponseMessageProperty.Name] as HttpResponseMessageProperty;      if (httpResponse != null)      {        string cookie = httpResponse.Headers["Set-Cookie"];        if (!string.IsNullOrEmpty(cookie))        {          // TODO: improve          int pos = cookie.IndexOf(';');          if (pos != -1)          {            cookie = cookie.Substring(0, pos);            string[] pieces = cookie.Split('=');            if ((pieces.Length == 2) && (pieces[0] == aspNetAuthCookieName))            {              aspNetAuthCookieValue = pieces[1];            }          }        }      }    }    public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)    {      HttpRequestMessageProperty httpRequest =        request.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty;      if ((httpRequest != null) && (!string.IsNullOrEmpty(aspNetAuthCookieValue)))      {        httpRequest.Headers.Add("Cookie", string.Format("{0}={1}", aspNetAuthCookieName, aspNetAuthCookieValue));      }      return (null);    }    static string aspNetAuthCookieValue;    const string aspNetAuthCookieName = "ASPAUTH";  }  public class MyBehavior : IEndpointBehavior  {    public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)    {          }    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)    {      clientRuntime.MessageInspectors.Add(new MyInspector());    }    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)    {          }    public void Validate(ServiceEndpoint endpoint)    {          }  }  public class MyExtension : BehaviorExtensionElement  {    public override Type BehaviorType    {      get { return (typeof(MyBehavior)); }    }    protected override object CreateBehavior()    {      return (new MyBehavior());    }  }

and I added a bit of config on the WPF client side to plug that in;

<extensions>

    <behaviorExtensions>

      <add name="clientInterception"

           type="ProductsApplication.MyExtension, ProductsApplication, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>

    </behaviorExtensions>

  </extensions>

  <behaviors>

    <endpointBehaviors>

      <behavior name="endpointBehavior">

        <clientInterception/>

      </behavior>

    </endpointBehaviors>

  </behaviors>

With that added my application sprung up in its WPF form for the first time;

image

and I could log in as a role ( e.g. veronica my viewer ) and do a search for data, view a sales graph and so on but I noticed that my reference data lookups in ComboBoxes weren’t working;

image

Now…I’d done a really horrible hack for the binding of these ComboBoxes so I wasn’t entirely surprised. I have a DataGrid in my ProductsDisplayGridControl and its template included a line like this;

<dg:DataGridTemplateColumn

                            Header="Category">

                            <dg:DataGridTemplateColumn.CellTemplate>

                                <DataTemplate>

                                    <controls:WpfComboBox

                                        IsEnabled="{Binding Source={StaticResource roleStatus},Path=IsEditor}"

                                        ItemsSource="{Binding Source={StaticResource referenceDataSource},Path=ReferenceData.Categories}"

                                        DisplayMemberPath="Title"

                                        SelectedValuePath="Id"

                                        SelectedValue="{Binding CategoryID,Mode=TwoWay}" />

                                </DataTemplate>

                            </dg:DataGridTemplateColumn.CellTemplate>

and the item referenceDataSource was actually a Rectangle stuck in the local resources section purely to try and get to the right DataContext to make this work. Not a great idea 🙂

I revisited this. I have my DataModel { SalesChartData, ReferenceData, ProductsData } and I was creating the DataModel in code and then setting it as the DataContext for my ProductsDisplayGridControl but my “problem” was that I then wanted to bind the ItemsSource above to the property DataModel.ReferenceData.Categories which felt a bit tricky when the DataGrid has its own ItemsSource bound to DataModel.ProductsData.Products.

So, I changed things around a little. I altered my main App.xaml to allow XAML to create the DataModel;

<ResourceDictionary

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:sec="clr-namespace:Security;assembly=SecurityLibrary"

    xmlns:ut="clr-namespace:Utilities;assembly=Utilities"

    xmlns:data="clr-namespace:Data;assembly=Data">

    <sec:DynamicLoginStatus

        x:Key="logonStatus" />

    <sec:DynamicRoleStatus

        x:Key="roleStatus" />

    <ut:BooleanToVisibilityConverter

        x:Key="converter" />

    <ut:BooleanToVisibilityInverter

        x:Key="visibilityInverter" />

    <ut:BooleanInverter

        x:Key="booleanInverter" />

    <data:DataModel

        x:Key="dataModel" />

</ResourceDictionary>

and then I just pick that up in code at runtime as in;

    void OnLoaded(object sender, RoutedEventArgs e)    {      dataModel = (DataModel)App.Current.Resources["dataModel"];      this.DataContext = dataModel;    }

and I now have a StaticResource that I can point to for my DataModel which means that when I come to bind my ComboBox in a templated DataGrid control in a UserControl I can just reach-out to that StaticResource as in;

<dg:DataGridTemplateColumn

                           Header="Category">

                           <dg:DataGridTemplateColumn.CellTemplate>

                               <DataTemplate>

                                   <controls:WpfComboBox

                                       IsEnabled="{Binding Source={StaticResource roleStatus},Path=IsEditor}"

                                       ItemsSource="{Binding Source={StaticResource dataModel},Path=ReferenceData.Categories}"

                                       DisplayMemberPath="Title"

                                       SelectedValuePath="Id"

                                       SelectedValue="{Binding CategoryID,Mode=TwoWay}" />

                               </DataTemplate>

                           </dg:DataGridTemplateColumn.CellTemplate>

 

i.e. – using {StaticResource dataModel},Path=ReferenceData.Categories to get to the data that I need. Works fine from both WPF and Silverlight and takes away my embarrassing Silverlight-Rectangle hack so it all turns out well.

With that in place I’ve got a WPF application and a Silverlight application with the same code-base working in the same way although I do notice a suspicious empty row at the bottom of the WPF grid here;

image

I’ve shared the source for all of this here for download. Some notes on this;

  1. I’m using a modified version of Ben’s original PreProcessXaml.targets file so if you try to build the code you’ll need a C++ pre-processor to do it.
  2. There are a number of projects in the solution and some aren’t-so-brilliantly-named;
    1. AuthBits – this is just a bit of work that’s referenced from the web site in order to flow the HttpContext identity into WCF. I referred to it in a previous post.
    2. ProductsMaintenance – this is the web site hosting my web services. I have it hosted in IIS because it needs HTTPS. It also relies on ASP.NET Membership and Roles so you’d need to make sure that you have something configured there ( my roles are users, viewers, editors )
    3. SilverlightApplication6 – the main Silverlight application which is also deployed to the ProductsMaintenance website.
    4. Data – my data classes that sit in front of the web services I use.
    5. Controls – my UserControls.
    6. Security – my classes that sit in front of ASP.NET Membership and Roles.
    7. Utilities – a few simple common classes.
    8. WPFApplication – the main WPF application – links to the MainPage control in the Silverlight project and also the AppXamlResources.xaml file in that project. Introduces its own App.xaml (nothing in it) and a Window1.xaml ( nothing in it ).
    9. WPFControls – the user controls from Controls linked in and cross-compiled to WPF.
    10. WPFData – the data code of Data linked-in and cross-compiled to WPF.
    11. WPFSecurity – the security code of Security linked-in and cross-compiled to WPF.
    12. WPFUtilities – the utilities code of Utilities linked-in and cross-compiled to WPF.

Note also that this code makes use of the SilverlightToolkit for charts and also the WPF Toolkit for the DataGrid and also the libraries that I referred to on the previous post which is a experimental port of the Silverlight Toolkit chart control to WPF.

Enjoy – remember, it’s just a sample and it’s not meant to be anything near what I’d write for a production-quality application. In terms of where I’d like to go with this next I’d like to;

  1. Add some common validation code.
  2. Look at re-building the same thing with the RIA Services Framework and see how much it saves me ( I’d imagine a lot 🙂 ).