Consume Json REST service with WCF and dynamic object response
Note: this demo is based off of the @Task project management tool’s REST API.
Define the contract:
[ServiceContract] public interface IAtTaskService { [OperationContract] [WebGet( UriTemplate = "/api/login?username={username}&password={password}", RequestFormat = WebMessageFormat.Json)] IDictionary<string, object> Login(string username, string password); }
Run the code:
public IDictionary<string, object> DoThis(string username, string pword) { using (var cf = new WebChannelFactory<IAtTaskService>( new Uri("https://mycompaniesurl.attask-ondemand.com/attask/"))) { var s = cf.CreateChannel(); var token = s.Login(username, pword); return token; } }
The above seemed really nice to be able to consume services but the result was always empty. I tried using strongly typed objects in the result, made sure it wasn’t an HTTPS issue, used the normal WCF configuration approach, etc… but couldn’t get it to work.
The ‘Fix’
I quite like the way that WCF has UriTemplates and you can just attribute up an interface and then make strongly typed calls to access your services so i decided I’d just make that work with my own implementation. Again, perhaps I was ‘doing it wrong’ or whatever, but the below implementation is pretty cool and also lets you get JSON results as a dynamic object :)
Define the contract:
This is pretty much the same as above, but we have a dynamic response
[ServiceContract] public interface IAtTaskService { [OperationContract] [WebGet( UriTemplate = "/api/login?username={username}&password={password}", RequestFormat = WebMessageFormat.Json)] dynamic Login(string username, string password); }
The JsonRestService base class
I created a class that lets you specify you contract service as a generic argument and then provides you with a method called “Send” which is defined a:
protected dynamic Send(Func<T, dynamic> func)
This class will manage all of the http requests and use the WCF attributes applied to your contract to generate the correct URLs and parameters (source code at the bottom of this article)
Define a standard service
So to consume the REST services, we just create a ‘real’ service class such as:
public class AtTaskService : JsonRestService<IAtTaskService>, IAtTaskService { public AtTaskService(string serviceUrl) : base(serviceUrl) { } public dynamic Login(string username, string password) { return Send(x => x.Login(username, password)); } }
As you can see, the “Login” method just calls the underlying “Send” method which uses a strongly typed delegate. The Send method then inspects the delegate and generates the correct URL with parameters based on the WCF attributes.
Using the service
So now, to use the new service we can do something like:
[TestMethod] public void AtAtask_Login() { //Arrange var svc = new AtTaskService("https://mycompanyname.attask-ondemand.com/attask/"); //Act var token = svc.Login("MyUserName", "mypassword"); //Assert Assert.IsTrue(token != null); Assert.IsTrue(token.data != null); Assert.IsTrue(token.data.userID != null); }
Too easy!
As I mentioned earlier, I would have assumed that WCF should handle this out of the box, but for the life of my I couldn’t get it to return any results. Whether it was a request issue or a parsing issue, or whatever i have no idea. In the meantime, here’s the source code for the JsonRestService class which will allow you to do the above. The source requires references to Json.Net and Aaron-powell.dynamics
JsonRestService source
Disclaimer: I made this today in a couple of hours, I’m sure there’s some code in here that could be refactored to be nicer
public class JsonRestService<T> { public string ServiceUrl { get; private set; } public JsonRestService(string serviceUrl) { ServiceUrl = serviceUrl; } /// <summary> /// Queries the WCF attributes of the method being called, builds the REST URL and sends the http request /// based on the WCF attribute parameters. When the JSON response is returned, it is changed to a dynamic object. /// </summary> /// <param name="func"></param> /// <returns></returns> protected dynamic Send(Func<T, dynamic> func) { //find the method on the main type that is being called... i'm sure there's a better way to do this, //but this does work. //This will not work if there are methods with overloads. var methodNameMatch = Regex.Match(func.Method.Name, "<(.*?)>"); if (!methodNameMatch.Success && methodNameMatch.Groups.Count == 2) { throw new MissingMethodException("Could not find method " + func.Method.Name); } var realMethodName = methodNameMatch.Groups[1].Value; var m = typeof(T).GetMethods().Where(x => x.Name == realMethodName).SingleOrDefault(); if (m == null) { throw new MissingMethodException("Could not find method" + realMethodName + " on type " + typeof(T).FullName); } //now that we have the method, find the wcf attributes var a = m.GetCustomAttributes(false); var webGet = a.OfType<WebGetAttribute>().SingleOrDefault(); var webInvoke = a.OfType<WebInvokeAttribute>().SingleOrDefault(); var httpMethod = webGet != null ? "GET" : webInvoke != null ? webInvoke.Method : string.Empty; if (string.IsNullOrEmpty(httpMethod)) { throw new ArgumentNullException("The WebGet or WebInvoke attribute is missing from the method " + realMethodName); } //now that we have the WCF attributes, build the REST url based on the url template and the //method being called with it's parameters var urlTemplate = webGet != null ? webGet.UriTemplate : webInvoke.UriTemplate; var urlWithParams = GetUrlWithParams(urlTemplate, func); var url = ServiceUrl + urlWithParams; //Do the web requests string output; if (httpMethod == "GET") { output = HttpGet(url); } else { //need to move the query string params to the http parameters var parts = url.Split('?'); output = HttpInvoke(parts[0], httpMethod, parts.Length > 1 ? parts[1] : string.Empty); } //change the response to json and then dynamic var stringReader = new StringReader(output); var jReader = new JsonTextReader(stringReader); var jsonSerializer = new JsonSerializer(); var json = jsonSerializer.Deserialize<Dictionary<string, object>>(jReader); return json.AsDynamic(); } /// <summary> /// Updates the url template with the correct parameters /// </summary> /// <param name="template"></param> /// <param name="expression"></param> /// <returns></returns> private static string GetUrlWithParams(string template, Func<T, dynamic> expression) { //parse the template, get the matches foreach (var m in Regex.Matches(template, @"\{(.*?)\}").Cast<Match>()) { if (m.Groups.Count == 2) { var m1 = m; //find the fields based on the expression(Func<T>), get their values and replace the tokens in the url template var field = expression.Target.GetType().GetFields().Where(x => x.Name == m1.Groups[1].Value).Single(); template = template.Replace(m.Groups[0].Value, field.GetValue(expression.Target).ToString()); } } return template; } /// <summary> /// Do an Http GET /// </summary> /// <param name="uri"></param> /// <returns></returns> private static string HttpGet(string uri) { var webClient = new WebClient(); var data = webClient.DownloadString(uri); return data; } /// <summary> /// Do an Http POST/PUT/etc... /// </summary> /// <param name="uri"></param> /// <param name="method"></param> /// <param name="parameters"></param> /// <returns></returns> private static string HttpInvoke(string uri, string method, string parameters) { var webClient = new WebClient(); var data = webClient.UploadString(uri, method, parameters); return data; } }