Tuesday, October 22, 2019

Deserializing generic interfaces with System.Text.Json

.Net Core 3.0 introduces new JSON (de)serialization classes in the System.Text.Json Namespace.

These are high-performance classes for working with JSON. If you really don't want to use JSON.Net or need to get the most from your serialization performance you may want to consider them.

As they're new, documentation isn't a thorough as other JSON solutions and some things work a bit differently.

One thing that's particularly non-obvious is how to deserialize to a generic interface of interfaces.
e.g. an `IList<ISomething>`.

This is a problem I was recently challenged with.

It's easy to use a converter to deserialize a single interface to a concrete type with an attribute on the property.


   [JsonConverter(typeof(InterfaceConverter<FormattedDate, IFormattedDate>))]
   public IFormattedDate Opened { get; set; }


   ...

   class FormattedDate : IFormattedDate
   {
       public string DateValue { get; set; }
   }

   interface IFormattedDate
   {
       string DateValue { get; set; }
   }

   public class InterfaceConverter<M, I> : JsonConverter<I> where M : class, I
   {
        public override I Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            return JsonSerializer.Deserialize<M>(ref reader, options);
        }

        public override void Write(Utf8JsonWriter writer, I value, JsonSerializerOptions options) { }
   }

However, this doesn't work with generic interfaces.

For this, we need to use a converter factory. Actually, we need two, but first, what is a converter factory?

A converter factory takes a type (class, property, etc.) and can return a converter to use to serialize (write) or deserialize (read) instances of that type.

Rather than use attributes to tell when to use the converter, the factories are passed to the serialization methods.

    var serializerOptions = new JsonSerializerOptions
    {
        Converters = {
            new InterfaceConverterFactory(typeof(FormattedDate), typeof(IFormattedDate)),
            new IListInterfaceConverterFactory(typeof(IFormattedDate)),
            }
    };

    var goodObj = JsonSerializer.Deserialize<GoodObject>(json, serializerOptions);

Above, you can see two converter factories and this is what we need for our particular problem.
During the [de]serialization of a type, the serializer will ask the factory if there is a converter for the type.

    class GoodObject
    {
        [JsonConverter(typeof(Converters.InterfaceConverter<FormattedDate, IFormattedDate>))]
        public IFormattedDate Opened { get; set; }

        public IList<IFormattedDate> ImportantEvents { get; set; }
    }

The handling of types during [de]serialization happens at the broadest level first, so to deserialize the `ImportantEvents` in the above class the serializer will look for a converter for `GoodObject`, `IList`, and `IFormattedDate` in that order, assuming that the type has not already been handled by an earlier converter. This knowledge is enough to solve the problem.

Adding a converter to deserialize an `IList<I>` into a `List<I>` is the first step.

    public class ListConverter<M> : JsonConverter<IList<M>>
    {
        public override IList<M> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            return JsonSerializer.Deserialize<List<M>>(ref reader, options);
        }

        public override void Write(Utf8JsonWriter writer, IList<M> value, JsonSerializerOptions options)
        {
            throw new NotImplementedException();
        }
    }

Then we need an appropriate factory to create this converter.

    public class IListInterfaceConverterFactory : JsonConverterFactory
    {
        public IListInterfaceConverterFactory(Type interfaceType)
        {
            this.InterfaceType = interfaceType;
        }

        public Type InterfaceType { get; }

        public override bool CanConvert(Type typeToConvert)
        {
            if (typeToConvert.Equals(typeof(IList<>).MakeGenericType(this.InterfaceType))
             && typeToConvert.GenericTypeArguments[0].Equals(this.InterfaceType))
            {
                return true;
            }

            return false;
        }

        public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
        {
            return (JsonConverter)Activator.CreateInstance(
                typeof(ListConverter<>).MakeGenericType(this.InterfaceType));
        }
    }

