次の方法で共有


Azure Mobile Services managed client – portable libraries and breaking changes

Among the requested features in the various forums for Azure Mobile Services, the usage of portable libraries has popped up quite often. We now have that support, which unifies the managed clients for the Windows Store and Windows Phone 8 platforms – and we’re also adding support for Windows Phone 7.5 as well. Johan Laanstra wrote a great post explaining the architecture of the new packages, so I won’t repeat what he wrote.

However, since there were some differences between the Windows Phone 8 and the Windows Store SDKs, unifying them meant some breaking changes. We felt that the grief we’ll take for them is worth, given the gains we can get by the platform unification, and we also used this opportunity to make some other changes to the client SDK to make it more polished before the general availability of the service. This post will try to list to the best of my knowledge the changes which will need to be addressed for apps using the new client. Notice that we’ll still support the “non-portable” clients for some more time, but it will be discontinued likely before the Azure Mobile Services goes out of preview mode and into a general availability,

Breaking changes / functionality changes

This is the grouped list of changes, along with the updates one needs to make to their projects to account for them.

Serialization changes

This is the place where the majority of the changes were made. For the Windows Store SDK, conversion between the typed objects and the JSON which was actually transferred over the wire was done via a custom serializer which converted between the two formats. The serializer was fairly simple, and could only deal with simple types (numbers, strings, dates and,Boolean values). To support more complex types (unsupported primitives, arrays, complex objects), we’d need to provide a custom implementation of the IDataMemberJsonCoverter interface, and decorate the appropriate member with the [DataMemberJsonConverter] attribute. Even simple things such as enumerations were not supported. With this new client, all the custom serialization code goes away, as we’ll be using the JSON.NET serializer, and by that move we gain all of the support of that great serializer. That means that complex types, arrays, enumerations will just work. The new client also exposes the serializer settings which allow the developer to fine-tune the output data, including adding custom converters and changing policies such as default value serialization.

