Saturday, August 29, 2009

Calling a WCF Service from JavaScript

I spent the last couple of days working with SketchUp, WCF, and JavaScript.  SketchUp is a really cool tool for creating, modifying, and sharing 3D models.  I am certainly not a modeling expert but I do have some experience working with 3D models and I have to say I am was impressed with the free version.  The pro version is only $500.  While this is not a trivial sum of money, it is very affordable compared to some of the packages I have seen.

 

My job was to create a web service that could be called using JavasSript.  I will mention that this was a proof of concept and I specifically did not address security.  The following code will work but there is no security of any kind so if the resulting service is exposed to the internet, anybody can call it.   

 

The first thing I did was use the create WCF project wizard.  I wasn’t particularly worried about passing  complex data types at this point so I defined a simple Service Contract.



Code Snippet



  1. using System.ServiceModel;
  2. using System.ServiceModel.Web;
  3. namespace WcfModelService
  4. {
  5.     [ServiceContract(Namespace = "WcfModelService")]
  6.     public interface IModel
  7.     {        
  8.         [OperationContract]
  9.         [WebGet]
  10.         string GetData(int value);
  11.         [OperationContract]
  12.         [WebGet]
  13.         string HelloWorld();
  14.         [OperationContract]
  15.         [WebGet]
  16.         string HelloSomeone(string message);
  17.     }
  18. }




When you apply the WebGet attribute to  a service contract decorated with the OperationContract attribute you are adding some extra metadata to the operation.  This metadata doesn’t actually do anything unless a service behavior is looking for it.

Here is the implementation:



