ChannelFactory.CreateChannel goes to the Gentlemen's Club
Currently, I'm tasked with getting some people up to speed on .NET 3.5 as well as WCF. WCF is really cool and it's easy to fall in love with, but it's also easy to cause you to sleep when you're first learning it. So instead of walking through Hello World, I changed course.
Basically, we're using an architecture where the Business Layer and Data Layer are services. In most cases, we'd run them on the same machine, in Proc. In other cases, when scalability is more of an issue, we'd need to separate them out.
Creating Services is nothing special and other than a few attributes (ServiceContract, OperationContract, often DataContract and/or DataMember or EnumMember) and an interface, there's no real way to tell a wcf service from any other class (and since most of those attributes should go on the interface, the class itself shows no sign of being a service other than the interface implementation). One thing though, when you add a WCF service to an application through VS, it will stick the service class and the corresponding interface into the same project. I typically move my interfaces out of the project and into a library so that I can share them. In a case where your business methods just call the dal's methods, the interfaces/contracts will be the same so you may be able to share them.
WCF gives you similar hosting options to Remoting and Web Services. You can add a Service Reference and have svcutil.exe generate a proxy class which is virtually identical to what you did with web services. Hosting them is the easy part and if you can run a Console application and remember to put Console.Readline at the end of it, you can start a service host. There's about a zillion examples out there and it's usually one of the first things covered in WCF so I'm leaving that alone for now.
On the client, you can add a service reference as I said. However that is not without its problems and you may want a little more flexibility. If you want to create your own proxies, there are two primary methods (outside of svcutil related approaches). Let's say you have the following interface:
namespace Cuckooz.Sample.WCFStripClub
{
[DataContract]
public enum BounceLevel{
[EnumMember]
Lame,
[EnumMember]
Woot,
[EnumMember]
YeahBaby
};
[DataContract]
public enum PoleManeuver{
[EnumMember]
Standard,
[EnumMember]
LookMaNoHands,
[EnumMember]
UpsideDownLookMaNoHands
};
[ServiceContract]
public interface IStripper
{
[OperationContract]
String Bounce(BounceLevel bouncin);
[OperationContract]
String SlideDownPoll(PoleManeuver maneuver);
[OperationContract]
long MilkSuckerForCash(String customerName);
}
}
One approach is to create your own Proxy class , inheriting from ClientBase and passing in the type of the interface, while implementing the interface. Pay attention to the implementations of this class:
namespace Cuckooz.Sample.WCFStripClub
{
public partial class PurePlatinumServiceClient : ClientBase<IStripper>, IStripper
{
public PurePlatinumServiceClient() { }
public PurePlatinumServiceClient(String endpointConfigurationName) : base(endpointConfigurationName) { }
public PurePlatinumServiceClient(String endpointConfigurationName, String remoteAddress) : base(endpointConfigurationName, remoteAddress) { }
public PurePlatinumServiceClient(Binding binding, EndpointAddress remoteAddress) : base(binding, remoteAddress) { }
public String Bounce(BounceLevel bouncin)
{
return base.Channel.Bounce(bouncin);
}
public String SlideDownPoll(PoleManeuver maneuver)
{
return base.Channel.SlideDownPoll(maneuver);
}
public long MilkSuckerForCash(String customerName)
{
return base.Channel.SlideDownPoll(customerName);
}
}
}
Note that b/c we are passing in the interface as a generic type parameter to ClientBase, we can refer to it through base.Channel.MethodName.
You will need to have an endpoint configured in order to call the service and you should give the endpoint a name to make things easy. In this case, I have a namedPipe endpoint configured named "NamedPipe". With that in place, you simply need to create an instance of your proxy and have at it:
PurePlatinumServiceClient dancerProxy = new PurePlatinumServiceClient("NamedPipe");
Console.WriteLine(dancerProxy.Bounce(BounceLevel.YeahBaby));
Console.WriteLine(dancerProxy.SlideDownPoll(PoleManeuver.UpsideDownLookMaNoHands));
Console.WriteLine(dancerProxy.MilkSuckerForCash("Peter Griffin"));
If you are going to create your proxies like this, do yourself a favor and use CodeSmith, some other template, or if you're just really hard up, copy and paste where applicable - it'll save you a lot of time.
If you don't want to go this route though, you can skip the whole proxy altogether. Using the ChannelFactory class, you can specify the same interface we used above and create a new ChannelFactory. Remember to name your endpoints in the configuration file so that you can refer to them. Otherwise, you'll need to specify the endpoint hard code style - which is lame. Once you've done that, you instantiate an IYourInterface object using the CreateChannel method of the ChannelFactory you just created. Then, you should cast it to an IDisposable object and throw it in a using block, and have at it from there. Note, you don't have to use IDispose but the other approach is to cast it as a ICommuncationObject and then call Close. For the money, it's easier and safer to just go the IDispose route. So the code below is functionally equivalent to the preceding block:
ChannelFactory<IStripper> stripperFactory = new ChannelFactory<IStripper>("NamedPipe");
IStripper serviceInstance = stripperFactory.CreateChannel();
using (serviceInstance as IDisposable)
{
Console.WriteLine(serviceInstance.Bounce(BounceLevel.Woot));
Console.WriteLine(serviceInstance.SlideDownPoll(PoleManeuver.LookMaNoHands));
Console.WriteLine(serviceInstance.MilkSuckerForCash("Peter Griffin"));
}
Either of these approaches provides a ton of flexibility. For instance, you can create several endpoints, look at the environment and then decide which endpoint you want to talk on, hence, which binding you are going to use. Similarly, if you had several services you could easily change the execution to use on over the other ones. All in all it really couldn't be simpler and in this case, the hardest part of the equation is going to be setting up your configuration file properly.
If I have time tomorrow I'll post the next lesson which uses Duplexing, there's a lot of potential with this interface and service when you enhance it with duplexing ;-)