Tuesday, March 18, 2008

WCF Services - Part 2

In the last post I tried to show how to use HTTP GET to get data back from my Services. Now I want to put my thoughts in order and show how I created new Employees and updated them using programmatic POST using the ubiquitous Northwind database.

My first attempts where at trying to update a record using values embedded in the querystring... easier said than done, it was quite an effort.. At then end it worked, it just required a shift in the way I was tackling the problem. I ended up having my contract accept a Stream parameter and then had to read the stream and get the values back.

 

My contract was:

//STREAM EXAMPLE
[OperationContract]
[WebInvoke(UriTemplate = "*", Method = "POST")]
void UpdateEmployeeName(Stream id);

My uri template was "*" since I wanted to have the querystring collection of values straight after the 'root' of my Service.svc

My implementation was very simple...

public void UpdateEmployeeName(Stream id)
 {
     StreamReader reader = new StreamReader(id);
     String res = reader.ReadToEnd();
     NameValueCollection coll = HttpUtility.ParseQueryString(res);
     
     int employeeID = Int32.Parse(coll["elid"]);
     String newName = coll["newName"];
     // Bless LinqToSql
     NorthwindDataContext cts = new NorthwindDataContext(ConnectionManager.GetHomeLocalConnectionString);
     Employee e = cts.Employees.Where(a => a.EmployeeID == employeeID ).Single();
     e.FirstName = newName;
     cts.SubmitChanges();
 }

Now, how the heck do I make a programmatic POST to my contract?

I just created a WebRequest and set the content type to form-urlencoded and set the request length to the length of my queryString collection.

//set the data
ASCIIEncoding enc = new ASCIIEncoding();
string datatext = "elid=" + elEmployeeID.Text;
datatext += "&newName=" + elEmployeeNewName.Text;

byte[] data = enc.GetBytes(datatext);

//HTTP POST query
WebRequest request = HttpWebRequest.Create("http://localhost/DemoWCF/Service.svc");
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = data.Length;

Stream datasteam = request.GetRequestStream();
datasteam.Write(data, 0, data.Length);
datasteam.Close();
WebResponse response = request.GetResponse();
 

To test it I threw a few textfields together and magic....

part2_1

 

And checking the db....

 

part2_2

 

I also wanted to create a new Employee using Javascript using a programmatic post as above but this time using Javascript Object Notation (JSON ) http://www.json.org/

 

My contract..

[OperationContract]
[WebInvoke(BodyStyle = WebMessageBodyStyle.Wrapped,
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json,
UriTemplate = "/CreateEmployee")]
void CreateEmployee(string FirstName, string LastName);
Implementation:
public void CreateEmployee(string FirstName, string LastName)
{
      NorthwindDataContext cts = new NorthwindDataContext();
      Employee e = new Employee();      
      e.LastName = LastName;
      e.FirstName = FirstName;
      cts.Employees.InsertOnSubmit(e);
      cts.SubmitChanges();           
}

 

To generate an HTTP POST call with JavaScript, I created a XMLHTTPRequest and then I just constructed the url, set the header content type to json and was careful to format my JSON pair's payload..

<asp:Button  ID="btnJSONCreateEmployee" runat="server" OnClientClick="doEmployeeCreate()"
function doEmployeeCreate()
{            
       var newFirstName = document.getElementById('TextBox1').value;            
       var newLastName = document.getElementById('TextBox2').value;
            
       var xmlHttp = new XMLHttpRequest();            
          
       // Create result handler 
       xmlHttp.onreadystatechange=function()
       {
          if(xmlHttp.readyState == 4)
          {
             document.getElementById("result").value = xmlHttp.responseText;
           }
       }
            
       var url = "Service.svc/CreateEmployee";
       var body = '{"FirstName": '+ '"' + newFirstName + '"';
       body += ', "LastName": ' + '"'+newLastName + '"' + '}';
            
       //Send the Http Request
       xmlHttp.open("POST", url, true);
       xmlHttp.setRequestHeader("Content-type", "application/json");
       xmlHttp.send(body); 
}

 

To create a JSON payload in C# is very simple, just format a string carefully, escaping the " and you'll be fine..

protected void btnCallService_Clicked(object sender, EventArgs args)
{
   ASCIIEncoding enc = new ASCIIEncoding();
   string employeeID = txtJsonEmpID.Text;
   string firstName = txtJsonFirstName.Text;
   string lastName = txtJsonLastName.Text;           
   string datatext = "{\"EmployeeID\":{0}, \"FirstName\":\"{1}\"  , \"LastName\":\"{2}\" }";
        
   datatext = datatext.Replace("{0}", employeeID)
                      .Replace("{1}",firstName)
                      .Replace("{2}", lastName);
               
   byte[] data = enc.GetBytes(datatext);

   WebRequest request = HttpWebRequest.Create("http://localhost/DemoWCF/Service.svc/CallJason");
   request.Method = "POST";
   request.ContentType = "application/json";
   request.ContentLength = data.Length;
   
   Stream datasteam = request.GetRequestStream();
   datasteam.Write(data, 0, data.Length);
   datasteam.Close();
   WebResponse response = request.GetResponse();
}

 

Ok, now unfortunately one of the new Employees didn't leave good comments in SVN and el# got a bit upset when he had a look at the logs. So let's fire the guy, tough choice yes, but then also we need to demo a programmatic HTTP DELETE.

part2_3 

Simple contract...I just specify the request format, the response format and the method that my client will be using..

 
[OperationContract]
[WebInvoke(BodyStyle = WebMessageBodyStyle.Wrapped,
    RequestFormat = WebMessageFormat.Json,
    ResponseFormat = WebMessageFormat.Json,
    UriTemplate = "/FireEmployee",
    Method="DELETE" )]
void FireEmployee(int EmployeeID);

Implementation is dead simple...this has got nothing to do with WCF, but I just post it here for completeness.

public void FireEmployee(int EmployeeID)
{
    NorthwindDataContext ctx = new NorthwindDataContext();
    Employee emp = ctx.Employees.Where(e => e.EmployeeID == EmployeeID).Single();
    ctx.Employees.DeleteOnSubmit(emp);
    ctx.SubmitChanges();  
}  

Calling using JavaScript is v. simple too..

function fireTheGuy()
{
    var employeeID  =  document.getElementById('txtToDeleteEmployeeID').value;
    var xmlHttp = new XMLHttpRequest();            
          
    // Create result handler 
    xmlHttp.onreadystatechange=function()
    {
        if(xmlHttp.readyState == 4)
        {
          document.getElementById("result").value = xmlHttp.responseText;
        }
    }
            
    // void CreateEmployee(string FirstName, string LastName);
    var url = "Service.svc/FireEmployee";
    var body = ' { "EmployeeID": ' +  employeeID + ' }';
            
    //Send the Http Request
    xmlHttp.open("DELETE", url, true);
    xmlHttp.setRequestHeader("Content-type", "application/json");
    xmlHttp.send(body);       
}
 

And trust me, it works like a charm.

Next post I would like to return complex types back to the client, as in Employee's objects etc..

 

1 comment:

Anonymous said...

Thank you, thank you! I've been searching for hours for an example like this!