Click here to Skip to main content
15,867,488 members
Articles / Desktop Programming / WPF

Dynamic LINQ to Entities Queries Using WCF/WPF Demo Code

Rate me:
Please Sign up or sign in to vote.
4.93/5 (86 votes)
30 Nov 2008CPOL22 min read 248.2K   4.5K   230   66
Demonstrates a method of dynamic query across WCF Service boundaries.

Contents

Introduction

It has been a while since I have undertaken a large article, so I thought it was about time I corrected that, and wrote a fairly meaty article. This article represents this meat.

I suppose I should start by stating what it is this article will cover. Well, it's going to cover quite a lot actually. The basic idea is that there is a SQL Server database somewhere that has the Northwind database installed, and we will be using an ORM to obtain data from this database using a WCF Service. WCF itself will be hosted inside a Windows Service, which will be installed via a customer installer.

In order to interact with this Windows hosted WCF Service, we will be using a WPF client application. The WPF client application will allow the user to obtain particular entities (Customers only, as I would have been writing this article forever to allow for everything) to be queried using a custom query builder that will then be used to send to the Windows hosted WCF Service, which in turn will then query the ORM for the queried entities, and return the results to the WPF client app.

In essence, that is it; it may not be that exciting, but there is enough here for me to show you how to create a service/client and a hosting Windows Service. Also, along the way, I will be taking slight detours to talk about certain practices and cool code that may make your life easier.

I should point out that the UI was meant to be a throw away; OK, I tried to make it an OK looking one, but that's just because I like WPF. I mean, there is still some nice ideas in there, like the way the queries are constructed, that could be ported to a richer query generator, but that is left as an exercise to the reader.

Prerequisites

You will need VS2008 (for the Entity Framework) and VS2008 SP1, and .NET 3.5 SP1 installed.

Things to Do to Get it Running For You

In order to get the attached code (WCF Service hosted in a Windows Service/WPF client), you will need to make sure the following has been done:

  1. That the Windows Service has been installed and provided with login details as part of the installation.
  2. That the LINQ to Entities connectionStrings section of the associated .Config file is updated to point to your local SQL Server installation.
  3. That the Windows Service is running when you attempt to use WPFClient.
  4. That the App.Config within the WPFClient project is configured to use the correct security login; this is in the identity/userPrincipalName element.

Just to get you a little bit more familiar with it all, here is the project structure:

Image 1

Show it

What does it look like when running? Well, I decided to show you some screenshots, and also have a link to a small video for this one:

Image 2

Initial view loaded

Image 3

Changing the type of view for the selected Customer Orders

Image 4

Starting to build up a dynamic query

LINQ to Entities

For the ORM, I had originally thought about using LINQ to SQL, but you know, I think Microsoft is slowly sweeping (personal opinion) that one under the carpet in favor of LINQ to Entities. So I thought why not use LINQ to Entities. That's what this article uses for the ORM.

I had in the past installed LINQ to Entities whilst it was still in BETA, and it was horrible; the designer used to churn out three sections of XML:

As I saw, the designer used to create these, but not all that well in the old days, and you always had to get your hands dirty in the XML, and there was loads of it, a couple of 1000 lines for a small database. Eeek.

If you want to know how bad this was, just use any of the three links above.

Maintaining these three sections of XML using the old beta of LINQ to Entities was a frigging nightmare. Thankfully, the new designer does away with the user having to worry about the XML directly, which I think is a good thing. What happens now is that the code-behind for the designer is now a C#|VB class that has an App.Config file associated with it, with a very strange ConnectionStrings section. Let us have a look at one of these:

XML
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <connectionStrings>
    <!-- HOME -->
    <add name="NorthwindEntities" 
    connectionString="metadata=res://LINQ2Entities/Northwind.csdl|
    res://LINQ2Entities/Northwind.ssdl|
    res://LINQ2Entities/Northwind.msl;provider=System.Data.SqlClient;
    provider connection string="Data Source=VISTA01\SQLEXPRESS;
    Initial Catalog=Northwind;Integrated Security=True;
    MultipleActiveResultSets=True"" 
    providerName="System.Data.EntityClient" />

  </connectionStrings>
</configuration>

To run the attached code, you will need to change the associated config within the folder where you decide to install the service using the installer; this is covered later within the article in the section: Windows Service Hosting and Installation.

We can see that there are still links to CSDL/MSL/SSDL, but they are now treated as metadata resources. Much nicer.

So if we now focus our attention to the LINQ to Entities Designer, we can see that we have something like the LINQ to SQL Designer, but it is a little different. This is to allow an Entity to be constructed from multiple views|tables.

Image 5

For this example, I will, however, be using a 1 to 1 mapping between the Entity and the Northwind database table.

LINQ to Entities SQL Language

LINQ to Entities actually goes one step further than LINQ to SQL does in that it now supports a full blown SQL query language, which as far as I can tell is actually the same as the SQL syntax. Here is a small example:

