Hosting and consuming WCF services without configuration files

Posted by martinsj on Geeks with Blogs See other posts from Geeks with Blogs or by martinsj
Published on Sun, 14 Oct 2012 14:58:00 GMT Indexed on 2012/10/14 21:38 UTC
Read the original article Hit count: 307

Filed under:

In this post, I'll demonstrate how to configure both the host and the client in code without the need for configuring services i the <system.serviceModel> section of the config-file. In fact, you don't need a  <system.serviceModel> section at all. What you'll do need (and want) sometimes, is the Uri of the service in the configuration file. Configuring the Uri of the the service is actually only needed for the client or when self-hosting, not when hosting in IIS.

So, exactly What do we need to configure?

  • The binding type and the binding constraints
  • The metadata behavior
  • Debug behavior

You can of course configure even more, and even more if you want to, WCF is after all the king of configuration…

As an example I'll be hosting and consuming a service that removes most of the default constraints for WCF-services, using a BasicHttpBinding. Of course, in regards to security, it is probably better to have some constraints on the server, but this is only a demonstration.

The ServerConfig class in the code beneath is a static helper class that will be used in the examples. In this post, I’ll be using this helper-class for all configuration, for both the server and the client. In WCF, the  client and the server have both their own WCF-configuration. With this piece of code, they will be sharing the same configuration.


   1: public static class ServiceConfig
   2: {
   3:     public static Binding DefaultBinding
   4:     {
   5:         get
   6:         {
   7:                 var binding = new BasicHttpBinding();
   8:                 Configure(binding);
   9:                 return binding;
  10:             } 
  11:         } 
  12:  
  13:         public static void Configure(HttpBindingBase binding)
  14:         {
  15:             if (binding == null)
  16:             {
  17:                 throw new ArgumentException("Argument 'binding' cannot be null. Cannot configure binding.");
  18:             }
  19:  
  20:             binding.SendTimeout = new TimeSpan(0, 0, 30, 0); // 30 minute timeout
  21:             binding.MaxBufferSize = Int32.MaxValue;
  22:             binding.MaxBufferPoolSize = 2147483647;
  23:             binding.MaxReceivedMessageSize = Int32.MaxValue;
  24:             binding.ReaderQuotas.MaxArrayLength = Int32.MaxValue;
  25:             binding.ReaderQuotas.MaxBytesPerRead = Int32.MaxValue;
  26:             binding.ReaderQuotas.MaxDepth = Int32.MaxValue;
  27:             binding.ReaderQuotas.MaxNameTableCharCount = Int32.MaxValue;
  28:             binding.ReaderQuotas.MaxStringContentLength = Int32.MaxValue;
  29:         }
  30:  
  31:         public static ServiceMetadataBehavior ServiceMetadataBehavior
  32:         {
  33:             get
  34:             {
  35:                 return new ServiceMetadataBehavior
  36:                 {
  37:                     HttpGetEnabled = true, 
  38:                     MetadataExporter = {PolicyVersion = PolicyVersion.Policy15}
  39:                 };
  40:             }
  41:         }
  42:  
  43:         public static ServiceDebugBehavior ServiceDebugBehavior
  44:         {
  45:             get
  46:             {
  47:                 var smb = new ServiceDebugBehavior();
  48:                 Configure(smb);
  49:                 return smb;
  50:             }
  51:         }
  52:  
  53:  
  54:         public static void Configure(ServiceDebugBehavior behavior)
  55:         {
  56:             if (behavior == null)
  57:             {
  58:                 throw new ArgumentException("Argument 'behavior' cannot be null. Cannot configure debug behavior.");
  59:             }
  60:             
  61:             behavior.IncludeExceptionDetailInFaults = true;
  62:         }
  63: }

Configuring the server

There are basically two ways to host a WCF service, in IIS and self-hosting. When hosting a WCF service in a production environment using SOA architecture, you'll be most likely hosting it in IIS. When testing the service in integration tests, it's very handy to be able to self-host services in the unit-tests. In fact, you can share the the WCF configuration for self-hosted services and services hosted in IIS. And that is exactly what you want to do, testing the same configurations for test and production environments.

 