Code Snippet



  1. using System.ServiceModel.Activation;
  2. namespace WcfModelService
  3. {
  4.     [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
  5.     public class Model : IModel
  6.     {
  7.         #region IModel Members
  8.         public string GetData(int value)
  9.         {
  10.             return string.Format("You entered: {0}", value);
  11.         }
  12.         public string HelloWorld()
  13.         {
  14.             return "Hello World.";
  15.         }
  16.         public string HelloSomeone(string message)
  17.         {
  18.             return "Hello World and " + message + " too.";
  19.         }
  20.         #endregion
  21.     }
  22. }




As I mentioned, adding the WebGet attribute doesn’t actually have any effect on our ability to call the service via JavaScript unless we have a service behavior that will use the metadata.  In our configuration file we add the AjaxBehavior.  Notice the enableWebScript element in the behavior.  This is logically equivalent to adding the ScriptService attribute to an ASMX service.  The final thing we need to do is to expose an endpoint using the webHttpBinding.



Code Snippet



  1.     <system.serviceModel>
  2.     <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
  3.     <services>
  4.             <service  name="WcfModelService.Model">
  5.                 <endpoint address="" binding="webHttpBinding" contract="WcfModelService.IModel" behaviorConfiguration="AjaxBehavior">
  6.                 </endpoint>
  7.             </service>
  8.         </services>
  9.         <behaviors>
  10.             <endpointBehaviors>
  11.                 <behavior name="AjaxBehavior">
  12.           <enableWebScript/>
  13.                 </behavior>
  14.             </endpointBehaviors>
  15.         </behaviors>
  16.     </system.serviceModel>




All we have left to do now is to call the service from JavaScript.  We will keep things simple and add a ScriptManager with a ServiceReference to the Model service.  Now in JavaScript create the service proxy:

var proxy = new WcfModelService.IModel()

and call an operation:

proxy.HelloSomeone(s2, onSuccess, onFail, null); 



Code Snippet



  1. <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WcfTest.aspx.cs" Inherits="WcfModelService.Test" %>
  2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  3. <html xmlns="http://www.w3.org/1999/xhtml">
  4. <head>
  5.     <title>Wcf AJAX Service Client Page</title>
  6.     <script type="text/javascript">
  7.     // This function creates an asynchronous call to the service
  8.     function makeCall(operation){
  9.         var n1 = document.getElementById("num1").value;
  10.         var s2 = document.getElementById("string1").value;
  11.         
  12.         
  13.             // Instantiate a service proxy
  14.         var proxy = new WcfModelService.IModel();
  15.             // Call correct operation on proxy      
  16.             switch(operation){
  17.                 case "Data":
  18.                     proxy.GetData( parseInt(n1),  onSuccess, onFail, null);            
  19.                 break;
  20.                 
  21.                 case "Hello":
  22.                     proxy.HelloWorld( onSuccess, onFail, null);                        
  23.                 break;
  24.                 
  25.                 case "Someone":
  26.                     proxy.HelloSomeone(s2, onSuccess, onFail, null);            
  27.                 break;
  28.                 
  29.             
  30.             }
  31.         
  32.     }
  33.     // This function is called when the result from the service call is received
  34.     function onSuccess(callResult){
  35.         document.getElementById("result").value = callResult;
  36.     }
  37.     // This function is called if the service call fails
  38.     function onFail(){
  39.         document.getElementById("result").value = "Error";
  40.     }
  41.     // ]]>
  42.     </script>
  43.     <style type="text/css">
  44.         #num1
  45.         {
  46.             width: 303px;
  47.         }
  48.         #string1
  49.         {
  50.             width: 317px;
  51.         }
  52.         #result
  53.         {
  54.             width: 333px;
  55.         }
  56.         #btnData
  57.         {
  58.             width: 118px;
  59.         }
  60.         #btnHelloWorld
  61.         {
  62.             width: 99px;
  63.         }
  64.     </style>
  65. </head>
  66. <body>
  67.     <h1>
  68.         Wcf AJAX Service Client Page</h1>
  69.     <p>
  70.         A Number:
  71.         <input type="text" id="num1" /></p>
  72.     <p>
  73.         A String:
  74.         <input type="text" id="string1" /></p>
  75.     <input id="btnData" type="button" onclick="return makeCall('Data');"
  76.         value="Get Data" />
  77.     <input id="btnHelloWorld" type="button" onclick="return makeCall('Hello');"
  78.         value="Hello" />
  79.     <input id="btnHelloSomeone" type="button"
  80.         onclick="return makeCall('Someone');" value="Hello Someone" />&nbsp;
  81.     <p>
  82.       Result:
  83.         <input type="text" id="result" /></p>
  84.     <form id="aForm" action="" runat="server">
  85.     <asp:ScriptManager ID="ScriptManager" runat="server" >
  86.         <Services>
  87.             <asp:ServiceReference Path="~/Model.svc" />
  88.         </Services>
  89.     </asp:ScriptManager>
  90.     </form>
  91. </body>
  92. </html>




Here is an ASMX version of the same service.



Code Snippet



  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Web;
  5. using System.Web.Services;
  6. using System.Web.Script.Services;
  7. namespace AsmxModelService
  8. {
  9.     /// <summary>
  10.     /// Summary description for AsmxModelService
  11.     /// </summary>
  12.     [WebService(Namespace = "WcfModelService")]
  13.     [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
  14.     [System.ComponentModel.ToolboxItem(false)]
  15.     // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
  16.     [System.Web.Script.Services.ScriptService]
  17.     public class AsmxModelService : System.Web.Services.WebService
  18.     {
  19.         [WebMethod]
  20.         [ScriptMethod(UseHttpGet = true,ResponseFormat = ResponseFormat.Json)]
  21.         public string GetData(int value)
  22.         {
  23.             return string.Format("You entered: {0}", value);
  24.         }
  25.         
  26.         [WebMethod]
  27.         [ScriptMethod(UseHttpGet = true, ResponseFormat = ResponseFormat.Json)]
  28.         public string HelloWorld()
  29.         {
  30.             return "Hello World";
  31.         }
  32.         [WebMethod]
  33.         [ScriptMethod(UseHttpGet = true, ResponseFormat = ResponseFormat.Json)]
  34.         public string HelloSomeone(string message)
  35.         {
  36.             return "Hello World and " + message + " too.";
  37.         }
  38.     }
  39. }




If you need to secure the call, we could force the user to login before accessing the aspx page containing the JavaScript service call.  An Authentication cookie will now be passed to the application with each request. 

 

By setting aspNetCompatibilityEnabled=true we can access the identity using a call to HttpContext.Current.User.Identity.Name.  Each operation should now verify the user is authorized to make the call before executing the operation logic.

Reference:

http://msdn.microsoft.com/en-us/magazine/cc793961.aspx#id0070025

1 comment:

  1. This comment has been removed by a blog administrator.

    ReplyDelete