C#
using (AdventureWorksEntities advWorksContext =
       new AdventureWorksEntities())
{
    string esqlQuery = @"SELECT contactID, AVG(order.TotalDue) 
                            FROM AdventureWorksEntities.SalesOrderHeader 
                            AS order GROUP BY order.Contact.ContactID as contactID";

    try
    {
        foreach (DbDataRecord rec in
            new ObjectQuery<DbDataRecord>(esqlQuery, advWorksContext))
        {
            Console.WriteLine("ContactID = {0}  Average TotalDue = {1} ",
                rec[0], rec[1]);
        }
    }
    catch (EntityException ex)
    {
        Console.WriteLine(ex.ToString());
    }
    catch (InvalidOperationException ex)
    {
        Console.WriteLine(ex.ToString());
    }
}

Now, the fact that we can store a String with a query is kind of nice, as it means we can pass a query across a service boundary, which was something that LINQ to SQL just couldn't do out of the box. I mean, you couldn't serialize a Expression<Func<Customers,Bool>> nor could you return a var, as var has a method level scope. You are also unable to create LINQ queries at a client and serialize them (though this would be very cool) and send them to a service to run. So, LINQ to SQL was definitely limited. So this new String query ability appears to be a good thing at first glance.

But, let's just think about that for a minute. We used to be able to do this sort of thing with inline/across the wire SQL using ADO.NET, and look what a mess that was, not to mention SQL Injection Attacks. So, one would have to ask whether this is a good thing after all. I mean, what is to stop a malicious user from creating their own query string; the fact that it now uses the Entity Framework rather than the actual database doesn't really matter to the malicious user, they would probably get the same results.

I think a far better option to is to just limit the search results by using a dynamically generated Where clause, or if you have time, create a SQL Query Builder that never actually contains the full string, until the last second when it needs to create the actual query string, and pass it to the DB|ORM. The latter approach is what we actually do at work; this article will actually discuss the dynamically generated Where clause solution.

Dynamic Where Clauses

LINQ to Entities actually offers a ObjectQuery<T>.Where method that accepts a string and an array of ObjectParameters.

Image 6

According to the ObjectParameter documentation, the following should actually work. Unfortunately, it doesn't seem to like the ObjectParameter names.

Image 7

How annoying! Oh well, luckily, help is at hand. Recall what I said about LINQ to SQL not being able to run dynamic queries out of the box? Well, that was/is true. There is, however, an extra Microsoft constructed class that has IQueryable<T> extension methods that allow both LINQ to Entities and LINQ to SQL to create dynamic Where clauses.

So if we change the original query to use the DynamicQuery API, we can successfully run this query. We must however use numbered parameters, which is a bit of a pain, but it is still better than the LINQ to Entities horror show shown above. I can only imagine the reason for the problem shown above is that the LINQ to Entities implementation has some strange parsing in there to try and make the actual SQL, whilst the DynamicQuery API will try and create a Expression<Func<Customers,Bool>>.

Image 8

How These Two APIs Differ

As just mentioned when using the native LINQ to Entites ObjectQuery<T>.Where method, the System.Data.Entity classes actually try and generate the actual SQL which can be used against the database.

Image 9

Image 10

Image 11

Let's now see how the DynamicQuery API works. What it does is provides an extension method against a IQueryable<T> type, and it creates an Expression Tree based on the input values.

Here is a small section of the DynamicQuery API:

C#
public static IQueryable<T> Where<T>(this IQueryable<T> source, 
       string predicate, params object[] values)
{
    return (IQueryable<T>)Where((IQueryable)source, predicate, values);
}

public static IQueryable Where(this IQueryable source, 
    string predicate, params object[] values)
{
    if (source == null) throw new ArgumentNullException("source");
    if (predicate == null) throw new ArgumentNullException("predicate");
    LambdaExpression lambda = 
        DynamicExpression.ParseLambda(source.ElementType, 
        typeof(bool), predicate, values);
    return source.Provider.CreateQuery(
        Expression.Call(
            typeof(Queryable), "Where",
            new Type[] { source.ElementType },
            source.Expression, Expression.Quote(lambda)));
}

Some of you may recall that I recently wrote an article about Expression Trees and went to show you how they worked. This DynamicQuery API is the entire reason that I wrote that article.

I hope you can now see why we might want to create an Expression Tree at runtime. It allows us to create a dynamic query based on an Expression Tree.

WCF Service

Overall Structure

The WCF Service is actually pretty simple; the entire ServiceContract is shown below:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace Service.Portal
{
    [ServiceContract(
        Namespace = "http://www.sachabarber.net/2008/cp", 
        SessionMode = SessionMode.Allowed)]
    public interface IPortal
    {
        //Allows FaultContract(s) to callback
        [OperationContract(IsOneWay = false)]
        //Allows TransactionScope at client
        [TransactionFlow(TransactionFlowOption.Allowed)]
        //Allows SOAP faults to client
        [FaultContract(typeof(ApplicationException))]
        [FaultContract(typeof(ArgumentException))]
        [FaultContract(typeof(Exception))]
        Response ExecuteRequest(Request request);
    }
}