Because this will return a concrete list of interfaces and the serializer doesn't know how to handle those interfaces, it will ask the registered factories if they can provide a converter. We can use the converter we originally used in an attribute, we just need to add a factory to create it.

    public class InterfaceConverterFactory : JsonConverterFactory
    {
        public InterfaceConverterFactory(Type concrete, Type interfaceType)
        {
            this.ConcreteType = concrete;
            this.InterfaceType = interfaceType;
        }

        public Type ConcreteType { get; }
        public Type InterfaceType { get; }

        public override bool CanConvert(Type typeToConvert)
        {
            return typeToConvert == this.InterfaceType;
        }

        public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
        {
            var converterType = typeof(InterfaceConverter<,>).MakeGenericType(this.ConcreteType, this.InterfaceType);

            return (JsonConverter)Activator.CreateInstance(converterType);
        }
    }

Now we can deserialize a provided string.

    var json = "{\"Opened\":{\"DateValue\":\"2019-10-21T13:35\"}, \"ImportantEvents\":[{\"DateValue\":\"2019-10-21T13:36\"},{\"DateValue\":\"2019-10-21T13:37\"}]}";

    var serializerOptions = new JsonSerializerOptions
    {
        Converters = {
            new InterfaceConverterFactory(typeof(FormattedDate), typeof(IFormattedDate)),
            new IListInterfaceConverterFactory(typeof(IFormattedDate)),
            }
    };

    var goodObj = JsonSerializer.Deserialize<GoodObject>(json, serializerOptions);

Yay!






Thursday, September 19, 2019

Rapid XAML Toolkit - Now in preview!

Oh, the joy when a side project reaches a release milestone.

The Rapid XAML Toolkit is a collection of tools (hence the name) for helping with XAML development. The idea is to help fill some of the gaps in the current, MS provided tooling.

Today I'm in Montreal at #UnoConf (actually on stage as this post goes live) talking about this project and the great news is that there's now a preview version available that you can download and try it out yourself. Get it in the Visual Studio Marketplace now.

VS Extension search results showing the Rapid XAML (Preview) entry

This is an official-unofficial preview. There will be an official (signed by Microsoft*) version shortly.

So, what does it do?

There are two main things it does: XAML Generation and XAML Analysis.

It can create a stub UI from the ViewModel based on the types, names, and accessibility of the properties. Highlight the properties, copy them (as XAML) and then paste them on the view, where you want. Or just drag the VM (from solution explorer) onto the designer.
If you write your ViewModels before the Views, this should save you some time. It won't be the final UI for your app, but it's a big step in the right direction.

Additionally, it can perform analysis on XAML in the same way that Roslyn Code Analyzers do for C# and VB. You'll see squiggly lines under issues or places for improvement like in the image below. As you'd expect, you can press ctrl+. and access suggested actions to automatically make the proposed changes.

Screenshot showing XAML analysis errors

It works with UWP, WPF, and Xamarin.Forms.

If you use XAML to build apps in Visual Studio (on Windows), I'd love if you could try it out and share your thoughts, experience, suggestions, comments, or anything else on GitHub.


Sound good?


* There's a complicated relationship with Microsoft and me regarding this project. The code is all open-source on GitHub (MIT Licensed) under the Microsoft org account. I've written most of it. They're (eventually) going to publish it. The long term situation is still under discussion.

---

Fun bonus image that also relates to something I showed in my UnoConf talk, which I'll leave open to your interpretation.

Cross-Platform Template Studio - name TBC


Thursday, July 18, 2019

UWP inline design-time data - helper library v1.1

Over the weekend I announced my library to enable inline design-time only data in UWP apps.

Now, I've released an update (v1.1) which allows it to work with the supported properties on any control.
This removes the limitation of only working on default classes and you can use it on any object with the relevant property. This could be your own (custom) control or one from a 3rd party library.

The source is also now available at https://github.com/mrlacey/UwpDesignTimeData

Sunday, July 14, 2019

UWP Inline DesignTime data

TLDR: UWP apps can now simulate the inline design-time data that Xamarin.Forms supports.
Simply install `Install-Package UwpDesignTimeData`
Then, do things like this in your XAML: `dt:DesignTime:Text="Only shown in designer."`

---

Let's say you have a simple page in an app. Imagine something like this. It's a default settings page as generated by Windows Template Studio.