Now for the list of changes:

  • JSON.NET: that’s the serializer used for all platforms (not only for Windows Phone 8 as before). That causes the following behavioral changes from the current Windows Store SDK
    • JSON bool values cannot be parsed as integers; false was interpreted as 0, and true as 1; now an exception will be thrown. This has since been modified to preserve the original behavior.
    • Better / consistent exception messages for failed JSON parsing: there was a lot of code in the custom serializer for dealing with bad data; now this is all taken care of by JSON.NET, and its exception messages include more data such as the path in the JSON document, and position of the error
    • All floating point members (even those with integer values) will be serialized with a decimal point: a JSON.NET behavior. Before a float / double with value 2 would be serialized as {“number”:2}; now it will be serialized as {“number”:2.0}.
      • To maintain the previous behavior, one possibility is to use a custom JsonConverter class that first casts the values to integers if they are indeed integers.
    • Float (System.Single) will be serialized with its “native” precision: before a member of type float (Single) would be serialized as if it had double precision. For example, the object new MyType { fltValue = float.MinValue } would be serialized as {“fltValue”:1.4012984643248171E-45}; now it will be serialized as {"fltValue":1.401298E-45}.
      • To maintain the previous behavior, use a JsonConverter class that serializes float values as doubles (or change the property type to double)
    • Float and double values less than their respective Epsilon now deserialize as 0.0: before it used to throw an exception. Now deserializing something like {“dblValue:1.23E-1000} will cause the dblValue member to be set to zero, instead of an exception being thrown.
    • Fields are now serialized before properties: before properties were serialized before fields; now is the opposite. Since JSON objects are supposed to be unordered sets of members, this shouldn’t affect existing apps.
    • Finer control of serialization via settings: the MobileServiceClient now exposes the JsonSerializerSettings object which is used by the JSON.NET serializer, so you can now have greater control of the serialization.
    • Miscellaneous bugs fixed:
      • Tab / newline characters can now be deserialized (before it would result in an exception)
      • Integer values can now be deserialized to members of type char.
    • Miscellaneous scenarios which are now supported:
      • Uri / TimeSpan members don’t need a special converter anymore: they’re serialized as JSON strings
      • Arrays / objects also now just work. They’re serialized as JSON array / object respectively. Notice that the server runtime still needs to process that value, since they cannot be inserted into the database directly.
  • Non JSON.NET specific changes:
    • Enumeration support: it was enabled by the move to JSON.NET, but our code now serializes the string value of the enumerations. Enums can also be used in ‘Where’ clauses while querying data from the service.
    • Loss of precision handling: the runtime for Azure Mobile Services is based on JavaScript (node.js), which only stores numbers in a double-precision floating representation. That means that there are some integer and decimal values which cannot be represented accurately. Basically, any long number less than 2^53 can be represented, without loss of precision, in the server, so those are serialized correctly; any values larger than that (or smaller than -2^53) will cause an exception to be thrown. Notice that this is the current behavior, although it was a little buggy in which it would report some numbers within the valid range as invalid as well. Decimal values can also lose precision, and now this validation (exception thrown if precision would be lost) is applied to them as well. If you want to bypass the validation, either change the data type of the properties to double, or remove the converter (MobileServicePrecisionCheckConverter) from the serializer settings.
      • One minor breaking change: before the exception thrown for long values outside the non-precision-loss range was ArgumentOutOfRangeException; now the exception type is InvalidOperationException.
    • IDataMemberJsonConverter, [DataMemberJsonConverter] removed: now they either are not necessary anymore, or if you still want full control over the serialization of a property, you can use the [JsonConverterAttribute] applied to that property.
    • ICustomMobileTableSerialization removed: if a type needs special serialization, decorate it with [JsonConverterAttribute].
    • Only one member with name ‘id’ – regardless of casing – can exist in a type being serialized: before an object with members called “id”, “Id” and “ID” would be serialized to the server – where it would fail, since only one id is allowed. Now it will throw during serialization.
    • [DataMember] attributes in members now require a [DataContract] attribute in the class: the contract of [DataMember] for serialization is tied with [DataContract], but we had a bug in which data members were being honored even if the containing class itself didn’t opt in to the data contract model (by using the [DataContract] attribute). Now if the SDK finds such a scenario it will throw an exception to signal that this is not a valid scenario. The typical scenario for using [DataMember] would be to change the serialized name of the member; now it can be accomplished via the [JsonPropertyAttribute] instead.
    • The [ DataTableAttribute ] now takes the table name in the constructor: before: [DataTable(Name = “MyTable”)]; after:: [DataTable(“MyTable”)]

If I remember more or if you find one which is not listed here, please add a comment and I’ll update this post.

Operations with untyped (i.e., JSON) data

When performing CRUD operations in a managed client, usually we’d create a data type to store our information (e.g., Person, Order, etc.) and pass them to the Insert/Read/Update/Delete operations. We could also work directly with JSON data, and the table would support those as well. In the Windows Store client the SDK used the classes in the Windows.Data.Json namespace, native to the WinRT platform. However, those classes were not present in the Windows Phone 8, so that SDK was already using the JSON.NET classes from the Newtonsoft.Json.Linq namespace (JToken / JArray / JValue / JObject). To move to a common set of classes, now all managed libraries use the JSON.NET classes, including the Windows Store apps.