It can be seen from the code above that there is a single ExecuteRequest OperationContract that accepts a Request and returns a Response. Sounds easy enough, but aren't there going to be many many Request objects? Well, yes actually, but it is just a bit of standard OO polymorphism in play, where we can store any subclass of Request in its base class. I'll cover this in a minute; for now, let's carry on examining the service. It can be seen that the single ExecuteRequest OperationContract is marked as OneWay=false; this means that there is a callback to the client from the service. You may notice that I am not specifying a CallBack interface anywhere, so what is the OneWay=false all about? Quite simply, it allows faults to be sent back to the client. More on this later. The other thing to note is that the service allows transactions to be used should the client wish to use them.

Let's now have a look at the Request object, shall we?

C#
using System;
using System.Runtime.Serialization;

namespace Service.Portal
{
    /// <summary>
    /// A Base request
    /// </summary>
    [DataContract]
    [KnownType(typeof(CustomerRequest))]
    [KnownType(typeof(CustomerSearchRequest))]
    public abstract class Request : IExtensibleDataObject
    {
        #region Data
        private ExtensionDataObject extensionDataObject=null;
        #endregion

        #region Abstract Methods
        public abstract Response CreateResponse(Object requestResults);
        public abstract Object Execute();
        #endregion

        ....
        ....
    }

That's essentially it. By using this approach, you do not have to worry about a changing service, as the ServiceContract is always the same; the only thing that changes is the number of Requests.

And here is a specific Response DataContract class:

C#
namespace Service.Portal
{
    [DataContract]
    public class CustomerResponse : Response
    {
        [DataMember(IsRequired = true, Name = "Customers")]
        public List<Customers> Customers { get; set; }

    }
}

KnownTypes

One of the really cool things about WCF is that you can expose a type such as Request which has many subclasses, and you can mark the base class (Request, in this case) up with a KnowTypeAttribute, and the DataContractSerializer will know how to treat these.

Within the attached demo application, there are only two subclasses of Request, but they should serve well enough to demonstrate the idea of working with KnowTypes.

IExtensibleDataObject

"The IExtensibleDataObject interface provides a single property that sets or returns a structure used to store data that is external to a data contract. The extra data is stored in an instance of the ExtensionDataObject class and accessed through the ExtensionData property. In a roundtrip operation where data is received, processed, and sent back, the extra data is sent back to the original sender intact. This is useful to store data received from future versions of the contract. If you do not implement the interface, any extra data is ignored and discarded during a roundtrip operation."

MSDN link: http://msdn.microsoft.com/en-us/library/system.runtime.serialization.iextensibledataobject.aspx.

In practical terms, what this means is that by making your DataContract class implement IExtensibleDataObject, you are creating a versionable object.

In the attached demo code, Request/Response implements IExtensibleDataObject as follows:

C#
using System;
using System.Runtime.Serialization;

namespace Service.Portal
{
    [DataContract]
    [KnownType(typeof(CustomerRequest))]
    [KnownType(typeof(CustomerSearchRequest))]
    public abstract class Request : IExtensibleDataObject
    {
        private ExtensionDataObject extensionDataObject=null;


        public ExtensionDataObject ExtensionData
        {
            get { return extensionDataObject; }
            set { extensionDataObject = value; }
        }
    }
}

ExtensionDataObject has an internal linked list of object references and type information, and it knows where unknown data members are stored. When an object implements IExtensibleDataObject, and when a request is made that includes unknown data members, they are stored in the list of unknown members within the internal list. If a request for an unknown data member is made that includes unknown types, the unknown data members can be found and deserialized by using the ExtensionDataObjects internal list of unknown data members.

Basically, this is a good idea, and one that I recommend that you all do.

Fault Contracts

We will now visit the WCF Service and have a look at the class that implements the ServiceContract.

The WCF Service is actually pretty simple; the entire ServiceContract is shown below:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace Service.Portal
{
    [ServiceContract(
        Namespace = "http://www.sachabarber.net/2008/cp", 
        SessionMode = SessionMode.Allowed)]
    public interface IPortal
    {

        //Allows SOAP faults to client
        [FaultContract(typeof(ApplicationException))]
        [FaultContract(typeof(ArgumentException))]
        [FaultContract(typeof(Exception))]
        Response ExecuteRequest(Request request);
    }
}

We can see that we are catching Exceptions and throwing some strange looking FaultException<T>s, where the FaultException<T>s are used in a client application to catch contractually-specified SOAP faults. It can be seen that the service implementation is set for per call, which means we will get a new service per call, and we are not concerned with dealing with a threading synchronization context. The other thing to mention is that we have a small helper that lets us use IncludeExceptionDetailInFaults if we are in Debug mode, and not in Release mode. The single operation is also marked to not allow impersonation. There is no need to allow impersonation in the attached service code as it is the only service within the call chain, so there is no need to flow the current credentials to another service. I have also insisted the service participate in transactional calls, where the client code will need to use a TransactionScope class to manage the transaction. The TransactionAutoComplete property indicates that if no unhandled exceptions occur, the transaction scope is completed automatically.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Reflection;

namespace Service.Portal
{
    [ServiceBehavior(
        InstanceContextMode= InstanceContextMode.PerCall, 
        UseSynchronizationContext = false, 
        IncludeExceptionDetailInFaults =  
        DebugHelper.IncludeExceptionsInFaults)]
    public class Portal : IPortal
    {

