Yes, I know it had been a long time since I’ve blogged, and even longer since I’ve blogged any decent technical content. But I have a feeling that the drought is over, and a series of tech content is on the way ;)
This is the tale of how I spent a good part of a week trying to build something “the right way” only to be reminded that “the right way” is determined by your context (which way you want to look at something). You see, I’m an old school web service developer, and with that, teaching developers that the way to build services is to make sure not to forget that you are sending messages, not executing remote procedure calls. Way back when, people use to argue, do you build services code first, or contract first, and I came to the conclusion that it really didn’t matter as long as you designed them Message First. But I must not have done a good job spreading the word, because the overwhelming majority of Service code out there seems to be written in a RPC style. It still tries to encourage the developer to write the same style code, be it a call to a function within the same App Domain, or to Service in another App Domain (or even another machine running a totally different framework).
So here’s how this starts. I decide that I want more “modern” look and feel to administer ASP.Net Membership, and to do that, I want to use jQuery and WCF. Rick Strahl has done some excellent work getting jQuery and WCF JSON to work together, but like so many other examples out there, his examples use WCF’s DataContracts, and don’t implement a more explicit messaging model. I eventually want to open source this, so I give it the project name w00ton. I start to build out a WCF UserAdministrationService, write some integration tests (using NUnit, .Net client and host it in a Web Project), and everything is cool. I have explicit Message, Data, and Fault contracts, in a Service Factory style (except I put them all in one project, instead of separate projects like SF does). So here’s what the code looks like:
The Service Interface
namespace w00ton.MembershipAdministration.ServiceContracts
{
[ServiceContract(Namespace = "urn:w00ton.MembershipAdministration.ServiceContracts",
Name = "UserAdministrationService",
SessionMode = SessionMode.NotAllowed,
ProtectionLevel = ProtectionLevel.None)]
public interface IUserAdministrationService
{
[FaultContract(typeof(Faults.DuplicateEmailAddressFault))]
[FaultContract(typeof(Faults.DuplicateUserNameFault))]
[FaultContract(typeof(Faults.InvalidEmailAddressFault))]
[FaultContract(typeof(Faults.InvalidUserNameFault))]
[FaultContract(typeof(Faults.InvalidPasswordFault))]
[OperationContract(IsTerminating = false,
IsInitiating = true,
IsOneWay = false,
AsyncPattern = false,
Action = "CreateUser",
ProtectionLevel = ProtectionLevel.None)]
Messages.CreateUserResponse CreateUser(Messages.CreateUserRequest request);
[OperationContract(IsTerminating = false,
IsInitiating = true,
IsOneWay = false,
AsyncPattern = false,
Action = "GetAllUsersByPage",
ProtectionLevel = ProtectionLevel.None)]
Messages.GetAllUsersByPageResponse GetAllUsersByPage(Messages.GetAllUsersByPageRequest request);
}
}
Message Contract
namespace w00ton.MembershipAdministration.ServiceContracts.MessageContracts
{
[MessageContract(IsWrapped = false)]
public class GetAllUsersByPageRequest
{
[MessageBodyMember(Name = "Body")]
public GetAllUsersByPageRequestBody Body { get; set; }
}
}
Data Contract
namespace w00ton.MembershipAdministration.ServiceContracts.DataContracts
{
[DataContract(Namespace = "urn:w00ton.MembershipAdministration.DataContracts",
Name = "GetAllUsersByPageRequestBody")]
public class GetAllUsersByPageRequestBody : IExtensibleDataObject
{
[DataMember(Name = "PageIndex", IsRequired = true, Order = 0)]
public int PageIndex { get; set; }
[DataMember(Name = "PageSize", IsRequired = true, Order = 1)]
public int PageSize { get; set; }
#region Implementation of IExtensibleDataObject
public ExtensionDataObject ExtensionData
{
get; set;
}
#endregion
}
}
The code style I’m using here is something I call the MessageBody style. I’ll have to write up that style, and why I use it in another blog post. It’s just something I’ve developed over time that seems to work for me.
.Net Client Test
[TestFixture]
public class GetUsersTestFixture
{
[Test]
public void Must_Be_Able_To_Get_Users()
{
using (UserAdministrationServiceClient Client = new UserAdministrationServiceClient())
{
var Response =
Client.GetAllUsersByPage(new GetAllUsersByPageRequest
{
Body = new Data.GetAllUsersByPageRequestBody
{
PageIndex = 0,
PageSize = 10
}
});
Assert.That(Response.Body.MembershipUsers != null);
}
}
}
So, the test works and the WCF service seems to be fine, so I decide to try to call it using ASP.Net AJAX and the code gen’d Proxy class:
function GetAllUsersByPage(pageIndex, pageSize)
{
var tn = { "PageIndex": pageIndex, "PageSize": pageSize };
w00ton.MembershipAdministration.ServiceContracts.UserAdministrationService.GetAllUsersByPage(tn,
function(GetAllUsersByPageResponse)
{
alert(GetAllUsersByPageResponse);
}, onPageError
);
function onPageError(error)
{
alert("An error occurred:\r\n\r\n" + error.Message);
}
}
But when I test that code, it throws an error, “An Error Occured: Undefined”. So I check out what is being sent via Fiddler.
{"Body":{"PageIndex":0,"PageSize":5}}
Hmm, seems like the ASP.Net AJAX proxy automagically, adds the wrapping Body property. I didn’t put that in my JSON, ASP.Net AJAX did, and here is the complete error message:
{"ExceptionDetail":{"HelpLink":null,"InnerException":
{"HelpLink":null,"InnerException":null,
"Message":"The data contract type
'w00ton.MembershipAdministration.ServiceContracts.DataContracts.GetAllUsersByPageRequestBody'
cannot be deserialized because the required data members 'PageIndex, PageSize'
were not found."
So, it seems as though the JSON Serializer isn’t expecting the wrapping Body property, and chokes because it can’t find the 2 properties in the GetAllUsersByPageRequestBody DataContract. But I didn’t add the wrapping Body property, the ASP.Net AJAX code gen’d that in the proxy. So to prove it, I decide to call Sys.Net.WebServiceProxy directly, instead of going thru the generated version:
function GetAllUsersByPageDirect(pageIndex, pageSize)
{
var tn = { "PageIndex": pageIndex, "PageSize": pageSize };
Sys.Net.WebServiceProxy.invoke('../UserAdmin.svc', 'GetAllUsersByPage', false, tn,
function(GetAllUsersByPageResponse)
{
alert(GetAllUsersByPageResponse);
}, onPageError, null, 1000
);
}
And sure enough, it works!
So, I don’t know if I’m doing something wrong, because I’d expect that the MessageContract style would work with ASP.Net AJAX and JSON. I’d at least expect that someone else has bumped into this issue, but I can’t seem to find a blog entry or a question on a public newsgroup or discussion list. All I know is that I got it working with ASP.Net AJAX by skipping the code generated proxy, and that if you want to use jQuery’s AJAX the code is just about the same as the direct call in ASP.Net AJAX (using Rick’s ServiceProxy).
function GetAllUsersByPage(pageIndex, pageSize) {
UserAdminProxy.invoke("GetAllUsersByPage",
{
PageIndex: pageIndex,
PageSize: pageSize
}
,
function(FindUsersByNameResponse) {
// your code here
},
onPageError);
}
Just to prove that I wasn’t going insane, I rewrote the code using just DataContracts and no MessageContracts (just using the GetAllUsersByPageRequestBody) and sure enough, you do need to wrap the properties in GetAllUsersByPageRequestBody with a property of the same name as the parameter name used in the ServiceContract (which makes total sense, because that is the way every AJAX and WCF sample is written).
The key to getting this to work is knowing that when using MessageContracts the WCF JSON Serializer, it doesn’t behave the same way as if you use just DataContracts. It skips the Message and the MessageBody in both the request and the response (so do add them in or expect them in a response).
This sample code can be found here along with a presentation on using WCF & jQuery with WebForms.