And the list of changes:

  • [Windows Store only] Windows.Data.Json –> Newtonsoft.Json.Linq: there’s an added advantage that the JSON.NET classes have an API which is way more user-friendly than the “dry” WinRT one.
  • Insert / update operations do not change the input object (untyped data only) : This is a behavioral change which was the outcome of a lengthy discussion within the team. Before, the insert / operations would mutate the JsonObject (or JObject) value which was passed to it, by “patching” it with whatever the server returned. The most common case was the “id” in insert operations, but the server scripts are were free to mutate the object, even changing the shape of the value returned by the client (i.e., in an insert operation, the service could receive an object and return an array, or a primitive value). Before that would mean that the client had no way to retrieve that value. Now, the object passed to InsertAsync (and UpdateAsync) is not mutated, and the return of the server operation is returned as a new object instead. Notice that for typed operations (e.g., inserting / updating an Order object), the original object is still patched in place.
    • This may require some code changes. For example, the code below:
      • var table = MobileService.GetTable(“Clients”);
      • var obj = JsonObject.Parse("{\"name\":\"John Doe\",\"age\":33}");
      • await table.InsertAsync(obj);
      • var id = (int)obj["id"].GetNumber();
    • Would need to be rewritten as (notice changes in italics)
      • var table = MobileService.GetTable(“Clients”);
      • var obj = JObject.Parse("{\"name\":\"John Doe\",\"age\":33}");
      • var inserted = await table.InsertAsync(obj);
      • var id = inserted["id"].Value<int>();

Again, I’ll update the post if I can remember (or if you can add a comment) of any additional changes.

Service filters –> HttpClient primitives

Service filters are an advanced concept which implement a pipeline over the request / response. There is already such a pipeline in the HttpClient libraries – and since it has recently been made available for downlevel platforms (it originally only worked for .NET 4.5 and .NET 4.0) the Mobile Services SDK can now use this existing code instead of creating yet another pipeline.

And the changes:

  • Goodbye IServiceFilter, hello DelegatingHandler: this is a sample implementation of a service filter, and the equivalent implementation with a delegating handler. Notice that there’s no IAsyncOperation<T> anymore, so the code is cleaner. Below you can see an example of the before / after the change.
  • Message handlers are passed to the MobileServiceClient constructor, not to a method (WithFilter) : that method created a clone of the client, and applied the filters to it. That name caused some confusion among users, with many people thinking that it would modify the client. By moving it to a constructor the API now makes it clear.

Here’s an example of a service filter converted to a message handler. The filter will add a custom header to the request, and change the response status code (useful for testing the behavior on unexpected responses from the server). First the service filter.

  1. publicasyncTask CallFilteredClient()
  2. {
  3.     var client = newMobileServiceClient(appUrl, appKey).WithFilter(newMyFilter());
  4.     var table = client.GetTable<Person>();
  5.     var p = newPerson { Name = "John Doe" };
  6.     await table.InsertAsync(p);
  7. }
  8.  
  9. publicclassMyFilter : IServiceFilter
  10. {
  11.     publicIAsyncOperation<IServiceFilterResponse> Handle(IServiceFilterRequest request, IServiceFilterContinuation continuation)
  12.     {
  13.         request.Headers.Add("x-my-header", "my value");
  14.         return continuation.Handle(request).AsTask().ContinueWith<IServiceFilterResponse>(t => {
  15.             HttpStatusCode newStatusCode = HttpStatusCode.ServiceUnavailable;
  16.             var response = t.Result;
  17.             var newResponse = newMyResponse(response.Content, response.ContentType, response.Headers,
  18.                 response.ResponseStatus, (int)newStatusCode, newStatusCode.ToString());
  19.             return newResponse;
  20.         }).AsAsyncOperation();
  21.     }
  22.  
  23.     classMyResponse : IServiceFilterResponse
  24.     {
  25.         string content;
  26.         string contentType;
  27.         IDictionary<string, string> headers;
  28.         ServiceFilterResponseStatus responseStatus;
  29.         int statusCode;
  30.         string statusDescription;
  31.  
  32.         public MyResponse(string content, string contentType, IDictionary<string, string> headers,
  33.             ServiceFilterResponseStatus responseStatus, int statusCode, string statusDescription)
  34.         {
  35.             this.content = content;
  36.             this.contentType = contentType;
  37.             this.headers = headers;
  38.             this.responseStatus = responseStatus;
  39.             this.statusCode = statusCode;
  40.             this.statusDescription = statusDescription;
  41.         }
  42.  
  43.         publicstring Content
  44.         {
  45.             get { returnthis.content; }
  46.         }
  47.  
  48.         publicstring ContentType
  49.         {
  50.             get { returnthis.contentType; }
  51.         }
  52.  
  53.         publicIDictionary<string, string> Headers
  54.         {
  55.             get { returnthis.headers; }
  56.         }
  57.  
  58.         publicServiceFilterResponseStatus ResponseStatus
  59.         {
  60.             get { returnthis.responseStatus; }
  61.         }
  62.  
  63.         publicint StatusCode
  64.         {
  65.             get { returnthis.statusCode; }
  66.         }
  67.  
  68.         publicstring StatusDescription
  69.         {
  70.             get { returnthis.statusDescription; }
  71.         }
  72.     }
  73. }