        #region IPortal Members

        [OperationBehavior(Impersonation = ImpersonationOption.NotAllowed, 
            TransactionScopeRequired = true, TransactionAutoComplete = true)]
        public Response ExecuteRequest(Request request)
        {
            Response r = null;

            if (request == null)
                throw new ArgumentNullException("request");

            try
            {
                Object results = request.Execute();
                r = request.CreateResponse(results);
            }
              ...
            ...
            ...
            catch (Exception ex)
            {
                Console.WriteLine(String.Format("Server Exception {0}", 
                    ex.Message));

                throw new FaultException<Exception>(
                    new Exception(ex.Message));
            }


            return r;
        }

 

        #endregion

    }

    //simply debug class to IncludeExceptionsInFaults in debug mode
    public static class DebugHelper
    {
        public const bool IncludeExceptionsInFaults =
#if DEBUG
            true;
#else
        false;
#endif

    }
}

Windows Service Hosting and Installation

As I said right at the start of this article, the WCF Service is actually hosted inside a Windows Service. Inside the attached demo, you will find a separate project that allows you to debug the WCF Service by the use of the following code if you are in Debug mode (where you would right click the PortalHost project and choose "Debug"), or run the service normally if you are in Release mode.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Reflection;

namespace PortalHost
{
    class Program
    {
        static void Main(string[] args)
        {
#if (!DEBUG)
            try
            {
                ServiceBase[] ServicesToRun;
                ServicesToRun = new ServiceBase[] { new Service() };
                ServiceBase.Run(ServicesToRun);
            }
            catch (Exception e)
            {
                Console.WriteLine("Error occurred " + e.Message);
            }
#else
            try
            {
                Console.WriteLine("Starting Service");
                Service.StartService();
                Console.WriteLine("Service Started");
                System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);

            }
            catch (Exception e)
            {
                Console.WriteLine("Error occurred " + e.Message);
            }
#endif
        }
    }
}

Where the actual Window Service class looks like this:

C#
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.ServiceModel;
using System.Reflection;
using Service.Portal;
using System.ServiceModel.Description;

namespace PortalHost
{
    public partial class Service : ServiceBase
    {
        private static ServiceHost portalHost;

        public Service()
        {
            
        }

        protected override void OnStart(string[] args)
        {
            Service.StartService();
        }

        public static void StartService()
        {
            try
            {
                //WCF service hosting
                portalHost = new ServiceHost(typeof(Portal));
                StartServiceHost(portalHost);
            }
            catch (TargetInvocationException tiEx)
            {
                Console.WriteLine("Error occurred " + tiEx.Message);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error occurred " + ex.Message);
            }
        }

        ....
        ....

        private static void StartServiceHost(ServiceHost serviceHost)
        {
            try
            {
                // We will recreate the service host here to be sure we don't have a 
                //service in a faulted state
                serviceHost = new ServiceHost(serviceHost.Description.ServiceType);
                Console.WriteLine("Attempting to open Service.Portal service.");
                serviceHost.Open();
                serviceHost.Faulted += new EventHandler(ServiceHost_Faulted);
            }
              ....
              ....
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }
        ....
        ....

    }
}

Also, part of the PortalHost project is a custom installer that looks like the following:

C#
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration.Install;
using System.Linq;
using System.ServiceProcess.Design;
using System.ServiceProcess;
using System.Windows.Forms;


namespace PortalHost
{
    [RunInstaller(true)]
    public partial class ProjectInstaller : Installer
    {
        public ProjectInstaller()
        {
            InitializeComponent();
        }

        // Prompt the user for service installation account values.
        public static bool GetServiceAccount(ref ServiceProcessInstaller svcInst)
        {
            bool accountSet = false;
            ServiceInstallerDialog svcDialog = new ServiceInstallerDialog();
            //Use the ServiceInstallerDialog to customise the username and password for
            //the installation process
            ....
            ....

            return accountSet;
        }

    }
}

Where we are using the following support classes ServiceProcessInstaller/ServiceInstaller and a ServiceInstallerDialog (to capture the username and password during the installation).

Handling a Faulted WCF Channel Within the Windows Service

When dealing with WCF, the last thing one wants to hear about is a faulted channel; this is incredibly bad news. Basically, a faulted channel is beyond useless. What can we do about this situation within a hosting Windows Service? Well, quite simply, what we do is catch the ServiceHost.Faulted exception, which WCF allows us to do (thank god), and stop/restart the WCF Service. This is demonstrated below:

C#
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.ServiceModel;
using System.Reflection;
using Service.Portal;
using System.ServiceModel.Description;

namespace PortalHost
{
    public partial class Service : ServiceBase
    {
        private static ServiceHost portalHost;

        public Service()
        {
            
        }

        protected override void OnStart(string[] args)
        {
            Service.StartService();
        }

        public static void StartService()
        {
            ....
            ....
        }

