I have been working for the past couple of days working with WCF’s JSON support and ExtJS. I had some trouble finding good examples beyond sending and retrieving simple data types. After spending some time searching and one forum post, I finally got a complete prototype up and going.
I am going to create a simple PersonService to show the functionality. I will start out by creating two DataContract’s that we will use with our PersonService.
1: [DataContract]
2: public class Person
3: {
4: private Guid _id = Guid.NewGuid();
5: private string _firstName;
6: private string _lastName;
7: private DateTime _dateOfBirth;
8: private Address _address;
9: private List<Person> _children;
10:
11: [DataMember]
12: public Guid Id
13: {
14: get { return _id; }
15: set { _id = value; }
16: }
17:
18: [DataMember]
19: public string FirstName
20: {
21: get { return _firstName; }
22: set { _firstName = value; }
23: }
24:
25: [DataMember]
26: public string LastName
27: {
28: get { return _lastName; }
29: set { _lastName = value; }
30: }
31:
32: [DataMember]
33: public DateTime DateOfBirth
34: {
35: get { return _dateOfBirth; }
36: set { _dateOfBirth = value; }
37: }
38:
39: [DataMember]
40: public Address Address
41: {
42: get { return _address; }
43: set { _address = value; }
44: }
45:
46: [DataMember]
47: public List<Person> Children
48: {
49: get { return _children; }
50: set { _children = value; }
51: }
52:
53: }
54:
55: [DataContract]
56: public class Address
57: {
58:
59: private string _address1;
60: private string _address2;
61: private string _city;
62: private string _state;
63: private string _zipCode;
64:
65: [DataMember]
66: public string Address1
67: {
68: get { return _address1; }
69: set { _address1 = value; }
70: }
71:
72: [DataMember]
73: public string Address2
74: {
75: get { return _address2; }
76: set { _address2 = value; }
77: }
78:
79: [DataMember]
80: public string City
81: {
82: get { return _city; }
83: set { _city = value; }
84: }
85:
86: [DataMember]
87: public string State
88: {
89: get { return _state; }
90: set { _state = value; }
91: }
92:
93: [DataMember]
94: public string ZipCode
95: {
96: get { return _zipCode; }
97: set { _zipCode = value; }
98: }
99:
100: }
I have three complex types on my Person entity: a DateTime, an Address, and a List<Person>. I have done this so that I can show how these work. Now, I define the ServiceContract for my Person service.
1: [ServiceContract]
2: public interface IPersonService
3: {
4:
5: [OperationContract]
6: [WebInvoke(BodyStyle = WebMessageBodyStyle.Wrapped,
7: RequestFormat = WebMessageFormat.Json,
8: ResponseFormat = WebMessageFormat.Json,
9: UriTemplate = "/Create")]
10: Guid Create(Person person);
11:
12: [OperationContract]
13: [WebInvoke(BodyStyle = WebMessageBodyStyle.Bare,
14: RequestFormat = WebMessageFormat.Json,
15: ResponseFormat = WebMessageFormat.Json,
16: UriTemplate = "/Get")]
17: Person Get(int id);
18:
19: [OperationContract]
20: [WebInvoke(BodyStyle = WebMessageBodyStyle.Wrapped,
21: RequestFormat = WebMessageFormat.Json,
22: ResponseFormat = WebMessageFormat.Json,
23: UriTemplate = "/GetAll")]
24: List<Person> GetAll();
25:
26: }
You will have to add a reference to System.ServiceModel.Web in your project for this to compile. A couple things to note about what I implemented. You can WebMessageFormat.Xml to send use XML as the payload for your request and response. The WebGet (as opposed to WebInvoke) attribute allows clients to use your services using HTTP GET. However, if you do not have the requirement for cross domain clients accessing your wbe services, then you should use the WebInvoke attribute. Also, WebGet does not support binding parameters to complex types.
Here’s a simple implementation of our ServiceContract that returns some sample data. For brevity, I am doing some checking to make sure the serialization occurs but of course you would actually use some integration tests.
1: public class PersonService : IPersonService
2: {
3:
4: public Guid Create(Person person)
5: {
6: NotNull(person.FirstName, "FirstName");
7: NotNull(person.LastName, "LastName");
8: NotEmpty(person.Children);
9: return Guid.NewGuid();
10: }
11:
12: public Person Get(int id)
13: {
14: if (id != 1) throw new ArgumentException("Expected 1 for ID");
15: return new Person()
16: {
17: FirstName = "John",
18: LastName = "Smith",
19: DateOfBirth = DateTime.Now,
20: Address = new Address()
21: {
22: Address1 = "123 Capitol St.",
23: Address2 = "Suite 10",
24: City = "Indianapolis",
25: State = "IN",
26: ZipCode = "46204"
27: },
28: Children = GetAll()
29: };
30: }
31:
32: public List<Person> GetAll()
33: {
34: return new List<Person>
35: {
36: new Person() { FirstName = "Steve", LastName = "Smith", DateOfBirth = DateTime.Now },
37: new Person() { FirstName = "Sally", LastName = "", DateOfBirth = DateTime.Now }
38: };
39: }
40:
41: private static void NotNull<T>(T o, string paramName) where T : class
42: {
43: if (o == null) throw new ArgumentNullException(paramName);
44: }
45:
46: private static void NotEmpty<T>(ICollection<T> collection)
47: {
48: if (collection == null || collection.Count == 0) throw new ArgumentException("Collection was empty");
49: }
50:
51: }
And the configuration for our service.
1: <system.serviceModel>
2: <behaviors>
3: <endpointBehaviors>
4: <behavior name="jsonBehavior">
5: <webHttp />
6: </behavior>
7: </endpointBehaviors>
8: <serviceBehaviors>
9: <behavior name="WebService1.PersonServiceBehavior">
10: <serviceMetadata httpGetEnabled="true"/>
11: <serviceDebug includeExceptionDetailInFaults="true"/>
12: </behavior>
13: </serviceBehaviors>
14: </behaviors>
15: <services>
16: <service behaviorConfiguration="WebService1.PersonServiceBehavior" name="WebService1.PersonService">
17: <endpoint behaviorConfiguration="jsonBehavior" address="" binding="webHttpBinding" contract="WebService1.IPersonService"/>
18: <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
19: </service>
20: </services>
21: </system.serviceModel>
Now for the fun part, actually working with our services. Whatever AJAX library you are using, you have to make sure that your POSTs are sending the header: ‘application/json’. Unless you change this behavior explicilty, ExtJS by default sends a default header like x-form-urlencoded with your posts. So when I added application/json as a heaeder, it would send ‘x-form-urlencoded,application/json’ as the header. This does not work — WCF will simply ignore your requests. I corrected this by setting ExtJS to only send ‘application/json’ (see code below).
1: <script type="text/javascript" src="ext-base.js"></script>
2: <script type="text/javascript" src="ext-all-debug.js"></script>
3: <script type="text/javascript" language="javascript">
4: Ext.onReady(function() {
5: Ext.lib.Ajax.defaultPostHeader = 'application/json';
6:
7: var child = {
8: person: {
9: FirstName: 'Tom',
10: LastName: 'Doe',
11: DateOfBirth: new Date(),
12: }
13: }
14:
15: var request = {
16: person: {
17: FirstName: 'Jane',
18: LastName: 'Doe',
19: DateOfBirth: '\/Date(62831853071)\/',
20: Address: {
21: Address1: '101 Capital St.',
22: Address2: 'Suite 100',
23: City: 'Indpls',
24: State: 'IN',
25: ZipCode: '46220'
26: },
27: Children: [ child, child ]
28: }
29: };
30:
31: var onFailure = function(r, opts) {
32: Ext.get("errors").insertHtml('afterend', '<br/><br/>' + r.responseText, false);
33: }
34:
35: Ext.Ajax.request({
36: url: '/PersonService.svc/Create',
37: method: 'POST',
38: params: Ext.util.JSON.encode(request),
39: success: function(response, options) {
40: Ext.get('create-p').update(response.responseText);
41: },
42: failure: onFailure
43: });
44: Ext.Ajax.request({
45: url: '/PersonService.svc/Get',
46: method: 'POST',
47: params: 1,
48: success: function(response, options) {
49: Ext.get('get-p').update(response.responseText);
50: },
51: failure: onFailure
52: });
53: Ext.Ajax.request({
54: url: 'PersonService.svc/GetAll',
55: method: 'POST',
56: success: function(response, options) {
57: Ext.get('getall-p').update(response.responseText);
58: },
59: failure: onFailure
60: });
61: });
62: </script>
63: <h3>Create:</h3>
64: <p id="create-p"></p>
65: <h3>Get:</h3>
66: <p id="get-p"></p>
67: <h3>GetAll:</h3>
68: <p id="getall-p">
69: </p>
70: <p id="errors">
71: </p>
I’ll leave it up to you to see the output, but you should see the JSON representation of the objects returned by the service.
A couple of other items to note. If you are using Ext forms, just name your form fields with the same values as the properties on your objects. Then if you want to serialize a form as a parameter, just use: { ‘person’ : form.getValues(false) }. I am reusing the same form often for insert and update, so sometimes I have to remove a parameter. When I want to do that, I use the Javascript ‘delete’ keyword to remove the property. Also, I highly recommend using Firefox along with Firebug and Tamper Data to debug the communication between your services and the page. Tamper Data lets you change the values of your POST parameters (like IE Fiddler). The WCF Service Trace Viewer is also very helpful.
I need to investigate wiring the WCF services to the Enterprise Library’s Validation Application Block. It ships with WCF integration, but I have not tested using it with JSON and ExtJS forms.
Keep in mind that you can always use ASP.NET AJAX and ScriptManager to generate Javascript proxies for you. The reason I choose to go this route was simply because I did not want to introduce a dependency on ASP.NET for my application because I am using ExtJS. For most scenarios where you are already using ASP.NET AJAX, it is probably the best solution for getting started.
References: