View RSS Feed

richyrich

Convert .asmx file to WCF .svc file to create simple API web service

Rating: 2 votes, 1.00 average.
by on October 19th, 2009 at 11:12 PM (5380 Views)
3) Converting a .asmx file to WCF .svc file

This is part of a series of blogs. The others are:-
1) Introduction
2) A simple .asmx file and calling a function

The next section to cover is converting our .asmx file to a WCF web service. This is placed in a .svc file and uses Contracts to define what can be consumed in the web service and endpoints in the web.config of your application define how the service will be exposed.

I guess the first question is, why go to the trouble of converting your web services?
I found this on Rick Strahl’s Web log[1], which made a lot of sense to me:-
“A valid question to ask though is: Do I need to use WCF or should I continue to use ASMX?

If ASMX services are working fine for you as is with MS AJAX you probably don't have to switch. In fact if your services are purely based on making AJAX callbacks from a pure Web app and you don't plan on reusing existing services, there's not much of an advantage using WCF over ASMX services. ASMX services continue to be easier to set up and work with and maybe more importantly they don't require .NET 3.5.

WCF REST services however provide features that ASMX does not, such as the ability to serve raw non-ASP.NET AJAX formatted data in 'bare' format. If you're working with non-ASP.NET AJAX clients like jQuery or Prototype this can be a bonus and lets you avoid using the MS AJax client libraries and ScriptManager. WCF REST also supports raw XML based access to services which is also useful and can't be directly accomplished with ASMX.

Another consideration: If you have existing services that rely on HttpContext, realize that WCF by default doesn't support access to the underlying 'host platform'. WCF is supposed to be host agnostic and WCF services can in fact be running outside of IIS. If you have existing code that relies on HttpContext access to the Request or Session object which is common , it's probably best to leave it in ASMX (although WCF has a way to do that as well).

But if you're starting a new app and you're using .NET 3.5 on the server anyway you might as well take advantage of WCF's REST/Ajax features and the wider range of options available to publish data.”

So, assuming you want to have a go at converting your asmx web service to WCF, read on. A lot of the coding below is based on a couple of excellent screen casts I found from Rob Bagby[2,3,4]

In the web service you have [ServiceContract] attribute that defines the Interface between the server and the client, an [OperationContract] attribute that defines the functions that are available inside the web service, a [DataContract] attribute that exposes a class object in the web service and the [DataMember] attribute that defines which properties of the class will be available.

Using the example from the previous blogs, this is how I have created the .svc file
Code:
 <%@ ServiceHost Language="C#" Debug="true" Service="services.RESTEmailService" CodeBehind="~/App_Code/emailing.cs" %>
 

The relevant piece of information for us is the Service attribute. This is how we will reference the service in our web.config I have called this file RESTEmail.svc

And the emailing.cs code behind
Code:
 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.Serialization;
 using System.ServiceModel;
 using System.ServiceModel.Web;
 using System.Net;
 using System.Text;
 using w3d.App.BLL.Emails;


 namespace services
 {

     [DataContract]
  public class response
     {
  public bool error { get; set; }
         [DataMember]
  public string message { get; set; }
     }

     [DataContract]
  public class iEmail
     {
         [DataMember]
  public string emailTo { get; set; }
         [DataMember]
  public string emailFrom { get; set; }

  public string emailCc { get; set; }

  public string emailBcc { get; set; }
         [DataMember]
  public string subject { get; set; }
         [DataMember]
  public string message { get; set; }
         [DataMember]
  public string priority { get; set; }
         [DataMember]
  public bool read { get; set; }
         [DataMember]
  public bool own { get; set; }

  public string filepath { get; set; }

     }

     [ServiceContract]
  public interface iRESTEmailService
     {
         [OperationContract]
         [WebGet(UriTemplate="get", ResponseFormat=WebMessageFormat.Xml)]
  iEmail GetEmail();

         [OperationContract]
         [WebInvoke(Method = "PUT", UriTemplate = "get", ResponseFormat = WebMessageFormat.Xml, RequestFormat = WebMessageFormat.Xml)]
  //[WebGet]
  response SendEmail(iEmail iEmail);
     }

  public class RESTEmailService : emailingBase, iRESTEmailService
     { }

  public class emailingBase
     {
  public response SendEmail(iEmail iEmail)
         {
  OutgoingWebResponseContext outResponse = WebOperationContext.Current.OutgoingResponse;
  IncomingWebRequestContext inRequest = WebOperationContext.Current.IncomingRequest;

  response response = new response();

  email newEmail = new email();
             newEmail.emailTo.Add(new emailAdd(iEmail.emailTo));
             newEmail.emailFrom.Add(new emailAdd(iEmail.emailFrom));
             newEmail.subject = iEmail.subject;
             newEmail.message = iEmail.message;
             newEmail.priority = iEmail.priority;
             newEmail.read = iEmail.read;
             newEmail.filepath = "";

             newEmail.seperate = false;
             newEmail.isHTML = true;
             newEmail.sendAsync = false;

  if (!email.SendEmail(newEmail))
             {
                 outResponse.StatusCode = HttpStatusCode.InternalServerError;
                 response.message = newEmail.err;
  //return null;
             }else{
                 response.message = "Your email has been sent";
             }
  return response;
         }

  public iEmail GetEmail()
         {
  iEmail iEmail = new iEmail();
             iEmail.emailBcc = "emailBcc";
             iEmail.emailCc = "emailCc";
             iEmail.emailFrom = "emailFrom";
             iEmail.emailTo = "emailTo";
             iEmail.filepath = "filepath";
             iEmail.message = "message";
             iEmail.own = false;
             iEmail.priority = "Normal";
             iEmail.read = false;
             iEmail.subject = "subject";

  return iEmail;
         }
     }
 }
 


