次の方法で共有


Custom API in Azure Mobile Services – Client SDKs

In the last post I talked about the new custom API support in Azure Mobile Services. In that post I focused on the feature at the server side, but we also released updates to the client SDKs for all platforms to take advantage of that feature. In this post I’ll talk about how to use custom APIs from each of our supported platforms, to have some reference material available while the official documentation doesn’t get updated.

.NET (Portable Library)

A small parenthesis before APIs - the portable library for Windows Azure Mobile Services is now a stable NuGet package. If you were waiting to use it because you don’t trust pre-release packages, there’s no need to fear anymore :)

Ok, custom APIs. There is a new method (with many overloads) in the MobileServiceClient class – InvokeApiAsync. There are a lot of overloads, but they can be grouped in basically three categories: typed JSON calls (with serialization of parameters and responses), untyped JSON calls (where parameters and return types are in the Newtonsoft.Json.Linq namespace), and “raw” HTTP request, which can be used for calls with any type of content type (not only JSON, but also XML, text, basically anything). Let’s look at all of them:

Typed API calls

There are four overloads in this category:

  • Simple POST requests, with or without a request body
    • Task<T> InvokeApiAsync<T>(string apiName);
    • Task<U> InvokeApiAsync<T, U>(string apiName, T body);
  • Requests with any HTTP method, with or without request body, and with optional query string parameters
    • Task<T> InvokeApiAsync<T>(string apiName, HttpMethod method, IDictionary<string, string> parameters);
    • Task<U> InvokeApiAsync<T, U>(string apiName, T body, HttpMethod method, IDictionary<string, string> parameters);

The first pair is the simplest of them – send an optional request body to the service, via a POST call. Let’s get a simple example API – called “orderpizza”, which exports a handler for the POST verb:

  1. exports.post = function (req, res) {
  2.     var size = req.body.Size;
  3.     var flavor = req.body.Flavor;
  4.     var user = req.body.UserPhone;
  5.     var mySystem = require('./myPizzaSystem');
  6.     mySystem.orderPizza(user, flavor, size, function (error, orderNumber, deliveryTime) {
  7.         if (error) {
  8.             res.send(500, { error: 'There was a problem with your order' });
  9.         } else {
  10.             res.send(200, { OrderNumber: orderNumber, EstimatedDelivery: deliveryTime });
  11.         }
  12.     });
  13. }

Now we can call it directly from our client:

  1. public sealed partial class MainPage : Page
  2. {
  3.     private async void btnStart_Click(object sender, RoutedEventArgs e)
  4.     {
  5.         try
  6.         {
  7.             var client = new MobileServiceClient("https://myservice.azure-mobile.net/", "mykey");
  8.             var myOrder = new PizzaOrder
  9.             {
  10.                 Size = "Large",
  11.                 Flavor = "Four Cheeses",
  12.                 UserPhone = "555-555-1234"
  13.             };
  14.             var orderResult = await client.InvokeApiAsync<PizzaOrder, PizzaOrderResponse>("orderPizza", myOrder);
  15.             InformUser("My pizza will be delivered at {0}", orderResult.EstimatedDelivery);
  16.         }
  17.         catch (Exception ex)
  18.         {
  19.             InformUser("Error: {0}", ex);
  20.         }
  21.     }
  22. }
  23.  
  24. public class PizzaOrder
  25. {
  26.     public string Size { get; set; }
  27.     public string Flavor { get; set; }
  28.     public string UserPhone { get; set; }
  29. }
  30. public class PizzaOrderResponse
  31. {
  32.     public int OrderNumber { get; set; }
  33.     public DateTime EstimatedDelivery { get; set; }
  34. }

