|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
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
IntroductionIt 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. PreRequisitesYou will need VS2008 (for the Entity Framework) and VS2008 SP1, and .NET 3.5 SP1 installed. Things To Do To Get It Running For YouIn 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
Show ItSo 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 EntitiesFor 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 <?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 LanguageLINQ 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 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 ClausesLINQ to Entities actually offers a
According to the ObjectParameters
documentation the following should actually work. Unfortunately it doesn't seem
to like the 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 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
How These 2 APIs DifferAs just mentioned when using the native LINQ to Entites
Let's now see how the DynamicQuery
API works. What it does it provide an extension method against a 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 ServiceOverall StructureThe WCF service is actually pretty simple, the entire 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 Let's now have a look at the 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 And here is a specific namespace Service.Portal
{
[DataContract]
public class CustomerResponse : Response
{
[DataMember(IsRequired = true, Name = "Customers")]
public List<Customers> Customers { get; set; }
}
}
KnownTypesOne of the really cool things about WCF is that you can expose a type such
as Within the attached demo application there are only 2 sub classes of IExtensibleDataObject"The 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 In the attached demo code 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; }
}
}
}
Basically this is a good idea and one that I recommend that you all do. Fault ContractsIf we now visit the WCF service and have a look at the class that implements
the The WCF service is actually pretty simple, the entire 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 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 InstallationAs 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 Handling A Faulted WCF Channel Within The Windows ServiceWhen 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 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);
}
}
}
InstallationAs 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.exeA 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 MSIThe 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 The // 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 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 ClientThe attached "WPFClient" project carries out the following functions
I shall tackle each of these areas in turn in the rest of the sections below
Basic WPFClient Project SetupBefore 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
UserControls
ViewModels The attached code is using the MVVM pattern, for the main window (
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 CallsIn 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 DiagnosticsI 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
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
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 /// <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 There are also 3 buttons on the
Search BuilderRight 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 The property that provides the actual query string for a 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
What actually happens when the 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
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 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 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 ItThat is about all I wanted to say, but if you liked this article please leave a vote and a comment, thanks.
Special ThanksI 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 BibliographyHow to: Filter Data (Entity Framework) Dynamic LINQ (Part 1: Using the LINQ Dynamic Query Library) | ||||||||||||||||||||