        private static void StartServiceHost(ServiceHost serviceHost)
        {
             ....
            ....
        }

        private static void StopServiceHost(ServiceHost serviceHost)
        {
            ....
            ....
        }

        private static void RestartServiceHost(ServiceHost serviceHost)
        {
            StopServiceHost(serviceHost);
            StartServiceHost(serviceHost);
        }


        private static void ServiceHost_Faulted(Object sender, EventArgs e)
        {
            ServiceHost serviceHost = sender as ServiceHost;
            Console.Write(String.Format("{0} Faulted. Attempting Restart.", 
                serviceHost.Description.Name));
            RestartServiceHost(serviceHost);
        }
    }
}

Installation

As previously stated, the attached demo WCF Service is actually hosted inside a Windows Service, and as most of us should know, a Windows Service runs under a particular user account. But how does one install a Windows Service?

The attached demo code has an installer that deals with installing the Windows Service that hosts the WCF Service. As part of this installation process, you will be prompted for login credentials for the service.

Using InstallUtil.exe

A Windows Service can be installed using the InstallUtil.exe command line util. In order to install the attached Portal service, you will need something like:

installutil.exe C:\Users\sacha\Desktop\Linq2WCF_NEW\
                   Linq2WCF\Linq2WCF\PortalHost\bin\Release\PortalHost.exe

Which when run will have a dialog where you can put in the account details for the Windows Service to use.

Using an MSI

The attached demo solution actually has an installer entitled "PortalMSIInstaller", which is a typical installer/setup project that simply uses the output of the Windows Service project "PortalHost". The only thing to not using this project is that the service is installed and must then be configured to use the correct login credentials, as these are not provided as part of the installation process. The MSI also uses ServiceInstallerDialog, but this window is sometimes pushed to the back, so you may have to use Task Manager to find it as part of the installation process.

The ServiceInstallerDialog code that is used within the custom installer for the service, that is used to show the service login screen below, is as follows:

C#
// Prompt the user for service installation account values.
public static bool GetServiceAccount(ref ServiceProcessInstaller svcInst)
{
    bool accountSet = false;
    ServiceInstallerDialog svcDialog = new ServiceInstallerDialog();

    // Query the user for the service account type.
    do
    {
        svcDialog.TopMost = true;
        svcDialog.StartPosition = FormStartPosition.CenterScreen;
        svcDialog.ShowDialog();

        if (svcDialog.Result == ServiceInstallerDialogResult.OK)
        {
            // Do a very simple validation on the user
            // input. Check to see whether the user name
            // or password is blank.

            if ((svcDialog.Username.Length > 0) &&
                (svcDialog.Password.Length > 0))
            {
                // Use the account and password.
                accountSet = true;

                svcInst.Account = ServiceAccount.User;
                svcInst.Username = svcDialog.Username;
                svcInst.Password = svcDialog.Password;
            }
        }
        else if (svcDialog.Result == ServiceInstallerDialogResult.UseSystem)
        {
            svcInst.Account = ServiceAccount.LocalSystem;
            svcInst.Username = null;
            svcInst.Password = null;
            accountSet = true;
        }

        if (!accountSet)
        {
            // Display a message box. Tell the user to
            // enter a valid user and password, or cancel
            // out to leave the service account alone.
            DialogResult result;
            result = MessageBox.Show(
                "Invalid user name or password for service installation." +
                "  Press Cancel to leave the service account unchanged.",
                "Change Service Account",
                MessageBoxButtons.OKCancel,
                MessageBoxIcon.Hand);

            if (result == DialogResult.Cancel)
            {
                // Break out of loop.
                break;
            }
        }
    } while (!accountSet);

    return accountSet;
}

When the installer is run, it will allow you to enter the service login credentials to run under, using the following dialog box:

Image 12

Once the installer has been run, you will need to change the Config file to point to your own SQL Server installation. The default config file is the name of the output project used by the installer project, where the config file will be something like "PortalHost.exe.config", and you should make sure to change the <connectionStrings> section.

I'll leave it as an exercise to the reader to have a look at the "PortalMSIInstaller" project; it's all fairly standard stuff.

Starting the Windows Service (That Hosts the WCF Service)

Once the service is installed, you will need to start it. This is easily done using the Services Manager.

Image 13

WPF Client

The attached "WPFClient" project carries out the following functions:

  • Provides a WCF setup diagnostics screen, which we have found quite invaluable at work, as we can get non-techie users to print a screen of their setup without the need to have to locate and open an App.Config file.
  • Provides a 3D carousel view over a list of LINQ to Entities retrieved Customer objects.
  • Provides a list of LINQ to Entities retrieved Order objects for the current Customer.
  • Provides a search builder screen that allows the user to build up dynamic queries in order to query the LINQ to Entities layer via a WCF Request.

I shall tackle each of these areas in turn in the rest of the sections below.

Basic WPFClient Project Setup

Before we start to get into the code, I would like to talk about the rough structure of the application.

Windows

There are two windows: a diagnostics window and a main window. The main window hosts a 3D carousel and both a CurrentCustomerControl and a CurrentCustomerOrdersControl control.