The main points to note are:-
1)The System.ServiceModel reference.

2)DataContracts (iEmail and response)

3)
ServiceContract which defines the interface between server and client

4)
OperationContract that defines the methods exposed to the client

1)
The ServiceModel namespace handles web services

2) I have defined two DataContracts. One is a response that we will use to send a message back to our client. The second is an email contract that defines the parameters we require in order to process the request to the web service.
You will notice there are a couple of properties that do not have the DataMember attribute. These properties will not be exposed to the client, which will become clear when we move on to the get request

3) We have one ServiceContract that is our interface between the client and our web service. I have kept mine very simple, but you can add attributes to each, such as a namespace and the like. I haven’t included anything like that in mine

4) The ServiceContract has two OperationContracts, ie two functions exposed to our clients.

One is a GET request which returns our iEmail DataContact object. It is decorated with the WebGetAttribute, meaning it will be called by way of a GET request like in a browser address bar. It has been given a UriTemplate attribute of “get”. The UriTemplate allows you to define how this operation will be called by the client. The UriTemplate refers to anything you have after a filename.ext/ So in our case, a client would use RESTEmail.svc/get to call this operation, using a GET request. I have also defined the ResponseFormat as XML.
I’ll come onto the reason for this operation in a moment.

The other is a PUT request that will actually be used to send our email and return a response to the client. This has a WebInvokeAttribute that allows us to define how the operation will be called. You’ll notice that it has exactly the same UriTemplate as our GET operation, but we have defined the METHOD as PUT. So any PUT requests sent to RESTEmail.svc/get will be handled by this operation. There is a fair amount of debate over the use of HTTP verbs and what operations each should handle. You could, for example, decide to use a POST request for this operation. I have also defined the Request and Response formats as XML.

So, if you want a service to be accessed by a GET request, use the WebGetAttribute. If you want to use a different method, use the WebInvokeAttribute.

These OperationContracts are interfaces with the client which are used to call functions and routines contained in our base class that actually deal with the requests.

The above example is set up slightly differently to how you may see other examples, in that there is a holding class called RESTEmailService that contains no code. It inherits the interface class and a base class that contains all our main functions. This is due to a restriction in WCF regarding handling different request types and formats in the same service[1] I will go into this in more detail in another article.

Our SendEmail function expects an iEmail object as a parameter and returns a response object. The first thing we done is define a OutgoingWebResponseContext object to enable us to send back a response status code to our client and we have an IncomingWebRequestContext object to provide access to the request from the client. We then process the email details and use a standard emailing function in our emails namespace to send the email. If our SendEmail function[5] returns false then we set the status code to InternalServerError and return the excepton text in our response.message property. Otherwise the status code remains at 200 (OK) and returns a message saying the email has been sent.