And the same code implemented with a delegating handler:

  1. publicasyncTask CallClientWithHandler()
  2. {
  3.     var client = newMobileServiceClient(appUrl, appKey, newMyHandler());
  4.     var table = client.GetTable<Person>();
  5.     var p = newPerson { Name = "John Doe" };
  6.     await table.InsertAsync(p);
  7. }
  8.  
  9. publicclassMyHandler : DelegatingHandler
  10. {
  11.     protectedoverrideasyncTask<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
  12.     {
  13.         request.Headers.Add("x-my-header", "my value");
  14.         var response = awaitbase.SendAsync(request, cancellationToken);
  15.         response.StatusCode = HttpStatusCode.ServiceUnavailable;
  16.         return response;
  17.     }
  18. }

For more information on the HttpClient primitives, check the blog post from the BCL team.

Miscellaneous changes

Those are the other minor changes in the library:

  • MobileServiceClient.LoginInProgress has been removed
  • MobileServiceClient.LoginAsync(string) has been removed. This is now called LoginWithMicrosoftAccountAsync, which is added as an extension method to the class.
  • MobileServiceTable, MobileServiceTable<T> and MobileServiceTableQuery<T> are now internal classes: all methods which used to return a table object now return the interface type IMobileServiceTable (untyped) or IMobileServiceTable<T> (typed). Typed queries are now exposed as the IMobileServiceTableQuery<T> interface.
  • MobileServiceTable.<T>ToCollectionView() is now ToCollection() : the collection view implementation had some bugs, and it has been rewritten.
  • Multiple Take operations now considers the minimum value: before it would use the last value. The call table.Take(5).Take(3).Take(7).ToListAsync() would issue a request with $top=7; now it sends a request with $top=3.
  • Multiple Skip operations now add the values: before it would use the last value. The call table.Skip(3).Skip(5).Skip(4) would issue a request with $skip=4; now it sends a request with $skip=12.

That should be it. Please start using the new libraries and let us know what you think! We still have some (although not much) time to react to user feedback before we have to lock the API down as we move to the general release.

Comments

  • Anonymous
    April 13, 2013
    I wasted an entire day figuring why IServiceFilter is now deprecated. Thank you for the clarification, your code saved my day, and the entire world.Cheers.
  • Anonymous
    May 10, 2013
    The comment has been removed
  • Anonymous
    May 13, 2013
    genvej, the old DLLs should continue working - there are a few Windows Phone apps already in the store which use Azure Mobile Services as their backend. We had a bug in that old SDK which, if you used the web-based login and background agents on the phones, then it wouldn't pass the certification (since some of the web-based login functions weren't compatible with bg agents). If this is not the case, please create a new thread on the MSDN forum (social.msdn.microsoft.com/.../threads).And unless you were using service filters or a lot of serialization customization extensively, the changes should not be major for Windows Phone applications - with the merge of the platforms, the untyped JSON framework which is now used everywhere is the one that was already being used in the phone SDK (based on JSON.NET).Hope this helps.
  • Anonymous
    May 24, 2013
    thank you ,i was been for weeks for this problem