5,783,659 members and growing! (16,905 online)
Email Password   helpLost your password?
Platforms, Frameworks & Libraries » Windows Presentation Foundation » General     Intermediate License: The Code Project Open License (CPOL)

Dynamic LINQ To Entities Queries Using WCF/WPF demo code

By Sacha Barber

Demonstrates a method of dynamic query across WCF service boundaries
C# (C# 3.0, C#), .NET (.NET, .NET 3.5), WCF, WPF, LINQ, Design, Architect, DBA, Dev

Posted: 30 Nov 2008
Updated: 30 Nov 2008
Views: 11,819
Bookmarked: 74 times
Announcements
Loading...



Search    
Advanced Search
Sitemap
56 votes for this Article.
Popularity: 8.45 Rating: 4.83 out of 5
0 votes, 0.0%
1
0 votes, 0.0%
2
4 votes, 7.1%
3
4 votes, 7.1%
4
48 votes, 85.7%
5
Note: This is an unedited contribution. If this article is inappropriate, needs attention or copies someone else's work without reference then please Report This Article

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.

So 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 that we will be using an ORM to obtain data from this database using a WCF service. The 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 for ever 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 an 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 for an excercise 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 are updated to point to your local SQL server installation
  3. That the windows service is running when you attempt ot use the 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

 

Show It

So what does it look like when running, well I decided to show you some screen shots and also have a link to a small video for this one.

Initial view loaded

Changing the type of view for the selected Customer Orders

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 are slowly sweeping (personal opinion) that one under the carpet, in favour of LINQ to Entities. So I thought why not use LINQ to Entities. So that's what this article uses for the ORM.

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

CSDL : Conceptual schema definition language (CSDL)

MSDL : Mapping specification language (MSL)

SSDL : Store schema definition language (SSDL)

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 1000nd lines for a small database. Eeek.

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

Mantaining these 3 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 cod behind for the designer is now a C#|VB class that has a App.Config file associated with it, with a very strange ConnectionStrings section. Let us have a look at one of these:

<?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 section Windows Service Hosting And Installation.

We can see that there are still links to the CSDL/MSL/SSDL, but there 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.

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

LINQ To Entities SQL Language

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

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 accross a service boundary, which AFAIK 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 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 definately 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 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 was a good thing after all. I mean what is to stop a malicous user from creating there own query string, the fact that it now uses the Entity Framework rather than the actual database doesn't really matter to the malicous 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 to 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 later 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

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

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 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 2 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>>

How These 2 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 actual SQL which can be used against the database.

 

Let's now see how the DynamicQuery API works. What it does it provide 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 :

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.

So 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.

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 is just a bit of standard OO polymorphism in play. Where we can store any sub class of Request in its base class. Ill cover this in a minute for now let's carry on examining the service, it can be seen that the single ExecuteRequest OperationContract it 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.

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.

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 sub classes, 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 2 sub classes 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 implement IExtensibleDataObject as follows:

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 a 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

If we 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.

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>. Where the FaultException<T> 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 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 trasactional 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.

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 seperate 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.

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 :

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:

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. So 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:

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. So 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 like the following 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 the 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:

      // 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;
        }

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

 

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 excercise to the reader to have a look at the "PortalMSIInstaller" project, its 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.

 

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 there setup, without the need to have to locate and open a App.Config file.
  • Provides a 3D carosel 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 dyanamic 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 2 windows, 1 diagnostics window and 1 main window. The main window hosts a 3D carosel and a 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 a ItemsControl. There are 3 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 is using 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 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.

<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 nessecary 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 using 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.

/// <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 is are 2 small helper methods that are used to obtain property values

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 Carosel

For the 3D carosel, 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 Pavans code, I basically gave up as its 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 carosel is initially loaded with a list of 15 Northwind.Customers where the Custom must have more than 2 Orders. This is done by using the WCF CustomerRequest, which is done on the initial application startup.

Customers.Orders lists

From the 3D Carosel 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

/// <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 of the embedded ItemsControl, within the CurrentCustomerOrdersControl.

There are also 3 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 used the LINQ to Entities framework, and that we would be writing dynamic queries in the UI that were then sent to the WCF service and would be used to query the LINQ to Entities framework.

This is done in the UI using 2 user controls.

SearchClauseControl

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:

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 a number of SearchClauseControl controls. The only thing that the SearchControl does is to build up a dynamic query string based on the current configuration of all its contained SearchClauseControl controls. When a dyanimic query string is created it is sent (via the SearchRequestedEvent routed event) to the main window who in turn creates a CustomerSearchRequest query and runs the query.

 

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 windows view model (CustomerOrdersViewModel) SearchCustomerWithOrders() method is called.

This method is shown below.

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 very cool class indeed. Lets see it.

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 essentaily 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 orginal 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. Lets examine the CustomerSearchRequest and see how that works.

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 simple 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

Entity Framework Tasks

How to: Filter Data (Entity Framework)

Dynamic LINQ (Part 1: Using the LINQ Dynamic Query Library)

Indisposable: WCF Gotcha #1

How to: Host a WCF Service in a Managed Windows Service

Pavan Podilas FluidKit WPF goodies

License

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

About the Author

Sacha Barber


Mvp
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)

Occupation: Software Developer (Senior)
Location: United Kingdom United Kingdom

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 25 of 45 (Total in Forum: 45) (Refresh)FirstPrevNext
Question