The second pair allows us to customize the request a little more. We can both change the HTTP verb, and we can also pass some additional parameters which are passed in the request query string, as shown in the example below (talking to the calculator API shown in the previous post).

  1. public sealed partial class MainPage : Page
  2. {
  3.     private async void btnStart_Click(object sender, RoutedEventArgs e)
  4.     {
  5.         try
  6.         {
  7.             var client = new MobileServiceClient("https://myservice.azure-mobile.net/", "mykey");
  8.             var arguments = new Dictionary<string, string>
  9.             {
  10.                 { "x", "7" }, {"y", "9" }
  11.             };
  12.             var calcResult = await client.InvokeApiAsync<CalculatorResult>("calculator/add", HttpMethod.Get, arguments);
  13.             InformUser("Result: {0}", calcResult.result);
  14.         }
  15.         catch (Exception ex)
  16.         {
  17.             InformUser("Error: {0}", ex);
  18.         }
  19.     }
  20. }
  21.  
  22. public class CalculatorResult
  23. {
  24.     public double result { get; set; }
  25. }

One thing which should be noted: the ‘apiName’ parameter doesn’t need to be exactly the same name as the API on the server – in the case below, the route parameter is passed as the name. You can think of that first parameter as the path in the URL of the request.

Raw JSON API calls

The next set of overloads is the equivalent to the first, with the exception that instead of the generic typed parameters (and return values), we have

  • Simple POST requests, with or without a request body
    • Task<JToken> InvokeApiAsync(string apiName);
    • Task<JToken> InvokeApiAsync(string apiName, JToken body);
  • Requests with any HTTP method, with or without request body, and with optional query string parameters
    • Task<JToken> InvokeApiAsync(string apiName, HttpMethod method, IDictionary<string, string> parameters);
    • Task<JToken> InvokeApiAsync(string apiName, JToken body, HttpMethod method, IDictionary<string, string> parameters);

In the case of the pizza order system, if you didn’t want to create the types to be serialized, it could be implemented simply as follows:

  1. public sealed partial class MainPage : Page
  2. {
  3.     private async void btnStart_Click(object sender, RoutedEventArgs e)
  4.     {
  5.         try
  6.         {
  7.             var client = new MobileServiceClient("https://myservice.azure-mobile.net/", "mykey");
  8.             var myOrder = new JObject();
  9.             myOrder.Add("Size", "Large");
  10.             myOrder.Add("Flavor", "Four Cheeses");
  11.             myOrder.Add("UserPhone", "555-555-1234");
  12.             var orderResult = await client.InvokeApiAsync("orderPizza", myOrder);
  13.             InformUser("My pizza will be delivered at {0}", orderResult["EstimatedDelivery"].Value<DateTime>());
  14.         }
  15.         catch (Exception ex)
  16.         {
  17.             InformUser("Error: {0}", ex);
  18.         }
  19.     }
  20. }

Or the call to the calculator:

  1. private async void btnStart_Click(object sender, RoutedEventArgs e)
  2. {
  3.     try
  4.     {
  5.         var client = new MobileServiceClient("https://myservice.azure-mobile.net/", "mykey");
  6.         var arguments = new Dictionary<string, string>
  7.         {
  8.             { "x", "7" }, {"y", "9" }
  9.         };
  10.         var calcResult = await client.InvokeApiAsync("calculator/add", HttpMethod.Get, arguments);
  11.         InformUser("Result: {0}", calcResult["result"].Value<double>());
  12.     }
  13.     catch (Exception ex)
  14.     {
  15.         InformUser("Error: {0}", ex);
  16.     }
  17. }

So, for simple, JSON-based requests or responses, those overloads should be enough for the large majority of the scenarios. But for scenarios where you need more fine-grained control on the request or response, or if you want to use non-JSON, then you need the “master” overload, which works for all scenarios.

Raw HTTP API calls

And the last overload uses the primitives from the System.Net.Http namespace to give full control

  • Task<HttpResponseMessage> InvokeApiAsync(string apiName, HttpContent content, HttpMethod method, IDictionary<string, string> requestHeaders, IDictionary<string, string> parameters);

