IMPORTANT: It's really important that you read this post before reading Part 2. I'll assume you did, so a lot might not make any sense unless you've read it.
Introduction
Ok, in last post we decided on the following desired features for our WCF Services:
- No more Service References and we'll share contract assemblies between client and host.
- No more funky XML Config files that span multiple screens.
- Calling services should be simple and straightforward, with as little configuration as possible.
- Hosting the services should be easy and require as little configuration as possible.
We also decided on some assumptions that need to be true in order for our scenario to work in the way we intended:
- Well-defined policies on communication between services.
- Well-defined boundaries between services.
- Contracts shared between client and host (and versioned).
We have some degree of variability for those constraints in our solution, but for the rest of this post I'll assume them to be true.
Service Infrastructure
First thing we'll do is create a service infrastructure project in our solution to hold all our infrastructure code (the actual purpose for this post).
I'll call it Stormwind.EasyWcf.ServiceInfrastructure, and add it as a Class Library.
So, without further ado let's create our WCF Service.
MathService Host
We'll start with the contract for our service. This will be a very simple contract with just one operation that goes like this:
| 1: namespace Stormwind.EasyWcf.Contracts 2: { 3: [ServiceContract] 4: public interface IMathService 5: { 6: [OperationContract] 7: int Add(int value1, int value2); 8: } 9: } |
That is fairly common WCF stuff, right? We'll add that class to our Stormwind.EasyWcf.Contracts assembly. This is a new project which we'll add to our solution, and is responsible for holding all our contracts. This is the "currency" we'll use when having clients talking to hosts.
Now we just need to implement that contract. There are two approaches here. One is creating the implementation for it in the Web App that hosts the service. The other approach is to have an assembly of service implementation that you can version independently of the web site. I like the later one better than the former.
So we'll just create another class library called Stormwind.EasyWcf.Implementation, and add to it our MathService class, like this:
|
1: using WCFFacilityTest.Contracts; 2: 3: namespace Stormwind.EasyWcf.Implementation 4: { 5: public class MathService : IMathService 6: { 7: public int Add(int value1, int value2) 8: { 9: return value1 + value2; 10: } 11: } 12: } |
Ok, again pretty straightforward. Now we have both our contract and our implementation. All that's left is hosting the service. That's should be easy, right? Well, after we get the infrastructure out of our way it actually is.
WCF can automatically host the services in IIS for you provided you supply it with some configurations (config files) and a service file (*.svc) so that it can serve as a hook for IIS to call on the ASPNET_Isapi.dll and then delegate the call to WCF.
That's all fine and dandy IF you want to use config files, which is not our case. Fortunately for us, there is another way. WCF allows us to specify our own ServiceHost factory. What that means is that we can specify how our service is to be hosted, instead of relying in the default one.
In order to host our service using that approach we'll rely on our own implementation of the ServiceHostFactory class. This class is responsible for building our own ServiceHost via the CreateServiceHost method. This method gets a service and a bunch of urls. We're only interested in the service. We'll add a class called DefaultServiceHostFactory to our ServiceInfrastructure project like this one:
namespace Stormwind.EasyWcf.ServiceInfrastructure
{ public class DefaultServiceHostFactory : ServiceHostFactory
{ public override ServiceHostBase CreateServiceHost(string service, Uri[] baseAddresses)
{ var implementationType = Type.GetType(service);
var contractType = GetContractFrom(implementationType);
var serviceHost = new ServiceHost(implementationType);
serviceHost.AddServiceEndpoint(contractType,
new BasicHttpBinding(), UrlHelper.GetUrlFor(contractType));
return serviceHost;
}
private static Type GetContractFrom(Type serviceType)
{ var contract = serviceType.GetInterfaces().Where(type => type.IsServiceContract()).SingleOrDefault();
if (contract != null)
return contract;
throw new InvalidOperationException(string.Format("The type {0} is not a service. Try using the ServiceContractAttribute.", serviceType));
}
}
}
Let's take it slow. Before I start commenting on the code above, let me show you how we would use that in a MathService.svc file:
<%@ ServiceHost Language="C#" Service="Stormwind.EasyWcf.Implementation.MathService, Stormwind.EasyWcf.Implementation"
Factory="Stormwind.EasyWcf.ServiceInfrastructure.DefaultServiceHostFactory, Stormwind.EasyWcf.ServiceInfrastructure" %>
So in the service declaration you can see that we're specifying the ServiceHost factory to use for this service. This is how WCF knows how to build the service host for this service.
If we go back to the DefaultServiceHostFactory class, you'll see that the CreateServiceHost method gets the service that is trying to use this factory as a parameter. That string is the one supplied in the .svc file in the "Service" attribute, in our case "Stormwind.EasyWcf.Implementation.MathService, Stormwind.EasyWcf.Implementation".
From this string which is the full name for our service implementation we can get to a type, represented by the implementationType variable. From that we can get to the contract easily by inspecting the interfaces implemented by the "implementationType" type. The GetContractFrom method uses an extension method defined in the class below:
using System;
using System.ServiceModel;
namespace Stormwind.EasyWcf.ServiceInfrastructure
{ public static class TypeExtensions
{ public static bool IsServiceContract(this Type type)
{ var attributes = type.GetCustomAttributes(typeof(ServiceContractAttribute), true);
return attributes != null && attributes.Length != 0;
}
}
}
This class just defined a method that returns if some type contains the ServiceContractAttribute, which is the one that defines a WCF Service Contract.
After retrieving the ContractType, we can just define our ServiceHost. In this sample we're just using a BasicHttpBinding, but you can use any binding you want. We also applied a Convention-Over-Configurations concept that defines that we need an AppSettings value that defines the URL for the endpoint using "Contract Name" + Url, in our case "IMathServiceUrl". So after defining that key in our web.config file for the Web App, we are set. We have a ServiceHost that can handle requests properly.
If you want to try it out just attach your VS to ASP.Net and access the SVC file's url from your browser. Remember to put a breakpoint in the ServiceHostFactory to see how it is hit.
Conclusion
Oh no, not again!
Sorry, this is bigger than most people would read already. If you still feel like reading and would like to know how to USE this hosted service just go to Part 3.
Cheers.