Configuring when Self-hosting

When self-hosting, in order to start the service, you'll have to instantiate the ServiceHost class, configure the  service and open it.

   1: // Create the service-host.
   2: var host = new ServiceHost(typeof(MyService), endpoint);
   3:  
   4: // Configure the binding    
   5: host.AddServiceEndpoint(typeof(IMyService), ServiceConfig.DefaultBinding, endpoint);
   6:  
   7: // Configure metadata behavior
   8: host.Description.Behaviors.Add(ServiceConfig.ServiceMetadataBehavior);
   9:  
  10: // Configure debgug behavior
  11: ServiceConfig.Configure((ServiceDebugBehavior)host.Description.Behaviors[typeof(ServiceDebugBehavior)]);
  12:     
  13: // Start listening to the service
  14: host.Open();
  15:  

Configuring when hosting in IIS

When you create a WCF service application with the wizard in Visual Studio, you'll end up with bits and pieces of code in order to get the service running:

  • Svc-file with codebehind.
  • A interface to the service
  • Web.config

In order to get rid of the configuration in the <system.serviceModel> section, which the wizard has generated for us, we must tell the service that we have a factory that will create the service for us. We do this by changing the markup for the svc-file:

   1: <%@ ServiceHost Language="C#" Debug="true" Service="Namespace.MyService" Factory="Namespace.ServiceHostFactory" %>

The markup tells IIS that we have a factory called ServiceHostFactory for this service.

The service factory has a method we can override which will be called when someone asks IIS for the service. There are overloads we can override:

   1: System.ServiceModel.ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
   2: System.ServiceModel.ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
   3:  

In this example, we'll be using the last one, so our implementation looks like this:

   1: public class ServiceHostFactory : System.ServiceModel.Activation.ServiceHostFactory
   2:     {
   3:  
   4:         protected override System.ServiceModel.ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
   5:         {
   6:             var host = base.CreateServiceHost(serviceType, baseAddresses);
   7:             host.Description.Behaviors.Add(ServiceConfig.ServiceMetadataBehavior);
   8:             ServiceConfig.Configure((ServiceDebugBehavior)host.Description.Behaviors[typeof(ServiceDebugBehavior)]);
   9:             return host;
  10:         }
  11:     }
  12:  
   1: public class ServiceHostFactory : System.ServiceModel.Activation.ServiceHostFactory
   2:     {
   3:  
   4:         protected override System.ServiceModel.ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
   5:         {
   6:             var host = base.CreateServiceHost(serviceType, baseAddresses);
   7:             host.Description.Behaviors.Add(ServiceConfig.ServiceMetadataBehavior);
   8:             ServiceConfig.Configure((ServiceDebugBehavior)host.Description.Behaviors[typeof(ServiceDebugBehavior)]);
   9:             return host;
  10:         }
  11:     }
  12:  


As you can see, we are using the same configuration helper we used when self-hosting. Now, when you have a factory, the <system.serviceModel> section of the configuration can be removed, because the section will be ignored when the service has a custom factory. If you want to configure something else in the config-file, one could configure in some other section.

 

Configuring the client

Microsoft has helpfully created a ChannelFactory class in order to create a proxy client. When using this approach, you don't have generate those awfull proxy classes for the client. If you share the contracts with the server in it's own assembly like in the layer diagram under, you can share the same piece of code. The contracts in WCF are the interface to the service and if any, the datacontracts (custom types) the service depends on.

Capture


Using the ChannelFactory with our configuration helper-class is very simple:

   1: var identity = EndpointIdentity.CreateDnsIdentity("localhost");
   2: var endpointAddress = new EndpointAddress(endPoint, identity);
   3: var factory = new ChannelFactory<IMyService>(DeployServiceConfig.DefaultBinding, endpointAddress);
   4: using (var myService = new factory.CreateChannel())
   5: {
   6:     myService.Hello();
   7: }
   8: factory.Close();

 

Happy configuration!

© Geeks with Blogs or respective owner