UserControls

  • CurrentCustomerControl: Is a pretty simple user control that shows a reflected image and the currently selected Northwind.Customers name.
  • CurrentCustomerOrdersControl: Shows a list of the currently selected Northwind.Customers.Orders in an ItemsControl. There are three different views for showing more or less information about the bound data to the user.
  • SearchControl: Holds n-many SearchClauseControls.
  • SearchClauseControl: Allows a search clause to be built up for a single property of the bound object.

ViewModels

The attached code uses the MVVM pattern, for the main window (CustomerOrdersViewModel) and the SearchClauseControl (SearchClauseViewModel).

Rest of the Codebase

As for the rest of the code, I think it's all pretty standard WPF stuff, like Attached Properties / Styles / Commands etc.

Making Sure You Have the Correct User, For the WCF Service Calls

In order to correctly use the WCF Service being hosted in the Windows Service (assuming it is installed and running), you will need to supply login credentials; this is done via the identity\userPrincipalName section of the App.Config file for the "WPFClient" project in the attached code.

XML
<identity>
  <userPrincipalName value="YOUR PC\YOUR USER" />
</identity>

WCF Setup Diagnostics

I think any help that you can give end users to aid in problems that they may encounter is a good thing.

It should also be remembered that some users may not actually be technically minded, so would not relish the thought of trawling around for an App.Config file and having to look at XML; some I vouch would not even know what XML is. So protecting users from that is pretty necessary in my opinion.

To this end, I have created a small diagnostics screen that allows a user to view diagnostic information about the WCF configuration associated with the given Client App.Config file. The following code applies equally to a WPF/WinForms or a console app that consumes a WCF Service.

The code basically uses some of the Configuration based classes found in the System.ServiceModel.Configuration namespace, along with a little Reflection to search the App.Config file for all relevant WCF elements and to use this to display in a diagnostic window.

The main code is shown below:

C#
/// <summary>
/// Gets client EndPoint information out of the attached App.Config
/// </summary>
public List<String> EndPoints
{
    get
    {
        // Automatically find all client endpoints and related 
        //bindings defined in app.config
        List<String> endpointNames = new List<String>();

        try
        {
            BindingsSection bindingsSection =
                ConfigurationManager.GetSection(
                "system.serviceModel/bindings") as BindingsSection;

            if (bindingsSection == null)
            {
                Console.WriteLine(
                    "The configuration file doesn't contain " +
                    "a system.serviceModel/bindings configuration section");
            }
            else
            {
                endpointNames.Add("BINDINGS");
                foreach (BindingCollectionElement bce in 
                    bindingsSection.BindingCollections)
                {
                    AnalyseBinding(endpointNames, bce);
                }
            }

            ClientSection clientSection =
                ConfigurationManager.GetSection(
                "system.serviceModel/client") as ClientSection;

            if (clientSection == null)
            {
                Console.WriteLine("The configuration file doesn't " + 
                    "contain a system.serviceModel/client configuration section");
            }
            else
            {
                ChannelEndpointElementCollection endpointCollection =
                    clientSection.ElementInformation.
                    Properties[String.Empty].Value as
                    ChannelEndpointElementCollection;

                endpointNames.Add(Environment.NewLine);
                endpointNames.Add("ENDPOINTS");
                foreach (ChannelEndpointElement 
                    endpointElement in endpointCollection)
                {
                    GetPropetiesFromType(endpointNames,
                        endpointElement.GetType(), endpointElement);
                }                        
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);

        }
        return endpointNames;
    }
}

And here are two small helper methods that are used to obtain property values:

C#
private void AnalyseBinding(List<string> endpointNames, 
             BindingCollectionElement bce)
{
    foreach (IBindingConfigurationElement be in bce.ConfiguredBindings)
    {
        GetPropetiesFromType(endpointNames, be.GetType(), be);
    }
}

private void GetPropetiesFromType(List<string> endpointNames, 
                                  Type type, Object source)
{
    PropertyInfo[] pis = type.GetProperties();
    if (pis.Length > 0)
    {
        foreach (PropertyInfo pi in pis)
        {
            bool foundSysName = false;
            String currentValue = pi.GetValue(source, null).ToString();
            if (currentValue.Contains("System.Configuration"))
                return;

            if (currentValue.StartsWith("System.ServiceModel"))
            {
                Object o = pi.GetValue(source, null);
                foundSysName = true;
                GetPropetiesFromType(endpointNames, o.GetType(), o);

            }
            if (!foundSysName)
                endpointNames.Add(String.Format("{0} - {1}",
                    pi.Name, currentValue));
            else
                endpointNames.Add(String.Format("{0} - {1}",
                    pi.Name, "<SEE ABOVE>"));
        }
        endpointNames.Add(Environment.NewLine);
    }
}

3D Carousel

Image 14