Web.Config
We also need to define an endpoint in our web.config to describe how the service is accessed. This is contained in the <system.serviceModel> tag.
To configure the above web service we would have something like:-
Code:
       <system.serviceModel>
     <serviceHostingEnvironment>
       <baseAddressPrefixFilters>
         <add prefix="http://www.yourdomain.com/webservices" />
       </baseAddressPrefixFilters>
     </serviceHostingEnvironment>
     <behaviors>
                   <endpointBehaviors>
                         <behavior name="REST">
                               <webHttp />
                         </behavior>
                         <behavior name="AJAX">
                               <enableWebScript />
                         </behavior>
                   </endpointBehaviors>
             </behaviors>
             <services>
       <service name="services.RESTEmailService">
         <endpoint address="" binding="webHttpBinding" behaviorConfiguration="REST" contract="services.iRESTEmailService" />
       </service>
     </services>
       </system.serviceModel>
 

This is defined in the <configuration> tag.
I won’t go into huge detail here as the screencasts I suggested give a better explanation of what goes where and why. The basic details to note are the name attribute of our <service> tag is the same as the Service attribute in the ServiceHost declaration and the contract on the endpoint is the namespace (services) and the name of our interface ServiceContract in our code behind file.

Testing the web service
To test the service, I would suggest downloading Fiddler[6] which enables us to examine the HTTP requests and responses. Once installed and Open, find and click on the Request Builder tab. If not there already, in the Request Headers box add a separate line with the following code:-
Content-Type: text/xml

Change the dropdown to GET and then type in the textbox http://www.yourdomain.com/pathtowebs...TEmail.svc/get

In the left column (WebSessions) you should see a request to your webservice. It should be the last one in the list as the most recent. Once a response has been received, hopefully with a 200 status code, double click on it. This should take you to the Inspectors tab. In the bottom box, you should see the response received from the server which should be an XML format of our iEmail DataContract. Click on the Raw view to see the raw data returned from our GET service. You will notice that only the properties decorated with the [DataMember] attribute in our DataContract are returned. Highlight the body of the response (just the XML part starting <iEmail) and copy that into an XML file in Visual Studio. This just makes it easier to format it and change the values. Put each element on a new line and then change the values of each element to make a valid simple email to yourself.

Something like:-
Code:
 <iEmail xmlns="http://schemas.datacontract.org/2004/07/services" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
   <emailFrom>anotheremail@domain.com</emailFrom>
   <emailTo>youremail@yourdomain.com</emailTo>
   <message>test message</message>
   <own>false</own>
   <priority>Normal</priority>
   <read>false</read>
   <subject>test subject</subject>
 </iEmail>
 

Highlight this XML code, go back into Fiddler, click on the Request Builder tab again and paste the XML into the Request Body box. Again, make sure the Request Headers contains the Content-Type: text/xml line and the address is http://www.yourdomain.com/pathtowebs...TEmail.svc/get

You also need to make sure you change the dropdown to PUT as we are going to use the PUT request to send our email.

Click Execute and you should see the session appear in the left hand list. If all is well, you should get a response status code of 200, which means it has been successful. If you receive anything other than 200, double click on the session to examine the Raw response and to see any error message that may have been returned.

Consuming the web service
The service above is not AJAX enabled at the moment, so you couldn’t call it from the same domain using a client script at the moment. I will come onto that in the next article.

Calling the service from a different domain
You can call this service from an external domain, either via an aspx or PHP page.

