共用方式為


Supporting arbitrary types in Azure Mobile Services managed client – simple types

[The object model shown in this post for the client-side is mostly out-of-date; check the updated post at https://blogs.msdn.com/b/carlosfigueira/archive/2013/08/23/complex-types-and-azure-mobile-services.aspx for the more up-to-date version]

As I mentioned in my previous post, the Windows Azure Mobile Services client library supports natively four data types: Boolean, strings, dates and numbers – which translated to .NET terms in the managed client, means Boolean, String, DateTime and all numeric types ([S]Byte, [U]Int[16/32/64], Double, Single, Decimal) – and their Nullable equivalent. But if we have other types which we want to use on our client, we can do it as well, by defining a class which can convert between the type and JSON (the data format used by Mobile Services when transferring data). In order to do that, we’ll need to use some classes in the Microsoft.WindowsAzure.MobileServices namespace (the MSDN documentation isn’t online yet). Let’s look at some examples on how this can be done.

First a simple scenario: we have a type with a “simple” unsupported type, such as Uri or TimeSpan, which have a fairly trivial translation to JSON (they can be represented as strings). We could also create a data transfer object (DTO) which does the conversion between the two types, but that can lead to unnecessary code bloat. What we can use instead is an implementation of the IDataMemberJsonConverter interface, which defines operations to convert to / from the JSON DOM classes in the Windows.Data.Json namespace:

  1. public interface IDataMemberJsonConverter
  2. {
  3.     object ConvertFromJson(IJsonValue value);
  4.     IJsonValue ConvertToJson(object instance);
  5. }

Now in our application we’re storing movies. One of the properties we want to store is the duration of the movie, and in this case we’ll use a TimeSpan type.

  1. public class Movie
  2. {
  3.     public int Id { get; set; }
  4.     public string Title { get; set; }
  5.     public int ReleaseYear { get; set; }
  6.     public TimeSpan Duration { get; set; }
  7. }

TimeSpan is not natively supported in the managed client for Mobile Services, so if we try to insert an item in the appropriate table, we’ll get the following exception:

Error: System.ArgumentException: Cannot serialize member 'Duration' of type 'System.TimeSpan' declared on type 'Movie'.
Parameter name: instance
   at Microsoft.WindowsAzure.MobileServices.MobileServiceTableSerializer.Serialize(Object instance, Boolean ignoreCustomSerialization)
   at Microsoft.WindowsAzure.MobileServices.MobileServiceTable`1.<InsertAsync>d__21.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()

Ok, so we need now to tell the client how to serialize (and deserialize) that type. Here’s an implementation for a converter for the TimeSpan type:

  1. public class TimeSpanConverter : IDataMemberJsonConverter
  2. {
  3.     private static readonly IJsonValue NullJson = JsonValue.Parse("null");
  4.  
  5.     public object ConvertFromJson(IJsonValue value)
  6.     {
  7.         TimeSpan result = default(TimeSpan);
  8.         if (value != null && value.ValueType == JsonValueType.String)
  9.         {
  10.             result = TimeSpan.Parse(value.GetString());
  11.         }
  12.  
  13.         return result;
  14.     }
  15.  
  16.     public IJsonValue ConvertToJson(object instance)
  17.     {
  18.         if (instance isTimeSpan)
  19.         {
  20.             TimeSpan timeSpan = (TimeSpan)instance;
  21.             return JsonValue.CreateStringValue(timeSpan.ToString());
  22.         }
  23.         else
  24.         {
  25.             return NullJson;
  26.         }
  27.     }
  28. }

Let me just open up a parenthesis about the Windows.Data.Json classes: I’ve worked with two previous JSON DOM libraries in the past, the System.Json classes from Silverlight (which made it to the preview stage in the ASP.NET Web API, but were later removed), and the classes in the Newtonsoft.Json.Linq from JSON.NET. Those APIs were nice, with nice features for converting between JSON and CLR objects. The Windows.Data.Json classes aren’t such an API, and we need to write more code than we did with those other two. For this simple case of converting between TimeSpan and JSON strings this is not a big problem, but once I get to more complex types, the difference will become clearer. In my projects I use a library of extension methods to make my life easier, but for completeness sake I won’t be using them in the blog posts. Closing the parenthesis.

Now, we need to tell the Mobile Services client library that we want to use our newly created converter. And we do that using the [DataMemberJsonConverterAttribute] class, decorating the property with a reference to our type:

  1. public class Movie
  2. {
  3.     public int Id { get; set; }
  4.     public string Title { get; set; }
  5.     public int ReleaseYear { get; set; }
  6.  
  7.     [DataMemberJsonConverter(ConverterType = typeof(TimeSpanConverter))]
  8.     public TimeSpan Duration { get; set; }
  9. }

And now we can insert unsupported types in the Mobile Services backend, as long as we can convert them to simple JSON types.

In the next post, I’ll expand on this example, to enable the usage of more complex types in the client.

Comments

  • Anonymous
    November 14, 2012
    Excellent tip, I was researching for what would take Azure Mobile Services to support more complex type. The converter opens up so many possibilities from that perspective.Keep up with the writing and thank you,C:>Marius
  • Anonymous
    April 21, 2013
    Guys, I get an exception raised by the ConvertFromJson method call saying that theString was not recognized as a valid TimeSpan.it looks like this "1900-01-01T00:01:06.000Z"Any thoughts?