![]() |
Platforms, Frameworks & Libraries »
Windows Presentation Foundation »
General
Intermediate
License: The Code Project Open License (CPOL)
Dynamic LINQ To Entities Queries Using WCF/WPF demo codeBy Sacha BarberDemonstrates a method of dynamic query across WCF service boundaries |
C# (C# 3.0), .NET (.NET 3.5), WCF, WPF, LINQ, Architect, DBA, Dev, Design
|
||||||||
|
Advanced Search |
|
|
|
||||||||||||||||
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.
You will need VS2008 (for the Entity Framework) and VS2008 SP1, and .NET 3.5 SP1 installed.
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
Just to get you a little bit more familiar with it all here is the project structure
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.
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 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.
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>>

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.
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; }
}
}
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.
"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.
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
}
}
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).
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);
}
}
}
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.
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.
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.
Once the service is installed you will need to start it. This is easily done using the services manager.

The attached "WPFClient" project carries out the following functions
I shall tackle each of these areas in turn in the rest of the sections below
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
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>
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);
}
}

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

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.
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 is about all I wanted to say, but if you liked this article please leave a vote and a comment, 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
How to: Filter Data (Entity Framework)
Dynamic LINQ (Part 1: Using the LINQ Dynamic Query Library)
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 30 Nov 2008 Editor: |
Copyright 2008 by Sacha Barber Everything else Copyright © CodeProject, 1999-2009 Web10 | Advertise on the Code Project |