For an example: if we have a service with an API which processes an image and returns a modified version of that image, we could call it like the example below.

  1. var client = new MobileServiceClient("https://myservice.azure-mobile.net/", "mykey");
  2. var imageBytes = await ChooseImage();
  3. var content = new ByteArrayContent(imageBytes);
  4. content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
  5. var response = await client.InvokeApiAsync("processImage", content, HttpMethod.Post, null, null);
  6. var resultBytes = await response.Content.ReadAsByteArrayAsync();
  7. await SaveProcessedImage(resultBytes);
  8. InformUser("Image update complete");

And that’s it for the managed code version of the custom API.

JavaScript (Windows Store and HTML/JS apps)

As an untyped language, the JavaScript implementation is simpler than the one for the managed client. In fact, there is but one method which we need to invoke custom APIs in JS clients:

  • invokeApi(apiName, options)

Where ‘apiName’ has the same meaning as in the managed methods, and options is an object which can contain the following properties:

  • method (string): the HTTP method used in the request. Defaults to ‘POST’ if not set
  • body (variable): the body of the request. For JSON-based requests, passing arrays or objects will have them serialized. For non-JSON requests, the body should be a string value and it will be sent “as-is” (or to be more precise, encoded as UTF-8). Defaults to an empty body.
  • headers (object): any HTTP headers which you want to send with the request. For non-JSON requests, the object should have a ‘Content-Type’ header with the appropriate value. For requests with a body, the content-type will be set to JSON if it’s not explicitly set.
  • parameters (object): any query string parameters which will be appended to the request

The method returns a promise which will be fulfilled when the request is complete. In the case of success, the parameter passed to the callback contains an object which extends the XMLHttpRequest object with an additional member, called ‘result’ which is the parsed response body, in case of a JSON response. In the case of error, the parameter passed to the callback contains a member called ‘request’, which contains the XMLHttpRequest object used in the request, where you can query for properties such as status or responseText (among others of that object). Below is an example of a JS client calling the pizza API mentioned in the previous section:

  1. var client = new WindowsAzure.MobileServiceClient('https://myservice.azure-mobile.net/', 'mykey');
  2. client.invokeApi('orderPizza', {
  3.     method: 'POST',
  4.     body: { Size: 'Large', Flavor: 'Four Cheeses', UserPhone: '555-555-1234' }
  5. }).done(function (response) {
  6.     informUser('My pizza will be delivered at ' + response.result.EstimatedDelivery.toString());
  7. }, function (error) {
  8.     var xhr = error.request;
  9.     informUser('Error - status code: ' + xhr.status + '; body: ' + xhr.responseText);
  10. });

Or in the case of a request to an API which routes XML requests to another server (well, why not? :), where we need to set some HTTP headers to values which we need, as shown below.

  1. var client = new WindowsAzure.MobileServiceClient('https://myservice.azure-mobile.net/', 'mykey');
  2. var body = '<s:Envelope xmlns:s="https://schemas.xmlsoap.org/soap/envelope/">' +
  3.     '<s:Body><Add xmlns="https://tempuri.org"><x>7</x><y>9</y></Add></s:Body>' +
  4.     '</s:Envelope>';
  5. client.invokeApi('callService', {
  6.     method: 'POST',
  7.     body: body,
  8.     headers: { SOAPAction: 'https://tempuri.org/ITest/Add', 'Content-Type': 'text/xml' }
  9. }).done(function (response) {
  10.     informUser('Result ' + response.responseText);
  11. }, function (error) {
  12.     var xhr = error.request;
  13.     informUser('Error - status code: ' + xhr.status + '; body: ' + xhr.responseText);
  14. });

And that’s it for JavaScript.

iOS

iOS is similar to JavaScript in that it doesn’t support JSON serialization of arbitrary types. As such, it has quite a simple API for invoking custom APIs as well, consisting of two methods:

  • invokeApi:(NSString *)apiName body:(id)body HTTPMethod:(NSString *)method parameters:(NSDictionary *)parameters headers:(NSDictionary *)headers completion:^(id result, NSHTTPURLResponse *response, NSError *error)
    • Passes the body parameter through the NSJSONSerialization to be serialized to the request body, and passes the response through the same class to be deserialized and returned to the caller on the completion block.
  • invokeApi:(NSString *)apiName data:(NSData *)data HTTPMethod:(NSString *)method parameters:(NSDictionary *)parameters headers:(NSDictionary *)headers completion:^(NSData *result, NSHTTPURLResponse *response, NSError *error)
    • Sends the data parameter as the request body directly, without applying any formatting, and sends the raw bytes of the result to the completion block.

