AJAX and WCF


Most of the information in this post applies to .NET 3.5. Code examples are in F# because I’m teaching myself F#. If you want C#, wait until I post the same material on Azure or buy my Effective REST Services via .NET book.

I’m currently working on the finishing touches of an Amazon Web Services version of my PhotoWeb application. Because EC2 supports Windows, I’m building the whole thing for IIS, using F# as the programming language. One of the features of this application is that it uses AJAX to update metadata about the images on the screen. To handle AJAX, one commonly uses either ASMX or WCF. In .NET 3.5, the WCF team added some functionality to make AJAX a no-brainer via a ServiceHostFactory-derived class that just does the right thing and a few new attributes. This means no updates to web.config in order to get your service accessible to the world.

I am going to go through the mechanics of setting things up without diving into how the application interacts with storage-I’ll write those posts later.

The service interacts with a type: FSWebApp.ImageItem. ImageItem has the following structure:

[<DataContract>]

type ImageItem() =

    [<DefaultValue>]

    [<DataMember>]

    val mutable ImageUrl: System.String

 

    [<DefaultValue>]

    [<DataMember>]

    val mutable ImageId: System.Guid

 

    [<DefaultValue>]

    [<DataMember>]

    val mutable Description: System.String

 

    [<DefaultValue>]

    [<DataMember>]

    val mutable Caption: System.String

 

    [<DefaultValue>]

    [<DataMember>]

    val mutable PublicImage: System.Boolean

 

    [<DefaultValue>]

    [<DataMember>]

    val mutable UserName: System.String

The [<DefaultValue>] is an F#-ism stating that the mutable value should be initialized to the proper zero value for the type. This is the same as initializing each item in C# to default(type) which yields a value in the set [0|null|false].

With a data type to pass around, we can define some methods on that type. I know this is WCF and many of the early examples show defining an interface and then a concrete type. We will skip the interface here. It is OK to declare the ServiceContract and OperationContract information directly on the type since we won’t be writing any code to create a System.ServiceModel.Channels.Channel to that new type. (WCF requires an interface at the ServiceModel layer when creating your proxies. One is always present in the generated code. Any classes you use are helpers that eventually call into an interface.)

Simple operations are

  • Delete an image
  • Update an image metadata
  • Get all image metadata for the current user

The WCF, do nothing implementation is then:

[<ServiceContract>]

type PhotoWebService() =

 

    [<WebInvoke(Method = "POST")>]

    [<OperationContract>]

    member this.DeleteImage(imageId: System.String) =

        ()

 

    [<WebInvoke(Method = "POST")>]

    [<OperationContract>]

    member this.GetImagesForUser() =

        [|(new ImageItem()); (new ImageItem())|]

 

    [<WebInvoke(Method = "POST")>]

    [<OperationContract>]

    member this.UpdateImage(item: ImageItem, imageId: System.String) =

        ()

You may have noticed that all the parameters have explicit types. This is done because the F# engine cannot infer the types without usage of the methods. Because the caller will be external to the assembly, we have to tell the compiler what types to expect for the exposed methods.

Finally, we need to modify the .svc file so that the write ServiceHostFactory is used (and we can stay out of config!).

<%@ ServiceHost Language="F#" Service="FSWebApp.PhotoWebService" Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory" %>

With this in place, I added a ScriptManager to the page I was working with and got a beautiful JS file returned to me that knows how to interact with the PhotoWebService endpoint.

<asp:ScriptManagerProxy ID="scriptManagerProxy"
runat="server">

    <Services>

        <asp:ServiceReference Path="~/PhotoWebService.svc" />

    </Services>

</asp:ScriptManagerProxy>

Looking in a tool like Firefox’s Firebug, I see the script is being created for me, meaning that everything is wired up correctly!