As we are using webHttp to define the service, we need to make HttpRequests to it and handle the HttpResponses that are returned. To do this in an ASP.NET page, I used the following code:-
Code:
 using System;
 using System.Text;
 using System.Xml;
 using Microsoft.VisualBasic;
 using System.Web;
 using System.Net;
 using System.IO;


 partial class testc : System.Web.UI.Page
 {

  public void Page_Load()
     {
 //create an instance of an HttpWebRequest (from System.Net 
   //namespace) and create a WebRequest to our PUT service
  HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create("http://www.domain.com/webservices/RESTEmail.svc/get");
   //set the request method to PUT
         myRequest.Method = "PUT";
   //set the ContentType to text/xml
         myRequest.ContentType = "text/xml";
   //Build our XML request body using a stringbuilder
  StringBuilder xmlRequest = new StringBuilder();

  //Always run web service calls inside Try…Catch just incase
   //the server handling the web service is down or not responding
  try
         {
             xmlRequest.Append("<iEmail xmlns=\"http://schemas.datacontract.org/2004/07/services\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">");
             xmlRequest.Append("<emailFrom>yourname@domain1.com</emailFrom>");
             xmlRequest.Append("<emailTo>person@domain.com</emailTo>");
             xmlRequest.Append("<message>test message</message>");
             xmlRequest.Append("<own>false</own>");
             xmlRequest.Append("<priority>Normal</priority>");
             xmlRequest.Append("<read>false</read>");
             xmlRequest.Append("<subject>test subject</subject>");
             xmlRequest.Append("</iEmail>");

 // As we’re using a PUT request we must define the 
       // ContentLength which we get by checking the length of our 
 // StringBuilder
             myRequest.ContentLength = xmlRequest.Length;
             //Set a Stream object as the HttpRequest.GetRequestStream
  Stream mystream = myRequest.GetRequestStream();
 // Create a new writer to pass our request to the web 
 // service
  StreamWriter myRequestStream = new StreamWriter(mystream);
  // Write the XML to the stream
             myRequestStream.Write(xmlRequest.ToString());
  // Flush out the data in our Stream
             myRequestStream.Flush();
             myRequestStream.Close();

  // Create an instance of an HttpWebResponse to receive the
 // result from our webservice
  HttpWebResponse myResponse = (HttpWebResponse)myRequest.GetResponse();
  // Write the statuscode to the screen. Should be “OK”
  HttpContext.Current.Response.Write(myResponse.StatusCode);
         }
  catch(Exception ex)
         {
  HttpContext.Current.Response.Write(ex.ToString());
 ;        }

     }

 }
 

I found making an HTTP call using PHP to be a lot harder, but I then managed to find an XMLHttpRequest class[7] that uses cURL to send and receive requests. I also located a StringBuilder class[8] to make it easier to build the XML request body. So, the PHP page I constructed was something like this:-
PHP Code:
   <?php

      
include_once('class.StringBuilder.php');
   include_once(
'class.XMLHttpRequest.php');

      try
   {
   
$xmlRequest = new StringBuilder();
   
$xmlRequest->Append("<iEmail xmlns='http://schemas.datacontract.org/2004/07/services' xmlns:i='http://www.w3.org/2001/XMLSchema-instance'>");
   
$xmlRequest->Append("<emailFrom>yourname@domain1.com</emailFrom>");
   
$xmlRequest->Append("<emailTo>person@domain.com</emailTo>");
   
$xmlRequest->Append("<message>test message</message>");
   
$xmlRequest->Append("<own>false</own>");
   
$xmlRequest->Append("<priority>Normal</priority>");
   
$xmlRequest->Append("<read>false</read>");
   
$xmlRequest->Append("<subject>test subject</subject>");
   
$xmlRequest->Append("</iEmail>");

      
$request = new XMLHttpRequest();
   
$request->open("PUT","http://www.domain.com/webservices/RESTEmail.svc/get");
   
$request->setRequestHeader("Content-Type","text/xml");
   
$request->setRequestHeader("Content-Length",$xmlRequest->Length());
   
$request->send($xmlRequest->ToString());
   print 
$request->statusText;
   }

      catch(
HttpException $ex)
   {
   print 
$ex;
   }

      
?>
I’m not a PHP expert so this is a very simple example. You could include references to form elements etc. to populate the object

The next article will cover making this web service AJAX enabled and providing different request and response formats.

References

1) http://www.west-wind.com/WebLog/posts/310747.aspx
2) RobBagby | Posts | Channel 9
3) deCast - Creating a HI-REST GET Service with WCF 3.5 | RobBagby | Channel 9
4) deCast - Creating a HI-REST PUT Service That Exposes Insert and Update | RobBagby | Channel 9
5) http://www.developerbarn.com/net-cod...ail-class.html
6) Fiddler Web Debugger - Freeware HTTP(S) debugging tool
7) PHP Class XMLHttpRequest (XMLHttpRequest emulator using cURL) | Moonlight21
8) A Basic PHP StringBuilder - pedrocorreia.net

Submit "Convert .asmx file to WCF .svc file to create simple API web service" to Digg Submit "Convert .asmx file to WCF .svc file to create simple API web service" to del.icio.us Submit "Convert .asmx file to WCF .svc file to create simple API web service" to StumbleUpon Submit "Convert .asmx file to WCF .svc file to create simple API web service" to Google

Comments


SEO by vBSEO