And to continue on the pizza service, here’s how one would write code to order the pizza in an iOS application:

NSDictionary *order = @{@"Size": @"Large", @"Flavor": @"Four Cheeses", @"UserPhone": @"555-555-1234"};
[client invokeAPI:@"orderPizza" body:order HTTPMethod:@"POST" parameters:nil headers:nil completion:^(id result, NSHTTPURLResponse *response, NSError *error) {
if (error) {
[self informUser:@"Error: %@", error];
} else {
[self informUser:@"My pizza will be delivered at %@", [result objectForKey:@"EstimatedDelivery"]];
}
}];

And if you want to send / receive arbitrary data (non-JSON), you can use the other selector:

NSString *input = @"This is a string which will be passed to an API";
NSData *dataInput = [input dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *headers = @{@"Content-Type": @"text/plain"};
[client invokeAPI:@"reverseString" data:dataInput HTTPMethod:@"PUT" parameters:nil headers:headers completion:^(NSData *result, NSHTTPURLResponse *response, NSError *error) {
if (error) {
[self informUser:@"Error: %@", error];
} else {
NSString *reversedString = [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding];
[self informUser:@"Result: %@", reversedString];
}
}];

And in cases where there is no body in the request, you can simply pass ‘nil’ to it (in either of the methods), as in the example below.

[client invokeAPI:@"quoteOfTheDay" data:nil HTTPMethod:@"GET" parameters:nil headers:nil completion:^(NSData *result, NSHTTPURLResponse *response, NSError *error) {
if (error) {
[self informUser:@"Error: %@", error];
} else {
NSString *quoteOfTheDay = [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding];
[self informUser:@"Result: %@", quoteOfTheDay];
}
}];

A final note for the iOS client: the ‘response’ parameter passed to the completion blocks is where you’d get information such as the HTTP response headers, if necessary.

Android

With its typed serialization support, the android SDK is a lot similar to the implementation for the managed library, and it has the same nine overloads of the invokeApi method:

Typed API calls

As in the managed library, four overloads:

  • <E> void invokeApi(String apiName, Class<E> clazz, ApiOperationCallback<E> callback)
  • <E> void invokeApi(String apiName, Object body, Class<E> clazz, ApiOperationCallback<E> callback)
  • <E> void invokeApi(String apiName, String httpMethod, List<Pair<String, String>> parameters, Class<E> clazz, ApiOperationCallback<E> callback)
  • <E> void invokeApi(String apiName, Object body, String httpMethod, List<Pair<String, String>> parameters, Class<E> clazz, ApiOperationCallback<E> callback)

The first two send a ‘POST’ request with no query string parameters, while in the last two the caller can specify the HTTP method and also pass a list of query string parameters to be sent to the service. Continuing on our pizza order service, if we have two classes defined as follows:

  1. public class PizzaOrderResponse {
  2.     public int OrderNumber;
  3.     public Date EstimatedDelivery;
  4. }
  5.  
  6. public class PizzaOrder {
  7.     public String Size;
  8.     public String Flavor;
  9.     public String UserPhone;
  10. }

We can call the API to order a new pizza with the code below:

  1. PizzaOrder order = new PizzaOrder();
  2. order.Size = "Large";
  3. order.Flavor = "Four cheeses";
  4. order.UserPhone = "555-555-1234";
  5. mClient.invokeApi("orderPizza", order, PizzaOrderResponse.class, new ApiOperationCallback<PizzaOrderResponse>() {
  6.  
  7.     @Override
  8.     public void onCompleted(PizzaOrderResponse result,
  9.             Exception error, ServiceFilterResponse response) {
  10.         if (error == null) {
  11.             informUser("Pizza will be delivered at " + result.EstimatedDelivery.toString());                        
  12.         } else {
  13.             informUser("Error: " + error.toString());
  14.         }
  15.     }
  16.     
  17. });

Similarly, if we want to use a typed version of the invokeApi methods to make some calculations, we can define the class:

  1. public class CalculatorResult {
  2.     @SerializedName("result")
  3.     public double Result;
  4. }

And invoke it with the client:

  1. ArrayList<Pair<String, String>> parameters = new ArrayList<Pair<String, String>>();
  2. parameters.add(new Pair<String, String>("x", "7"));
  3. parameters.add(new Pair<String, String>("y", "13"));
  4. mClient.invokeApi("calculator/add", "GET", null, parameters, CalculatorResult.class, new ApiOperationCallback<CalculatorResult>() {
  5.  
  6.     @Override
  7.     public void onCompleted(CalculatorResult result,
  8.             Exception error, ServiceFilterResponse response) {
  9.         if (error == null) {
  10.             informUser("Result " + result.Result);                        
  11.         } else {
  12.             informUser("Error: " + error.toString());
  13.         }
  14.     }
  15.     
  16. });

As the previous example showed, annotations from the GSON library (like @SerializedName) will be honored when serializing / deserializing the calls.

Raw JSON API calls

For scenarios in which you either don’t want or cannot to create a type to pass arguments or receive results (minimizing number of types in project, schema now known at design time, etc.), you can use the JSON API calls, which use the JsonElement abstract class as the parameter to the ‘invokeApi’ and to the callback, in each of these four overloads:

  • void invokeApi(String apiName, ApiJsonOperationCallback callback)
  • void invokeApi(String apiName, JsonElement body, ApiJsonOperationCallback callback)
  • void invokeApi(String apiName, JsonElement body, String httpMethod, List<Pair<String, String>> parameters, ApiJsonOperationCallback callback)
  • void invokeApi(String apiName, String httpMethod, List<Pair<String, String>> parameters, ApiJsonOperationCallback callback)

As with the typed calls, the first two default to a ‘POST’ request, with no additional query string parameters, while in the last two the caller can specify the HTTP verb and a list of parameters to be sent in the query string of the request. In the callback, the result is passed as an instance of (a subclass of) JsonElement, as we can see in the pizza ordering example (last time, I swear) below

  1. JsonObject request = new JsonObject();
  2. request.addProperty("Size", "Large");
  3. request.addProperty("Flavor", "Four cheeses");
  4. request.addProperty("UserPhone", "555-555-1234");
  5. mClient.invokeApi("orderPizza", request, new ApiJsonOperationCallback() {
  6.     
  7.     @Override
  8.     public void onCompleted(JsonElement result, Exception error,
  9.             ServiceFilterResponse response) {
  10.         informUser("Pizza will be delivered at " +
  11.             result
  12.                 .getAsJsonObject()
  13.                 .get("ExpectedDelivery")
  14.                 .getAsString());
  15.     }
  16. });

I omitted the error handling in the example above for brevity, but it’s similar to the previous example.

Raw HTTP API calls

Finally, there are cases where we’re not dealing with JSON data; in this case, we can use the last overload of ‘invokeApi’, which gives the caller full control over the HTTP content (as a byte array) and headers.

  • void invokeApi(String apiName, byte[] content, String httpMethod, List<Pair<String, String>> requestHeaders, List<Pair<String, String>> parameters, ServiceFilterResponseCallback callback)

For completeness sake, an example of this overload:

  1. byte[] bytes = getImage();
  2. ArrayList<Pair<String, String>> headers = new ArrayList<Pair<String, String>>();
  3. headers.add(new Pair<String, String>("Content-Type", "image/png"));
  4. mClient.invokeApi("processImage", bytes, "POST", headers, null, new ServiceFilterResponseCallback() {
  5.  
  6.     @Override
  7.     public void onResponse(ServiceFilterResponse response,
  8.             Exception error) {
  9.         saveImage(response.getRawContent());
  10.     }
  11. });

And with this last Android overload we’re done with the client SDK support for custom APIs.

Wrapping up

I hope this clears some of the questions I’ve seen in the last few days on the forums. If you still have questions, please leave a comment or create a new thread in our MSDN Forum page.

Comments

  • Anonymous
    June 22, 2013
    Awesome article, thank you !!
  • Anonymous
    September 20, 2013
    The comment has been removed
  • Anonymous
    November 14, 2013
    i am using azure mobile service for my windows phone app, while inserting data i use the following call from my phone client:private async void SaveUser(){  try  {    await UserTable.InsertAsync(UserObject);  }  catch (MobileServiceInvalidOperationException ex)  {  }}this successfully insert the data, but i want to store the response of the insertasync call, how to get the response, please help.
  • Anonymous
    November 27, 2013
    @Kuldeep, see my response on the (I assume your) question on StackOverflow: stackoverflow.com/.../751090.@mr.dev, no, I haven't seen that error. If you have a small code which reproduces it, can you post here (or better, in the forums, so other people can see it) so we can investigate? Thanks!
  • Anonymous
    April 23, 2014
    Using this article I've created a Custom API that uses mssql to perform a query on a table and then perform some calculations on the results of that query of which I want to return the id and my calculation result as a object.The problem I have is I can't find a way to return my results to my Android app and map them to my Java object, it seems only to return the results from the mssql query rather than the custom results I return through response.send()Any help will be greatly appreciated!
  • Anonymous
    May 13, 2014
    @Nev, can you create a question in the MSDN forums with more details about your issue? Without seeing your server script and the code in Android you're using it's hard to identify the problem you're having.
  • Anonymous
    June 11, 2014
    how to deal with .net mobile services for custom api's?
  • Anonymous
    September 28, 2014
    I created a Custom API controller below with .Net backend. It always returns 0 record even there are multiple ones. But if I change  List<GroupUserByGID> to GroupUserByGID, it returns one record without any problem(I think it's the first record returned).Do you have any thought? Thanks,namespace MobileService.Controllers{   //public class GroupUserResult   //{   //    public int count;   //}   public class GroupUserByGID   {       //public string Id { get; set; }               public string groupID { get; set; }       public string userID { get; set; }       public bool isAdmin { get; set; }       public string groupName { get; set; }   }   public class GetGroupUserByGroupIDController : ApiController   {       public ApiServices Services { get; set; }     public List<GroupUserByGID> Get(string GroupID)       {           using (MobileServiceContext context = new MobileServiceContext())           {               // Get the database from the context.               var database = context.Database;               List<GroupUserByGID> result = new List<GroupUserByGID>();               //GroupUserByGID result = new GroupUserByGID();               string sql = "select gu.GroupID as groupID,gu.UserID as userID,gu.IsAdmin as isAdmin, g.Name as GroupName from MobileServiceOO.GroupUsers gu, MobileServiceOO.Groups g where gu.GroupID = @GroupID and gu.GroupID = g.Id";               result = database.SqlQuery<List<GroupUserByGID>>(sql, new SqlParameter("@GroupID&quot;, GroupID)).FirstOrDefault();               return result;           }       }}
  • Anonymous
    December 27, 2014
    I have a use case of doing multiple inserts in a single api call. Can i use the default api for this use case to do multiple inserts? Or do I need to create custom api? One lazy alternative is to loop through all the inserts and do them separately.
  • Anonymous
    December 28, 2014
    @Pramodh, you can do multiple inserts, but you'll need to wrap all the items in a single object (the item is expected to be an object), and write some script in the server side to handle those. The post at blogs.msdn.com/.../inserting-multiple-items-at-once-in-azure-mobile-services.aspx talks about this scenario.
  • Anonymous
    January 15, 2015
    How would you go about uploading a file with the JS SDK? Kinda like your example with the image processing, only for a web app written in HTML and JS. My actual use case is a user who uploads a file from his computer and then the file needs to be sent to the web server with a call to invokeAPI.