For those of you who want a glimpse at what gets generated, here you go!

    1 Type.registerNamespace(‘tempuri.org’);

    2 tempuri.org.PhotoWebService = function() {

    3     tempuri.org.PhotoWebService.initializeBase(this);

    4     this._timeout = 0;

    5     this._userContext = null;

    6     this._succeeded = null;

    7     this._failed = null;

    8 }

    9 tempuri.org.PhotoWebService.prototype = {

   10     _get_path: function() {

   11         var p = this.get_path();

   12         if (p) return p;

   13         else return tempuri.org.PhotoWebService._staticInstance.get_path();

   14     },

   15     DeleteImage: function(imageId, succeededCallback, failedCallback, userContext) {

   16         return this._invoke(this._get_path(), ‘DeleteImage’, false, { imageId: imageId }, succeededCallback, failedCallback, userContext);

   17     },

   18     GetImagesForUser: function(succeededCallback, failedCallback, userContext) {

   19         return this._invoke(this._get_path(), ‘GetImagesForUser’, false, {}, succeededCallback, failedCallback, userContext);

   20     },

   21     UpdateImage: function(item, imageId, succeededCallback, failedCallback, userContext) {

   22         return this._invoke(this._get_path(), ‘UpdateImage’, false, { item: item, imageId: imageId }, succeededCallback, failedCallback, userContext);

   23     }

   24 }

   25 tempuri.org.PhotoWebService.registerClass(‘tempuri.org.PhotoWebService’, Sys.Net.WebServiceProxy);

   26 tempuri.org.PhotoWebService._staticInstance = new tempuri.org.PhotoWebService();

   27 tempuri.org.PhotoWebService.set_path = function(value) { tempuri.org.PhotoWebService._staticInstance.set_path(value); }

   28 tempuri.org.PhotoWebService.get_path = function() { return tempuri.org.PhotoWebService._staticInstance.get_path(); }

   29 tempuri.org.PhotoWebService.set_timeout = function(value) { tempuri.org.PhotoWebService._staticInstance.set_timeout(value); }

   30 tempuri.org.PhotoWebService.get_timeout = function() { return tempuri.org.PhotoWebService._staticInstance.get_timeout(); }

   31 tempuri.org.PhotoWebService.set_defaultUserContext = function(value) { tempuri.org.PhotoWebService._staticInstance.set_defaultUserContext(value); }

   32 tempuri.org.PhotoWebService.get_defaultUserContext = function() { return tempuri.org.PhotoWebService._staticInstance.get_defaultUserContext(); }

   33 tempuri.org.PhotoWebService.set_defaultSucceededCallback = function(value) { tempuri.org.PhotoWebService._staticInstance.set_defaultSucceededCallback(value); }

   34 tempuri.org.PhotoWebService.get_defaultSucceededCallback = function() { return tempuri.org.PhotoWebService._staticInstance.get_defaultSucceededCallback(); }

   35 tempuri.org.PhotoWebService.set_defaultFailedCallback = function(value) { tempuri.org.PhotoWebService._staticInstance.set_defaultFailedCallback(value); }

   36 tempuri.org.PhotoWebService.get_defaultFailedCallback = function() { return tempuri.org.PhotoWebService._staticInstance.get_defaultFailedCallback(); }

   37 tempuri.org.PhotoWebService.set_path("/AWS/PhotoWebService.svc");

   38 tempuri.org.PhotoWebService.DeleteImage = function(imageId, onSuccess, onFailed, userContext) { tempuri.org.PhotoWebService._staticInstance.DeleteImage(imageId, onSuccess, onFailed, userContext); }

   39 tempuri.org.PhotoWebService.GetImagesForUser = function(onSuccess, onFailed, userCon
text) { tempuri.org.PhotoWebService._staticInstance.GetImagesForUser(onSuccess, onFailed, userContext); }

   40 tempuri.org.PhotoWebService.UpdateImage = function(item, imageId, onSuccess, onFailed, userContext) { tempuri.org.PhotoWebService._staticInstance.UpdateImage(item, imageId, onSuccess, onFailed, userContext); }

   41 var gtc = Sys.Net.WebServiceProxy._generateTypedConstructor;

   42 Type.registerNamespace(‘FSWebApp’);

   43 if (typeof (FSWebApp.ImageItem) === ‘undefined’) {

   44     FSWebApp.ImageItem = gtc("ImageItem:http://schemas.datacontract.org/2004/07/FSWebApp&quot;);

   45     FSWebApp.ImageItem.registerClass(‘FSWebApp.ImageItem’);

   46 }

%d bloggers like this: