In this part of the series on XmlHttp Service Interop we are going to extend the solution shown in part 1 by using the Windows Communication Foundation, or WCF, as the service. The scenario we will be looking at in this part may be a very common one if you are a heavy service user, but before we talk about interop with WCF, I want to talk in detail about about WCF itself. Specifically, I want to discuss manually setting up a WCF service.
WCF is Microsoft's communication unification framework that provides a consistent model for service implementation across various communication mechanisms in a way that unifies previous communication technologies. In human language, this means that you create web services the same way you create TCP services or other type of service.
One of the beautiful things about WCF is that you can take a previously existing API facade, tack a WCF interface on it, write a line in a hosting file someplace, and write a few elements in the application configuration file to create a fully functional service that is exposed as a webservice, TCP service, an IPC service, or other type of service. There's seriously very little work to provide solutions to many of the most common scenarios.
Our mission for this demonstration is to expose a method of an e-mail class as a WCF service, which we will then call via JavaScript. This is basically the same thing we did in part 1, but we will be doing it via WCF, thus simplying our service development and making the service more accessible to other clients.
To help things along, you are given a .NET class library called General. This is the same library I include in many of my projects to provide very basic functionality. This is the same General library showed in part 1 of this series. In addition to this, you can use most of the client-side code from part 1 as well. We will be changing some things in a bit, but most everything is the same.
Starting with a blank VS2005 add the General class library and add a new class library project. You can delete the default file in the project, but leave the Properties folder and the AssemblyInfo.cs file. We aren't going to use the WCF service template as I find it very important to demonstrate concepts like this by doing everything manually. I don't know about you, but I find it rediculous that many technology teachers today use GUI tools to teach programming. How in the world can you learn .NET data binding by dragging and dropping a SqlCommand and SqlDataAdapter to a screen? Lame.
After you added the new class library project, add a new class called "EmailService". This class will contain the implemenation logic of the service we are going to expose to the world. Here's the implemenation I'm using:
using System
using System.Collections.Generic;
using General.Mail;
using General.ExceptionHandling;
namespace Services
{
public class EmailService
{
public String Send(String from, String to, String cc, String bcc, String subject, DateTime datetime, String body, List<String> attachments) {
try {
Notification.Send(from, to, cc, bcc, subject, datetime, body, attachments);
return "0";
}
catch(Exception ex){
ExceptionManager.Report(ex);
return ex.Message;
}
}
}
}
This simply accepts some common e-mail properties and sends the message. If there is an error, the ExceptionMananger in the General library sends a notification to whomever is setup in the configuration file and the exception message is returned. If there is no exception, "0" is returned. Notice there is no manual XML here. Whereas in part 1 we used XPath to find the various parts of the incoming message, here we are simply focusing on the task at hand. All the XML processing is abstracted for us.
At this point we have a simple facade for use internally in .NET, but we want to use it as a WCF service. To do this we have to implement a WCF interface. The easiest way to explain this is to see how VS2005 does it for you and then just explain that. So, right-click on the name of the class and go to Refactor->Extract Interface. Leave the name as the default "IEmailService" and hit OK. Now open the IEmailService file and see what was created for you.
using System;
namespace Services
{
interface IEmailService
{
string Send(String from, String to, String cc, String bcc, String subject, DateTime datetime, String body, System.Collections.Generic.List< ;String> attachments);
}
}
As you can see, it's just a simple interface. You'll also notice that it was automatically implemented on your class.
The next thing we need to do is decorate the interface with two simple WCF attributes. You must add the "ServiceContract" attribute to the interface itself and the "OperationContract" to the interface method. In the process, you will find that you will need to add the " System.ServiceModel" namespace to your class aliases. The ServiceContract and OperationContract attributes are rather similiar to the WebService and WebMethod attributes used in ASMX, but these are obviously for WCF and are therefore more powerful..
Furthermore, just like in ASMX, it's an incredibly good idea to set a service namespace on the ServiceContract attribute. The service namespace effectively translates to an XML namespace as we will see a bit later. XML namespaces are similiar to .NET namespaces in that they allow for fully-qualified names and much like .NET namespaces, XML namespaces, or more specifically, service namespaces, follow specific guidelines. You should set a namespace that following the following pattern:
http://domain/fakepath/year/month
For example, the follow are possible service namespaces:
http://www.netfxharmonics.com/services/email/2007/03/ http://www.netfxharmonics.com/services/email/2007/04/ http://schemas.abccompany.com/orders/2006/11/
The year and month sections are very important as this is how services are typically versioned. Without this, there would be service-hell similiar to our old problem of DLL hell. Furthermore, looking that pattern, it's easy to say that service namespaces use these XML namespaces similar to how .NET uses four-part assembly names to allow for precise identitication.
Having added the two WCF attributes and set a service namespace, your interface should look something like this:
using System;
using System.Collections.Generic;
using System.ServiceModel ;
namespace Services
{
[ServiceContract(Namespace="http://www.netfxharmonics.com/services/email/2007/03/")]
interface IEmailService
{
[OperationContract]
String Send(String from, String to, String cc, String bcc, String subject, DateTime datetime, String body, List<string> attachments);
}
}
You now have a WCF service. Those two attributes are literally all that's required to create a service. Now, to actually expose the service we need two more things: hosting and configuration. Both of these steps are embarrasingly simple. In our situation hosting consists of creating a simple file in our website containing a single .NET directive and the configuration consists of a few elements in our web.config file.
As I've stated before, the client portion of this demo is basically the same as was seen in part 1, however, here we are using a WCF service instead of a manually created service. So, in our website we simply need to add a ".svc" file of any name, though we will be using " service.svc", and add the following directive in that file:
<%@ ServiceHost Service="Services.EmailService" %>
Now we have setup hosting. That's it. It's important to remember to use the ".svc" file extension as using a ".aspx" extension will cause an exception to be thrown regarding the then unknown "ServiceHost" directive. This is because the default machine.config sets a specific HttpHandler and BuildProvider for .aspx files. If for some reason you wanted to use ".aspx" for services, you could do this in your web.config file:
<system.web>
<httpHandlers>
<add path="*.aspx" verb="*" type="System.ServiceModel.Activation.HttpHandler, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" validate="false" />
</httpHandlers>
<compilation debug="false">
<buildProviders>
<remove extension=".aspx"/>
<add extension=".aspx" type=" System.ServiceModel.Activation.ServiceBuildProvider, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</buildProviders>
</compilation>
</system.web>
For our situation let's leave it as the default and use ".svc".
Now it's time to configure our WCF service. To do this, we simply have to add a few elements to the web.config file. Conceptually, all we need to do is setup a service with an endpoint consisting of a contract, binding and address declaration. That's all fancy way of saying that we need to tell ASP.NET what service we are exposing, how, and where. All that is stated in the following WCF configuration:
<system.serviceModel>
<services>
<service name=" Services.EmailService">
<endpoint address="" binding="basicHttpBinding" contract="Services.IEmailService"/>
</service>
</services>
</system.serviceModel>
As you can see the fully qualified class name is stated in the name attribute of the service property with the fully qualified interface name as the contract in the endpoint. Also notice that the address is blank. I'll discuss this in a bit. For now, take note of the binding. This "basicHttpBinding" states that the webservice is a basic profile website, which is fundamentally the same as an ASMX service. This is the binding you may want to opt for when you want to maximize the interopability of the service. For example, PHP's NuSoap and COM clients can easily access basicHttpBinding services.
The address of the endpoint is very important as it states where the endpoint is accessed. Since we are going to be hosting our WCF service in IIS (or the VS2005 WebDev Server), the address is automatically the name of the file, or " service.svc" in our case. If there is something in the address attribute it is appended after a "/" after the base file name. For example, to have an endpoint with the address of "http://localhost/service.svc/basic", you would use the following endpoint:
<endpoint address="basic" binding="basicHttpBinding" contract="Services.IEmailService "/>
You would do something like this if you had multiple HTTP endpoints. For example, you may want to have an explicitly named basicHttpBinding endpoint, which is interopable with just about anything, as well as a wsHttpBinding endpoint, which provides many WS-* specifications. If you wanted this extra binding, you simply have to add this endpoint right next to the other:
<endpoint address="ws" binding="wsHttpBinding" contract="Services.IEmailService"/>
This new endpoint would be accessible from "http://localhost/service.svc/ws" leaving the other one accessible as well. In this case we would have two endpoints, but still only one service. With the addition of a single line, you created an entirely new endpoint for the service, giving a while new world of possiblities. It's really like having two services share the same logic. For our demo here, however, we are going to stick with leaving the address blank using the binding of basicHttpBinding, thus making our service accessible via simply " service.svc"
At this point we have everything we need to run the service. So, in your web browser, you will go to service.svc and you will see... a very weird message stating "Metadata publishing for this service is currently disabled" as well as a bunch of other stuff. While it's true that we are done creating our service, we still need to know what XML to send to the service. The basicHttpBinding binding, as well as other web service bindings like wsHttpBinding and wsDualHttpBinding, follows Don Box's definition of service oriented architecture: "message is medium". That is, client and service are disconnected and communicate by sending messages to each other much like how us humans send e-mails to each other or how systems may send messages to MSMQ.
Furthermore, these bindings also follow Don Box's four tenets of SOA: services have explicit boundaries, they are policy driven, are autonomous, and share contract. That last one is important as when a message is send, it's important to know what the interface of the message looks like. Put another way, both client and service must sign a contract agreeing what interface to use before they go into business with each other. That IEmailService interface we had a bit ago is our interface, our contract. As such, the message that is sent from client to service, will be some XML form of that interface.
Since we are using a web service binding, the XML we are going to send is actually SOAP XML. This form of XML is how XML messages are formatted. If you know the SOAP protocol enough to look at the service interface and write the XML message based on that, then you're done at this point with the service as the service is up and running and ready to accept request. However, don't feel bad if you don't know SOAP in detail. In this demo, we are going to do down the road of discovering what message to send to the service. This relates directly to what the weird "Metadata publishing for this service is currently disabled" message was about.
The message was telling us that the mechanism that there in't really any way for us to discover the contract of the web service. Fortunately, that screen also states how to enable metadata generation. Actually it states two ways. The first way is to explicitly create an endpoint that allows use of the WS-Metadata Exchange (MEX) protocol. This protocol allows for exactly what it's name implies: exchange of metadata. Now, to actually enable MEX on our service we simply have to add another endpoint to our service. We add this endpoint the same way he added our basicHttpBinding endpoint, but this time we uses mexHttpBinding as the binding and IMetadataExchange as the contract. Looking at that, you may wonder how in the world that's possible since our service doesn't at all implement the IMetadataExchange interface. You can't just whip out any contract you want to use. If, for instance, you created a copy of our IEmailService contract and named it IEmailService2 and tried to use it in our endpoint, an exception would get thrown stating that our service doesn't implement that contract. Fortunately WCF has another mechanism called Behaviors that allows for things like this. In fact, to finish our enabling of MEX, we have to add a behavior.
Adding a behavior, or more specifically, a service behavior, is really as simple as adding a few elements to our web.config file. By declaring a new behavior with a serviceMetadata element in it, we have effectively created the behavior we need to allow our service to expose MEX data. To actually use it, however, we need to assign it to our service. The following web.config section shows everything we've discussed regarding MEX:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="NotificationServiceBehavior">
<serviceMetadata />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="NotificationServiceBehavior" name=" Services.EmailService">
<endpoint address="" binding="basicHttpBinding" contract="Services.IEmailService"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
</system.serviceModel>
However, this isn't what the WCF message screen was showing us. It was actually also showing a slightly modified version of this. Their version also allows access of the service WSDL via HTTP GET. That is, while our version allows for access of metadata information via the MEX protocol at " service.svc/mex", their's allows for WSDL access via "service.svc?wsdl". As you may recall from part 1 of this series, that type of access is through the HTTP GET mechanism. So, if we wanted to also allow access via that mechanism, we simply have to modify our serviceMetadata element to have the htpGetEnabled set to true like this:
<serviceMetadata httpGetEnabled="true"/>
Now you can see the service WSDL via the "?wsdl" as was previously accessible with ASMX services. Honestly, however, it's not really a fully WSDL file. You will notice that it doesn't include information regarding the parameters of the service method. By turning off the MEX protocol and enabling message tracing, which is osmething we will see a bit later, and then trying to use "?wsdl" will show that much more is going on than a simple download of a WSDL XML document. Regardless, our trek doesn't stop there anyhow. Let's not forget that our mission in doing all of this is to discover the messages that go over the wire. Before moving on to the next step, though, take some advice and don't worry about trying to memorize that stuff. Every drop of the instructions I just went over regarding the mex endpoint is shown when you access a service without the mex endpoint added.
Now that we have added the ability to get metadata from the service, we can now move on to discovering what the message is. To do this we need to run a test between client and service and log the messages as they transfer. The quickest way to do this is to throw together a simple .NET console application and add our WCF endpoint as a service reference, turn on message logging, access a service method from our client, and extract the message needed. This actually goes a lot quicker than it sounds and in the process you will also learn how to do WCF tracing, which will come in tremendously handy when troubleshooting WCF services.
First, create a simple console application named Console and add our service as a service setting "EmailService" as the service name. The service name doesn't have to match anything, but you should make it understandable and avoid things like "localhost" or "MyService". Next, create a simple client and access the Send method. Here is that code in the Program.cs file:
using System;
using Console.EmailService;
namespace Console
{
class Program
{
static void Main(string[] args) {
using (EmailServiceClient client = new EmailServiceClient( )) {
client.Send("from@tempuri.org", "to@tempuri.org", "", "", "subject", DateTime.Now , "body", new String[] { });
}
}
}
}
At this point you have a WCF client able to communicate with a WCF service, but that's not our goal. We want to be able to interop with a WCF service from anywhere using XmlHttp. Having that in mind, we need to go to the service and turn on message tracing. The easiest way to do this is through the WCF configuration tool. Since it's just XML and not an editing of a webpage, I feel comfortable letting a tool do this for me. Furthermore, there's honestly too many things to change to do manually. The tool makes it really simple.
To turn on the message tracing that we want, go to the service host, which is a website in our case, in VS2005, r-click on web.config and select "Edit WCF Configuration...", which will display the service configuration editor. Expand "Diagnostics" and select "Message Logging". Then, in the right hand side set LogEntireMessage to true. You will also want to select the "Diagnostics" node and turn on "Log Auto Flush" by clicking on "Enable Log Auto Flush" to make sure the tracing information gets written to disk. If you don't do this, only half the message may be get written to the disk at the time you want to see the log. Finally, we need to turn on message logging by clicking "Enable message logging". You may also opt to set a location for the logs, but we're going to leave the default for our discussion. When exiting be sure to save the configuration.
Now you can run the console application and logging will magically happen. If you accidentally run the application more than once, that's not a problem, but you made the log file larger and therefore possibly more confusing to read. To make things simpler on yourself I would recommend deleting the file and trying again. If you find tht the file is locked, simply open your web.config file and save it. This should unlock the file and allow you to delete it.
After you have successfully run the console, go to the log folder, which is the folder where the website is in our case, and open "web_messages.svclog". In there you will see a bunch of XML, but what you are specifically looking for are two things: the SOAPAction element and the SOAP request envelope.
The SOAPAction element looks something like this:
<SOAPAction>"http://www.netfxharmonics.com/services/email/2007/03/IEmailService/Send "</SOAPAction>
Frankly this element is something you could probably guess since it's simply a combination of our service namespace, our service contract, and our service method. You can think of this as a fully qualified method call. It's very important that the namespace of our service matches the namespace here as well as the namespace of our SOAP message, which we will see in a moment. Like .NET namespaces, you must make absolutely sure that they line up exactly. Just as a "Drawing" class in the "Raffle" namespace is different from the "Drawing" class in the "Graphics" namespace are different, any variations of namespaces here completely changes everything.
The SOAP request envelop looks something like this:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/ ">
<s:Header>
<To s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none ">http://localhost:62720/Website/service.svc</To>
<Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none ">http://www.netfxharmonics.com/services/email/2007/03/IEmailService/Send</Action>
</s:Header>
<s:Body>
<Send xmlns=" http://www.netfxharmonics.com/services/email/2007/03/">
<from>from</from>
<to>to</to>
<cc>cc</cc>
<bcc>bcc</bcc>
<subject>subject</subject>
<datetime>2007-03-09T17:31:21.9627522-06:00</datetime>
<body>body</body>
<attachments xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays" xmlns:i=" http://www.w3.org/2001/XMLSchema-instance">
</attachments>
</Send>
</s:Body>
</s:Envelope>
This is the XML that is sent to the service to call the method. Again, take note of the namespace on the Send element. This matches the namespace of our service. This is very important as mismatched namespaces are at the root of many service calls done by hand. Also, note the format of the message itself, it's just a method call with parameters written in a Body which is inturn wrapped in an Envelope, which we call a SOAP envelope. As you can see, the terminology really matches our human terminology with regard to it being a "message".
There's a third thing you may want to look for in the message log, though you don't need to really copy it out, and that's the SOAP response envelope, which looks like this:
<s:Envelope xmlns:s=" http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<SendResponse xmlns=" http://www.netfxharmonics.com/services/email/2007/03/">
<SendResult>0</SendResult>
</SendResponse>
</s:Body>
</s:Envelope>
You don't need to save this as much as you need to be aware of it. This will contain the result of your service call. As you can see here, the return value of the service is "0", which, as we know from the EmailService class, means the e-mail was successfully sent. You simply need to be aware of this response message and have a plan as to how to deal with it. Perhaps you want to parse the XML or perhaps you want to manually extract the data between "<SendResult>" and "</SendResult>". What you do is up to you.
Now that we have the SOAP messages, you can go back into the WCF configuration and remove the tracing information. You will definately want to turn it off as that seriously slows things down. You may actually find it easier to go into the web.config file yourself and manually delete the XML elements for these things rather than go through the GUI, but that's up to you.
At this point we have everything we need to call the WCF service. Now we can create the client. To make things easier we are going to use basically the same setup we used in part 1 of this series: the Client.htm loads XmlHttp.js, which then calls the service. As a quick reminder, this is the basic setup for calling an XmlHttp service in JavaScript:
var xmlhttp = new XMLHttpRequest( );
xmlhttp.open ('POST', 'service.svc', true);
xmlhttp.onreadystatechange = function ( ) {
if(xmlhttp.readyState == 4) {
alert(xmlhttp.status);
alert(xmlhttp.responseText);
}
};
xmlhttp.send(data);
With the exception of a new service address, this is the exact same code as in part 1. However, this isn't enough to call a WCF service. To complete the code we have to add two HTTP headers: the SOAPAction and the Content-Type. If you recall, we got the SOAPAction earlier from the message logging, but could quickly recreate it as it's simply the service namespace combined with the service contract and service method. To add this to our XmlHttp request in JavaScript, we simply use the XMLHttpRequest::setRequestHeader function as shown below:
xmlhttp.setRequestHeader('SOAPAction', 'http://www.netfxharmonics.com/services/email/2007/03/IEmailService/Send');
Next, we need to set the Content-Type to XML using the same function:
xmlhttp.setRequestHeader('Content-Type', 'text/xml');
As we discussed in part 1, it's very important to remember to put these HTTP header functions AFTER the call to XMLHttpRequest::open or else you will get a JavaScript exception.
Finally, we just need to populate the variable we are going to stream to the service with the proper data. This data is the SOAP request we got from the message logging with one minor change. The change is the envelope header. It's very important to remove the SOAP header from the envelope before trying to send. If you leave it in there, you will get a WS-Addressing exception from WCF. Now, here is the data we are going to stream to the service:
data += '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">'; data += ' <s:Body>'; data += ' <Send xmlns="http://www.netfxharmonics.com/services/email/2007/03/">'; data += ' <from>from</from>'; data += ' <to>to</to>'; data += ' <cc>cc</cc>'; data += ' <bcc>bcc</bcc>'; data += ' <subject>subject</subject>'; data += ' <datetime>2007-03-09T17:31:21.9627522-06:00</datetime>'; data += ' <body>body</body>'; data += ' <attachments xmlns:a=" http://schemas.microsoft.com/2003/10/Serialization/Arrays" xmlns:i="http://www.w3.org/2001/XMLSchema-instance ">'; data += ' </attachments>'; data += ' </Send>'; data += ' </s:Body>'; data += '</s:Envelope>';
When this data is sent to the WCF service, WCF should successfully accept the message, do all appropriate processing, and return with a SOAP response, which you may use as you wish. Actually, this same SOAP client should work the same way for an ASMX client. This is true beacuse we were using the basicHttpBinding, which is completely interopable with ASMX.
In this article we did many different things. We first created a class, extracted an interface and implemented it on the class, decorated the class with WCF attributes, added a hosting file, adding some WCF configuration and then used JavaScript to send a SOAP message with the appropriate SOAPAction and Content-Type HTTP headers to the WCF service and received a response. For those of us who don't live and breathe the SOAP protocol, we also had the extra steps of creating a console application, enabled logging, and intercepted the message and removing it's SOAP header.
All that may seem like a lot of work, but keep in mind that most of everything we did was in the process of creating the service. The real point of this post was to demonstrate how you can use JavaScript to call WCF services just like you can use JavaScript to call other HTTP endpoints. Most of the work we did here was in the discovery of the XML message we need to send to the service.
If you wanted to extend this service to be more interopable you could add more endpoints to the WCF service by simply adding another "endpoint" element to the service in the web.config file. For an example of using multiple endpoints to expose a service or to see how you can do this outside of IIS, you can see my WCF Relative Binding Speed demo which demonstrates exactly these things.