Now imagine this is a localized app and so all the text comes from a resource (RESW) file.
In this situation, the XAML will contain Universal Identifiers (`x:Uid`) for each element and the text will be defined in the resource file (for each language.)
These files will look something like this:


That's great, but if you want to change what the page looks like, the designer isn't a lot of help.
Here's what the designer shows.


This is far from great and it's something I can make better based on my knowledge of attached properties and the way the XAML designer works.

Simply install my new NuGet package. UpwDesignTimeData.
Then modify the XAML like this.
The designer now looks like this.
Yay!

It's not just great for localized content, it can also provide design-time data for values set with a binding.



It doesn't only work with text. It supports the following properties:

  • Caption
  • Content
  • Date
  • Description
  • FlowDirection
  • Footer
  • Header
  • Icon
  • IsChecked
  • IsOn
  • Maximum
  • MaxRating
  • Minimum
  • OffContent
  • OnContent
  • PaneFooter
  • PaneHeader
  • PaneTitle
  • Password
  • PasswordChar
  • PlaceholderText
  • PlaceholderValue
  • QueryText
  • Source
  • Symbol
  • Text
  • Time
  • Title
  • Value

These should work with all the standard controls. It won't work with custom controls. Sorry.

It even works for a few things that you can't do in XAML with the regular properties.
For example, you can specify a date or time for the DatePicker (or TimePicker) as strings and it will try and parse the value and then show it.



It works with Visual Studio 2017 and 2019.

Things to note.
  1. You may need to (re)build the project to force the designer to refresh and show the design-time data.
  2. If you find a bug or something that it doesn't work with, please log an issue with the details.
  3. If you define the actual and design-time versions of the same property, specify the design-time version second. This is necessary because of the way the XAML designer works.
  4. This should work regardless of the version of the SDK you are targetting (or have as the minimum.) If it doesn't work in your scenarios, please log an issue with the details.
  5. Documentation will follow.
Important.
Xamarin.Forms already has native support for this functionality. Hopefully, UWP (and WPF) will get this officially at some point in the not-to-distant future. ;)
When that happens, I hope and expect you'll be able to simply remove this package from your app and replace all instances of "dt:DesignTime." with "d:".


Sunday, July 07, 2019

Using data from SQL Server and/or Web API in UWP apps - Feedback wanted

In the next release of Windows Template Studio (the amazing tool for scaffolding your modern Windows apps) we'll be adding options to make it easier to connect to a SQL Server database or also create your own Web API.

If you've done, or are interested in either of these things your feedback will be gratefully appreciated.

SQL Server

In many enterprise scenarios, the ability to connect to data from a SQL Server database is a must. Adding appropriate references to a project can be more work than many developers would like and so this option makes setting up the project appropriately only require a single click.
Of course, it's still necessary to retrieve and load the required data but you'd have to do that anyway. What this option does is just set up the project so that this is easy to do. It also adds appropriate TODO notes so you know where the generated code needs to be modified. Additionally, there's also a method that shows how to use data from a NorthWind sample database as a replacement for the SampleData that is used by default by the pages included in the generated app.
This is deliberately not an option for generating an app based on the contents of a database. There are other tools available if that's something you want to do.


Web API (and HTTP data access)

Taking heavy inspiration from the MobileAppService option when creating a Xamarin.Forms app, we've added the ability to include an ASP.NET CORE Web API  project in the generated solution. If you are creating a custom web-based API to go with your new app, this should make it that little bit easier.
Differences between these new options and the Xamarin.Forms version are that we take a dependency on the "App.Core" shared library to avoid duplication, return our own `SampleData` in the default controller, and we've separated the code for the app to retrieve content from the API in a separate `HttpDataService` class so that they're available in apps that don't want (or need) to include the Web API directly.


As with everything that is included in Windows Template Studio we tread a delicate balance between providing enough to avoid developers needing to do more than they must, but also not being explicit on how something must be done, or including lots of code that must be removed.

If you have thoughts on any of the above, I'd love to hear them.



As an own-trumpet-blowing aside, the addition of these options means I now personally claim responsibility for twenty (20) of the options available in the WTS wizard. I've done a load of other stuff too (VB.NET support & internal testing being the main areas) but thought this was a nice milestone to celebrate.