For the 3D carousel, I am using the truly excellent ElementFlow that is part of the FluidKit CodePlex contribution by Pavan Podila. This is an excellent WPF control that I have toyed with creating myself in the past. When I found Pavan's code, I basically gave up as it's so cool. It acts just like a regular panel, which means you can use it inside ListBoxes, ItemsControls, instead of the normal panels these controls use. Pavan has done a bang up job, and this ElementFlow comes with the following features:

  • SelectedItem
  • Reflection On|Off
  • Many, many different views that are part of the standard control, that you can see in action using the F12 key. I am only using the following views, but there are others:
    • CoverFlow
    • Carousel
    • RollerCoaster
    • Rolodex

The 3D carousel is initially loaded with a list of 15 Northwind.Customers where the Customer must have more than two Orders. This is done by using the WCF CustomerRequest, which is done on initial application startup.

Customers.Orders Lists

Image 15

From the 3D Carousel that is used to hold a list of Northwind.Customers with Orders, the selected Customer is used to bind against a DP within the CurrentCustomerOrdersControl control. And from inside the CurrentCustomerOrdersControl control, a CustomerOrders DP is set with the current Northwind.Customers.Orders. This is shown below:

C#
/// <summary>
/// CustomerOrders Dependency Property
/// </summary>
public static readonly DependencyProperty CustomerOrdersProperty =
    DependencyProperty.Register("CustomerOrders", typeof(List<Orders>), 
    typeof(CurrentCustomerOrdersControl),
        new FrameworkPropertyMetadata(null,
            new PropertyChangedCallback(OnCustomerOrdersChanged)));

/// <summary>
/// Gets or sets the CustomerOrders property.
/// </summary>
public List<Orders> CustomerOrders
{
    get { return (List<Orders>)GetValue(CustomerOrdersProperty); }
    set { SetValue(CustomerOrdersProperty, value); }
}

/// <summary>
/// Handles changes to the CustomerOrders property.
/// </summary>
private static void OnCustomerOrdersChanged(DependencyObject d,
    DependencyPropertyChangedEventArgs e)
{
    List<Orders> orders = e.NewValue as List<Orders>;

    if (orders != null)
        ((CurrentCustomerOrdersControl)d).lstOrders.ItemsSource = orders;
}

/// <summary>
/// BoundCustomer Dependency Property
/// </summary>
public static readonly DependencyProperty BoundCustomerProperty =
    DependencyProperty.Register("BoundCustomer", typeof(Customers), 
        typeof(CurrentCustomerOrdersControl),
        new FrameworkPropertyMetadata(null,
            new PropertyChangedCallback(OnBoundCustomerChanged)));

/// <summary>
/// Gets or sets the BoundCustomer property.
/// </summary>
public Customers BoundCustomer
{
    get { return (Customers)GetValue(BoundCustomerProperty); }
    set { SetValue(BoundCustomerProperty, value); }
}

/// <summary>
/// Handles changes to the BoundCustomer property.
/// </summary>
private static void OnBoundCustomerChanged(DependencyObject d, 
    DependencyPropertyChangedEventArgs e)
{
    Customers customer = e.NewValue as Customers;

    if (customer !=null)
        ((CurrentCustomerOrdersControl) d).CustomerOrders = customer.Orders;
}

Once the Northwind.Customers.Orders are obtained, they are used as a source for the embedded ItemsControl, within the CurrentCustomerOrdersControl.

There are also three buttons on the CurrentCustomerOrdersControl that can be used to change the current DataTemplate that is applied to the ItemsControl.

Search Builder

Right at the beginning of this article, I stated that the attached code uses the LINQ to Entities framework, and that we would be writing dynamic queries in the UI that are then sent to the WCF Service and would be used to query the LINQ to Entities framework.

This is done in the UI using two user controls.

SearchClauseControl

Image 16

This is a simple control that takes a Type and then uses Reflection to build up various properties / allowable values that the user can pick based on the currently selected Types property type. Most of the logic associated with this control is done via the SearchClauseViewModel. The SearchClauseControl is also called by the parent SearchControl in order for the current search clause query to be added to any other search clause query.

The property that provides the actual query string for a SearchClauseControl single is as follows:

C#
public String ClauseResult
{
    get
    {
        StringBuilder sb = new StringBuilder(1000);

        if (!IsFirst)
            sb.Append(String.Format("{0} ", currentOperator));

        if (IsCollectionProperty)
            sb.Append(String.Format("{0}.Count ", currentProperty.Name));
        else
        {
            if (IsString)
                sb.Append(String.Format("{0}.", currentProperty.Name));
            else
                sb.Append(String.Format("{0} ", currentProperty.Name));
        }

        if (IsOperatorComparable)
        {
            sb.Append(String.Format("{0} ", currentCondition));
            sb.Append(String.Format("@{0} ", ParameterNumber));
        }

        if (IsString)
        {
            sb.Append(String.Format("{0}(@{1})",
                currentCondition,
                ParameterNumber));
        }

        return sb.ToString();
    }
}

SearchControl

Is basically a container that hosts a number of SearchClauseControl controls. The only thing that the SearchControl does is build up a dynamic query string based on the current configuration of all its contained SearchClauseControl controls. When a dynamic query string is created, it is sent (via the SearchRequestedEvent routed event) to the main window, which in turn creates a CustomerSearchRequest query and runs the query.

Image 17

