Imagine You have a Custom API in the Azure Mobile Service. In this post this is Order-API. The idea is to show in this post
how to implement Mobile Service operations as a Custom API and how to consume them from the client side. As client I will use in this post a Windows Store App.
Fortunately, the same methodic at the client side can be used on any other supported platform. The service itself will be implemented .NET C#.
In general a custom API on the Azure Mobile Platform is an implementation of WebApi.
That means we will implement the class which derives from ApiController. Here is an example of Order custom API.
public class OrderController : ApiController
This custom API supports some of CRUD operations on entity Order. It does not support all CRUD operations, because I didn’t implement all oft them. I didn’t do that,
because it is not necessary to implement all of them to show how to consume the service.
public class Order { public int Id { get; set; } public double Amount { get; set; } public string ProductName { get; set; } } |
Following code snippet shows the sample implementation of the order API. Please do not focus on implementation of operations. The only important thing in this example is the
signature of operation, which is defined by HTTP method, input arguments and output result.
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; namespace daenet.Controllers { public class OrderController : ApiController { [HttpGet] public IQueryable<Order> GetOrders(string prodName) { if (String.IsNullOrEmpty(prodName)) return m_Orders.AsQueryable(); else return m_Orders.Where(o => o.ProductName.Contains(prodName)).AsQueryable(); } [HttpGet] public IQueryable<Order> GetOrdersWithIdGratherThan(int idBiggerThan) { return m_Orders.Where(o => o.Id > idBiggerThan).AsQueryable(); } [HttpPost] public Order PostOrder(Order order) { if (order == null) return null; var existingOrder = m_Orders.FirstOrDefault(o=>o.Id == order.Id); if (existingOrder != null) { existingOrder = order; return order; } else return null; } } } |
To consume these methods on the client side there at least two approaches:
1. The simple, but limited method
2. The harder, but unlimited method.
How to consume Get operations?
var orders = await App.MobileService.InvokeApiAsync<IEnumerable<Order>>("Order", HttpMethod.Get, new Dictionary<string, string>() { { "prodName", "Prod" } }); | It gets all orders with product name '*Prod*'. It Invokes operation GetOrders(string prodName), by using of helper method InvokeApiAsync. |
var orders = await App.MobileService.InvokeApiAsync<IEnumerable<Order>>("Order", HttpMethod.Get, new Dictionary<string, string>() { { "idBiggerThan", "2" } }); | It Invokes operation GetOrdersWithIdGratherThan(int prodName), by using of helper method InvokeApiAsync. The routing will forward request to GetOrdersWithIdGratherThan instead of GetOrders. Decision is made by argument name idBiggerThan. |
var orders = await App.MobileService.InvokeApiAsync<IEnumerable<Order>>("Order", HttpMethod.Get, new Dictionary<string, string>()); | Note that dictionary should net be empty if there is no any method without arguments. Thrown: MobileServiceInvalidOperationException
|
var orders = await App.MobileService.InvokeApiAsync<IEnumerable<Order>>("Order", HttpMethod.Get, null); | You cannot also use NULL as argument.
|
How to use custom serialization ?
If you have service operations which expect more complex arguments in the HTTP content, you will have to more work.
I usually use helper methods for serialization and deserialization of arguments.
public static T Deserialize<T>(string jsonStringToDeserialize) { if (string.IsNullOrEmpty(jsonStringToDeserialize)) { throw new ArgumentException("jsonStringToDeserialize must not be null"); } MemoryStream ms = null; ms = new MemoryStream(Encoding.UTF8.GetBytes(jsonStringToDeserialize)); DataContractJsonSerializerSettings settings = new DataContractJsonSerializerSettings(); settings.DateTimeFormat = new System.Runtime.Serialization.DateTimeFormat("yyyy-MM-ddTHH:mm:ss"); DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T), settings); return (T)serializer.ReadObject(ms); } public static Stream Serialize<T>(T objectToSerialize) where T : class { if (objectToSerialize == null) { throw new ArgumentException("objectToSerialize must not be null"); } MemoryStream ms = null; DataContractJsonSerializerSettings settings = new DataContractJsonSerializerSettings(); settings.DateTimeFormat = new System.Runtime.Serialization.DateTimeFormat("yyyy-MM-ddTHH:mm:ss"); DataContractJsonSerializer serializer = new DataContractJsonSerializer(objectToSerialize.GetType(), settings); ms = new MemoryStream(); serializer.WriteObject(ms, objectToSerialize); ms.Seek(0, SeekOrigin.Begin); return ms; } } |
Now, we can use these to methods when we need to manipulate content of all requests and responses.
Following examples show how to consume operations shown above with using native HttpClient. This would be the harder way.
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("X-ZUMO-APPLICATION", "***KEY***"); var uri = String.Format("{0}api/order?prodName={1}", App.MobileService.ApplicationUri.AbsoluteUri, "Prod"); var res = await client.GetAsync(new Uri(uri)); var retrievedOrders = Deserialize<IEnumerable<Order>>(await res.Content.ReadAsStringAsync()); | It gets all orders with product name '*Prod*'. It Invokes operation GetOrders(string prodName) by using custom serialization and native HttpClient. Note that the header with key does not has to be applied when debugging on the local box. |
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-ZUMO-APPLICATION", "***KEY***"); uri = String.Format("{0}api/order?idBiggerThan={1}", App.MobileService.ApplicationUri.AbsoluteUri, 2); res = await client.GetAsync(new Uri(uri)); retrievedOrders = Deserialize<IEnumerable<Order>>(await res.Content.ReadAsStringAsync()); | It Invokes operation GetOrdersWithIdGratherThan(int prodName), by using custom serialization and native HttpClient. Note that the header with key does not has to be applied when debugging on the local box. |
How to consume Post operations?
Order o1 = new Order() { Id = 1, Amount = 100, ProductName = "Product 1" }; client = new HttpClient(); uri = String.Format("{0}api/order/", App.MobileService.ApplicationUri.AbsoluteUri); client.DefaultRequestHeaders.Add("X-ZUMO-APPLICATION", "***KEY***"); StreamContent content = new StreamContent(Serialize<Order>(o1)); content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); var response = await client.PostAsync(new Uri(uri), content); | This code snippet shows how to invoke a POST operation which expects a complex type argument. If the argument is a list of primitives you can use InvokeApiAsync as shown in Get-samples. However when designing services it is better to use complex types. When you want to change number of arguments routing might fail if client version does not math number of arguments. When using complex types like Order in this sample you can extend the type with additional properties without of changing of operation signature. Unknown properties on the type will be simply ignored.
|
Posted
Apr 29 2014, 07:57 AM
by
Damir Dobric