What actually happens when the SearchControl.SearchRequested routed event is seen in the main window (CustomOrdersWindow) is that the SearchControl.SearchRequested event is subscribed to and then the main window's View Model (CustomerOrdersViewModel) SearchCustomerWithOrders() method is called.

This method is shown below:

C#
public void SearchCustomerWithOrders(String whereClause, List<Object> searchParameters)
{
    try
    {
        Service<IPortal>.Use((client) =>
        {
            using (TransactionScope scope = new TransactionScope())
            {
                CustomerSearchRequest request = new CustomerSearchRequest();
                request.WhereClause = whereClause;
                request.SearchParameters = searchParameters;

                CustomerResponse response =
                    (CustomerResponse)client.ExecuteRequest(request);
                if (response.Customers != null)
                    CurrentCustomers =
                        new ObservableCollection<Customers>(response.Customers);

                if (CurrentCustomers.Count > 0)
                    SelectedCustomer = CurrentCustomers[0];

                //complete the Transaction
                scope.Complete();
            }
        });
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

What is actually happening here is that we are using the static method of the Service<T> class, which is a very cool class indeed. Let's see it.

C#
using System.ServiceModel;
using System;

namespace WpfClient.ServiceProxy
{
    /// Service client delegate
    public delegate void UseServiceDelegate<T>(T proxy); 

    /// <summary>
    /// A helper class to run code using a WCF client proxy
    /// 
    /// This weird class came from 
    /// http://www.iserviceoriented.com/blog/post/Indisposable+-+WCF+Gotcha+1.aspx
    /// </summary>
    public static class Service<T>
    {
        public static ChannelFactory<T> _channelFactory = 
            new ChannelFactory<T>("PortalServiceTcp");

        /// <summary>
        /// Creates the WCF service proxy and runs the codeBlock delegate
        /// using the WCF service proxy that is created
        /// </summary>
        /// <param name="codeBlock">The code to run using the WCF Proxy</param>
        public static void Use(UseServiceDelegate<T> codeBlock)
        {
            IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel(); 
            bool success = false; 
            try 
            { 
                codeBlock((T)proxy); 
                proxy.Close(); 
                success = true; 
            }
            catch (FaultException<ApplicationException> dex)
            {
                Console.WriteLine("Client FaultException<ApplicationException>");
            }
            catch (FaultException<ArgumentException> aex)
            {
                Console.WriteLine("Client FaultException<ArgumentException>");
            }
            catch (FaultException<Exception> ex)
            {
                Console.WriteLine("Client FaultException<Exception>");
            }
            catch (FaultException fEx)
            {
                Console.WriteLine("Client FaultException");
            }
            finally
            {
                if (!success)
                {
                    proxy.Abort();
                }
            }
        }
    }
}

It can be seen that the method has the following signature: Service<T>.Use(UseServiceDelegate<T> codeBlock); so what essentially happens is that a new WCF proxy is created, and then the delegate that was passed into the Service<T>.Use method is called where the newly created WCF proxy class is passed as a parameter to the original delegate (lambda from the (client) => { ...} within the CustomerOrdersViewModel.SearchCustomerWithOrders code previously shown).

Pretty tidy, huh? This is how all the calls to the WCF Service are done inside the attached WPFClient code.

So now that we have an actual WCF proxy, the lamda within the CustomerOrdersViewModel.SearchCustomerWithOrders() is run, which creates a new CustomerSearchRequest, which is then sent to the WCF service. Let's examine the CustomerSearchRequest and see how that works.

C#
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Linq.Expressions;
using System.Linq;

//Linq2Entities
using LINQ2Entities;
using System.Data.Objects;
using System.Linq.Dynamic;

namespace Service.Portal
{
    [DataContract]
    public class CustomerSearchRequest : Request
    {
        [DataMember]
        public List<Object> SearchParameters { get; set; }

        [DataMember]
        public String WhereClause { get; set; }

        public override Response CreateResponse(Object requestResults)
        {
            CustomerResponse response = new CustomerResponse();
            response.Customers = requestResults as List<Customers>;
            return response;
        }

        public override Object Execute()
        {
            NorthwindEntities context = new NorthwindEntities();

            List<Customers> custs =
                context.Customers.Include("Orders")
                .Where(this.WhereClause, 
                this.SearchParameters.ToArray()).ToList();

            return custs;
        }
        #endregion
    }
}

Thanks to the lovely Extension Methods provided by the DynamicQuery API discussed in the LINQ to Entities section, it becomes almost trivial to conduct a dynamic query; we simply include a Where clause and use the query string that was created in the WPF client along with the associated parameter values also passed into the WCF call from the WPF client.

That's it

That is about all I wanted to say, but if you liked this article, please leave a vote and a comment, thanks.

Special Thanks

I would just like to say thanks to my really bright and cool team at work where I am at the moment, that have made me learn more about WCF, and have provided me with answers to certain WCF issues. Thanks guys, you know who you are.

Bibliography

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
GeneralGreat Article! Pin
Espiritu John18-Jun-09 23:35
professionalEspiritu John18-Jun-09 23:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.