Windows (UWP) Designer in Visual Studio and Blend for Visual Studio

If you’ve been looking at the release notes for the Visual Studio previews, you’ll have noted that there’s some work being done on the XAML designer that now supports both WPF and UWP. I figured I would take this opportunity to go through and document some of the features that I use and some of … Continue reading “Windows (UWP) Designer in Visual Studio and Blend for Visual Studio”

If you’ve been looking at the release notes for the Visual Studio previews, you’ll have noted that there’s some work being done on the XAML designer that now supports both WPF and UWP. I figured I would take this opportunity to go through and document some of the features that I use and some of the new features that have appeared in the previews. Note that this isn’t an exhaustive list by any means – would love feedback on what other features you use and what you think is missing in the designer.

One of the reasons that the designer experience in Visual Studio and Blend is so relevant is that you can take the design you’ve done for your Windows app and port it across to iOS, Android, MacOS and even the web (using Web Assembly). All these platforms are available via the Uno platform – If you’re new to the Uno Platform, head over to https://platform.uno/ and get started with building cross-platform mobile, desktop and Web applications.

New Project

Ok, so let’s start by creating ourselves a new Windows (UWP) project based on the Blank App (Universal Windows) template.

As usual, give the project a name to get started – you’ll be prompted to pick which target and minimum versions of Windows you want but for the purpose of this post I just went with the defaults.

Visual Studio Designer

Once created, depending on your setup of Visual Studio, you’ll probably have the MainPage.xaml already open. If not, find MainPage.xaml in the Solution Explorer tool window and double-click it to open it. Here you can see that I have the Toolbox on the left of the design surface and the Properties window, above the Solution Explorer, on the right side. I find this layout works well for working with the designer but you can easily customise the layout of the tool windows to suit how you work.

Design in Blend

We’re actually going to switch across to Blend for Visual Studio for the rest of this post. I prefer to do any designer work in Blend because I prefer to have the tool windows in a different position when doing design work, than when writing code. Switching between Visual Studio and Blend also gives me a mental switch to go between designer mode (well at least “layout-oriented work” mode, since I’m clearly not a designer) and developer mode.

It’s worth noting that the designer experience in Visual Studio and Blend is very similar – Microsoft made the decision years ago to build a consistent experience with the majority of functionality now available in both tools. Blend still retains a number of designer oriented features, such as creating visual states and animations with storyboards, that haven’t been exposed in the Visual Studio designer.

You can easily switch to Blend by right-clicking on the project or a XAML file in Solution Explorer and selecting Design in Blend.

As you can see the tool windows are labeled slightly differently in Blend and have a different default position. Again, feel free to reposition them to suit how you work.

Zoom

If you look at the main design surface, you’ll note that the initial position of the MainPage is very small. Working at this zoom level will be quite hard as each of the elements will be small and hard to manipulate.

In the bottom left corner of the design surface there’s a series of icon buttons and a dropdown. Expanding the dropdown allows you to select from a number of predefined zoom levels.

Alternatively you can select Fit all, or Fit selection, in order to bring the whole page into view.

The other way that you can control the positioning of the design area is using the mouse:

  • Scroll up/down – Two-finger drag on touch pad, or scroll wheel on mouse
  • Scroll left/right – Hold Shift + Two-finger drag on touch pad, or scroll wheel on mouse
  • Zoom in/out – Hold Ctrl + Two-finger drag on touch pad, or scroll wheel on mouse

Adding Controls with Assets Tool Window

Let’s start to add some controls to the page. We’ll use the Assets tool window to locate the TextBlock control using the search function.

If I just click and drag the TextBlock onto the design surface, Blend will add the control where I drop it.

Objects and Timeline Tool Window

If we take a look at the XAML you’ll note that a very arbitrary margin has been set on the TextBlock

Layout – Reset All

Right-click on the TextBlock in the Objects and Timelines tool window and select Reset All from the Layout menu.

Now the TextBlock has been reposition to take up the whole page.

However because text flows from the top-left, the word TextBlock is in the top left corner of the page.

Edit Style – Apply Resource

Next, let’s increase the size of the text. Instead of manually setting FontSize, we’re going to make use of one of the existing TextBlock styles. Right-click on the TextBlock again in the Objects and Timeline window and select Edit Style, Apply Resource and then we’ll select HeaderTextBockStyle. You can read more about predefined styles and the use of typography here.

This gives our TextBlock a nice size, without hard coding font sizes and styles randomly throughout the application.

Design Surface

Up to now I haven’t given you much context for this app – we’re going to build a simple interface that shows a list of contacts. The TextBlock we’ve added so far will act as a header/title for the page, and then beneath it we’ll need a ListView showing the list of our contacts.

Drawing Grid Rows

Before we can add the ListView, let’s create two rows in the Grid that the TextBlock is sitting in. Select the Grid in the Objects and Timelines window and then on the design surface, if you move the mouse cursor near the edge of the page, you’ll see the cursor change to one that’s got a small plus sign on it. Clicking at this position will add a row to the Grid.

Once added, you can then adjust the sizing of the row. In this scenario we’re going to change the row from 33* to being Auto, which will mean the row will be sized based on the height of the TextBlock.

Reset Grid.RowSpan in Properties Tool Window

One thing you may notice is that after adjusting the row size, the row seems to disappear. This is because in creating the row, Blend decided that the TextBlock was going to span both rows. To fix this, we can change the RowSpan on the TextBlock from 2 to 1 using the Properties tool window.

In this case, we’re going to select the Reset option, rather than setting it explicitly to 1. This will mean one fewer attributes in XAML that needs to be parsed.

Assets Button

Next up, we’re going to add the ListView to the second row. This time, instead of going all the way to the Assets tool window, we’re going to use the dropdown from the chevrons on the top left of the designer. This allows us to easily search and add controls, without having to open the tool windows – when doing a lot of design work I will often hide the tool windows (or even detach them and put them on a different monitor), so not having to open the tool windows to find a control is quite handy.

Runtime Toolbar

After dragging the ListView onto the page and making sure it’s sitting in the second row, I’m going to run the application to see what it looks like. Note that despite me not doing anything, the color of the title bar has aligned with my Windows theme (and yes, it’s pink because I’m using the setting where the theme is derived off the background I have set, which in turn is changed daily using the Dynamic Theme app).

Hot Reload

You’ll also note at the top of the window there is a toolbar that’s been added whilst I’m debugging the application. This toolbar has been available for a while but the Hot Reload indicator is a new addition. Hot Reload allows you to make changes to your XAML and for it to immediately take effect on the page when you save the XAML file (Hot Reload can be configured via Options off the Tools menu).

Display Layout Adorners

I’m going to toggle the Display Layout Adorners button (immediately adjacent to the left of the Hot Reload green tick).

Select Element

Next I’m going to click on the Select Element button (second in from the left) and click on the open space immediately below the TextBlock. As you can see, this highlights the whole area in a light blue (the layout adorner) which is the ListView.

Go to Live Visual Tree

Next I can click on the Go to Live Visual Tree button (first from the left) which will switch back to Visual Studio with the focus being set on the appropriate node in the Live Visual Tree.

XAML Editor

You’ll also note that the corresponding code in the XAML editor has been selected. As I start to type in the XAML editor I get intellisense showing me what options I have.

I’ve gone ahead and set a simple data template for the ListView which will determine what’s displayed for each item in the list. In this case it’s just going to print the word “Hi” for each item.

XAML Highlighting

Note that as I move the cursor around the XAML editor, the matching pairs of XML tags are highlighted, making it easy to see start and end of the blocks of XAML.

DataContext for Data Binding

Currently, despite setting a data template, the ListView isn’t going to show any items because I haven’t connected it to any data. To allow me to continue to layout elements on the page I’ve created some mock, or fake, data.

public class FakeData
{
    public Contact[] Contacts { get; } = new[]
    {
        new Contact(){Name="Bob Jones",PhoneNumber="+1 442 002 3234", Photo="ms-appx:///Assets/Photos/p0.jpg"},
        new Contact(){Name="Jessica Phelps",PhoneNumber="+1 394 234 1235", Photo="ms-appx:///Assets/Photos/p1.jpg"},
        new Contact(){Name="Andrew Jenkins",PhoneNumber="+1 232 282 29321", Photo="ms-appx:///Assets/Photos/p2.jpg"},
        new Contact(){Name="Francis Davis",PhoneNumber="+1 92329 2923 923", Photo="ms-appx:///Assets/Photos/p3.jpg"},
        new Contact(){Name="Xavier Smith",PhoneNumber="+1 93483 3923423", Photo="ms-appx:///Assets/Photos/p4.jpg"},
        new Contact(){Name="Kevin Chow",PhoneNumber="+1 343 994 39342", Photo="ms-appx:///Assets/Photos/p5.jpg"},
        new Contact(){Name="Phil Stevenson",PhoneNumber="+1 885 367 44432", Photo="ms-appx:///Assets/Photos/p6.jpg"},
        new Contact(){Name="Heath Sales",PhoneNumber="+1 903 912 9392", Photo="ms-appx:///Assets/Photos/p7.jpg"},
        new Contact(){Name="Sarah Wright",PhoneNumber="+1 347 399 499234", Photo="ms-appx:///Assets/Photos/p8.jpg"},
        new Contact(){Name="Geoff Sans",PhoneNumber="+1 834 1232 01923", Photo="ms-appx:///Assets/Photos/p9.jpg"}
    };
}

I can go ahead and create an instance of the FakeData class as a Resource on the Page. This instance is then set as the DataContext for the root Grid element on the page (and subsequently the DataContext for all elements on the page).

The ItemsSource on the ListView is then data bound to the Contacts property on the FakeData instance. The running application updates immediately to show a list of “Hi” down the screen.

Hide the Runtime Toolbar

As we start to design the page, the toolbar at the top can often get in the way. If you click on the chevron on the right side of the toolbar, the first click will reduce the size of the Hot Reload indicator (removing the words Hot Reload). The second click will minimise the toolbar completely.

Let’s amend the data template for the list, this time data binding to the Name property.

The ListView is immediately looking better, showing the names of the people in the Contacts list.

Edit ListView Item Template

If your working in the designer there are a number of ways you can make changes to the data template for the list. In this example we’re using the dropdown at the top left corner of the design area. Clicking the down button, followed by Edit Additional Template, then Edit Current, then Edit Generated Items (ItemTemplate).

Objects and Timeline – Group Into

You’ll notice that the Objects and Timeline window updates to show the tree from the perspective of the data template. Right-clicking on the TextBlock I can select Group Into, followed by StackPanel, to wrap the TextBlock in a StackPanel.

TextBlock Button

Next, I can add a TextBlock to the newly created StackPanel but simply double-clicking the TextBlock button on the left of the design area.

XAML Suggested Actions

One of the newest additions to the designer is the XAML Suggested Actions. Anyone who’s worked in Word or Excel is familiar with the suggested actions toolbar that often appears to try to make more of the tools are accessible when you need them. in this case we can click on the lightbulb button to display an in-situ property editor.

In this case we’re going to click through to Create Data Binding.

Create Data Binding

From the Create Data Binding window we can traverse down to the PhoneNumber property on the Contact object.

Note that there’s a minor bug at this point because the data binding that’s created isn’t 100% accurate. Instead of data binding to the PhoneNumber property (eg {Binding PhontNumber}) the generated binding is with Contacts.PhoneNumber. Simply removing the “Contacts.” prefix is enough to get it to work.

Edit ColumnDefinitions

I’m going to follow a similar approach to before – select the StackPanel and group into a Grid. From the Properties tool window find the ColumnDefinitions.

Click the … to open the Collection Editor for the ColumnDefinitions. Here we’re going to limit the first column to 50 pixels and then leave the second column as *.

I’ve update the data template with the profile picture as an image. Simply add an Image control and follow the same process as before to setup data binding to the Photo property on the Contact.

WinUI – ProfilePicture Control

To improve the layout of the contacts I’m going to use the ProfilePicture control, which will ensure all the images are the same size.. Instead of using the built in control, I’m going to grab the ProfilePicture control from the WinUI toolkit. Using the nuget package manager, select the Microsoft.UI.Xaml package and install it.

Once you’ve added the nuget package, make sure you don’t ignore the readme file that’s displayed to remind you to include the appropriate runtime values.

We’re almost there, we just need to add the PersonProfile, which we can do by discovering it in the Assets window (even though its from a third party)

And so now we have all our contacts appearing.

Final Design

After applying some minor tweaks to the layout of the data set, we can have a nicer looking sample app.

Before I copied the final code to this post, I just wanted to format the XAML. From the Options window, you can configure how you want your XAML files to appear.

Then, once you have the formatting options the way you want them, you can invoke Format Document from the various places around the design surface.

Final XAML

And here’s the final source code

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:WindowsDesigner"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:Custom="using:Microsoft.UI.Xaml.Controls"
      x:Class="WindowsDesigner.MainPage"
      mc:Ignorable="d">
    <Page.Resources>
        <local:FakeData x:Key="DesignTimeData" />
    </Page.Resources>

    <Page.Background>
        <ThemeResource ResourceKey="ApplicationPageBackgroundThemeBrush" />
    </Page.Background>

    <Grid DataContext="{StaticResource DesignTimeData}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <TextBlock Margin="16"
                   Text="Contacts"
                   TextWrapping="Wrap"
                   Style="{StaticResource HeaderTextBlockStyle}" />
        <ListView Grid.Row="1"
                  ItemsSource="{Binding Contacts}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <Grid Margin="8">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="50" />
                            <ColumnDefinition />
                        </Grid.ColumnDefinitions>
                        <StackPanel Orientation="Vertical"
                                    Grid.Column="1"
                                    Margin="8"
                                    HorizontalAlignment="Center">
                            <TextBlock Text="{Binding Name}"
                                       Style="{StaticResource BaseTextBlockStyle}" />
                            <TextBlock Text="{Binding PhoneNumber}"
                                       Style="{StaticResource BodyTextBlockStyle}" />
                        </StackPanel>
                        <Custom:PersonPicture Width="50"
                                              Height="50"
                                              ProfilePicture="{Binding Photo}" />
                    </Grid>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</Page>

And that’s it – hopefully you’ve learnt a bit more about how to use the XAML editor and the new designer features.

Xamarin DevOps Snippets (aka Pipeline Templates)

Louis Matos has put together Xamarin Month with the topic of Code Snippets – Check out Louis’ blog for the full month of snippet. In this post I’m going to cover some code snippets that use Pipeline Templates in order to setup a Azure DevOps pipeline for your Xamarin application. Azure Pipeline Templates are a way … Continue reading “Xamarin DevOps Snippets (aka Pipeline Templates)”

Louis Matos has put together Xamarin Month with the topic of Code Snippets – Check out Louis’ blog for the full month of snippet. In this post I’m going to cover some code snippets that use Pipeline Templates in order to setup a Azure DevOps pipeline for your Xamarin application.

Azure Pipeline Templates are a way to define reusable components of YAML that can be shared across different pipelines and in fact across different repositories. Noting how difficult it was to setup even a basic pipeline to build and deploy a Xamarin application, the Pipeline Templates project was born to publish reusable templates that could easily be dropped into any pipeline.

Getting Started

In order to make use of templates, the first thing you need to do is to reference the pipeline_templates GitHub repository as a resource in you YAML build pipeline.

resources:
  repositories:
    - repository: builttoroam_templates
      type: github
      name: builttoroam/pipeline_templates
      ref: refs/tags/v0.6.1
      endpoint: github_connection

It’s worth noting that the ref attribute is referencing the v0.6.1 release by specifying the tag. Alternatively you could reference any branch simply by changing the ref to a value similar to “refs/heads/nickr/bugfix” where the branch is nickr/bugfix. I would recommend referencing one of the tagged releases for stability of your build pipeline – from time to time we do make breaking changes, so if you’re referencing a branch, your build might start failing.

Build Templates

Android

To build the Android application, use the build-xamarin-android template – simply provide the necessary parameters.

stages:
- template:  azure/stages/[email protected]_templates
  parameters:
    # Stage name and whether it's enabled
    stage_name: 'Build_Android'
    build_android_enabled: true
    # Version information
    full_version_number: '1.0.$(Build.BuildId)'
    # Signing information
    secure_file_keystore_filename: '$(android_keystore_filename)'
    keystore_alias: '$(android_keystore_alias)'
    keystore_password: '$(android_keystore_password)'
    # Solution to build
    solution_filename: 'src/SnippetXamarinApp.sln'
    solution_build_configuration: 'Release'
    # Output information
    artifact_folder: 'artifacts'
    application_package: 'android.apk'

iOS

To build the iOS application, use the build-xamarin-ios template

- template:  azure/stages/[email protected]_templates
  parameters:
    # Stage name and whether it's enabled
    stage_name: 'Build_iOS' 
    build_ios_enabled: true
    # Version information
    full_version_number: '1.0.$(Build.BuildId)'
    # Solution to build
    solution_filename: 'src/SnippetXamarinApp.sln'
    solution_build_configuration: 'Release'
    # Signing information
    ios_plist_filename: 'src/SnippetXamarinApp/SnippetXamarinApp.iOS/Info.plist'
    ios_cert_password: '$(ios_signing_certificate_password)'
    ios_cert_securefiles_filename: '$(ios_signing_certificate_securefiles_filename)'
    ios_provisioning_profile_securefiles_filename: '$(ios_provisioning_profile_securefiles_filename)'
    # Output information
    artifact_folder: 'artifacts'
    application_package: 'ios.ipa'

Windows

To build the Windows application, use the build-xamarin-windows template. Technically this template should work with any UWP application.

- template:  azure/stages/[email protected]_templates
  parameters:
    # Stage name and whether it's enabled
    stage_name: 'Build_Windows'
    build_windows_enabled: true
    # Version information
    full_version_number: '1.0.$(Build.BuildId)'
    # Signing information
    windows_cert_securefiles_filename: '$(windows_signing_certificate_securefiles_filename)'
    windows_cert_password: '$(windows_signing_certificate_password)'
    # Solution to build
    solution_filename: 'src/SnippetXamarinApp.sln'
    solution_build_configuration: 'Release'
    # Output information
    artifact_folder: 'artifacts'
    application_package: 'windows.msix'

Deploy

Of course, once you’re done building your applications, you probably want to deploy the applications for testing. For this you can use the deploy-appcenter template. Note that you need to add a stage for each platform you want to deploy but you can use the same template as it knows how to deploy iOS, Android and Windows applications to AppCenter.

- template:  azure/stages/[email protected]_templates
  parameters:
    # Stage name and dependencies
    stage_name: 'Deploy_Android'
    depends_on: 'Build_Android'
    deploy_appcenter_enabled: true
    environment_name: 'AppCenter'
    # Build artifacts
    artifact_folder: 'artifacts'
    application_package: 'android.apk'
    # Signing information (for Android repack to APK)
    secure_file_keystore_filename: '$(android_keystore_filename)'
    keystore_alias: '$(android_keystore_alias)'
    keystore_password: '$(android_keystore_password)'
    # Deployment to AppCenter
    appcenter_service_connection: $(appcenter_service_connection)
    appcenter_organisation: $(appcenter_organisation)
    appcenter_applicationid: $(appcenter_android_appid)

For a more detailed walk through of using the pipeline templates for building cross platform xamarin applications, please check out this post that covers the process end to end.

Call to Action: Contributions

The Pipeline Templates project is an open source project and I would love to get feedback and contributions from the community in order to provide more templates (not just for mobile).

Specifically, if anyone has built out the tasks necessary to deploy applications to each of the three stores, it would be great to create a template similar to the AppCenter template that targets the actual stores.

If anyone is familiar with both GitHub Actions and Azure Pipelines, it would be great to get someone to give me a hand to convert the existing templates for Azure DevOps across to GitHub Actions.

Consuming REST API with Swagger / OpenAPI in Xamarin and Uno Applications

I still recall the simplicity of standing up a SOAP service and adding a service reference via Visual Studio by simply entering the url to the WSDL – this scenario just worked…. until we moved on. There was a rapid progression away from the overly prescriptive XML based world of SOAP to REST based APIs. … Continue reading “Consuming REST API with Swagger / OpenAPI in Xamarin and Uno Applications”

I still recall the simplicity of standing up a SOAP service and adding a service reference via Visual Studio by simply entering the url to the WSDL – this scenario just worked…. until we moved on. There was a rapid progression away from the overly prescriptive XML based world of SOAP to REST based APIs. This was all well and good but there was no longer a standard approach to documenting these APIS. Enter Swagger, and subsequently OpenAPI, as a way to document REST based APIs. Rather than spend time in this post detailing how you can add some Swagger to your web api (look here if you’re interested), I’m going to focus on consuming a REST based API by first importing its Swagger and using that to generate the code for accessing the API.

One of the most important points to note about this process, is that we’ll be working with a .NET Standard 2.0 class library. This means that the same library can be used across any application you might want to build, whether it be a console application, or a Xamarin.Forms app, or a Uno app (go to https://platform.uno/ to learn more about the Uno Platform).

In fact, to illustrate this point, I have a solution which has both a Uno and a Xamarin.Forms application in it. The solution also has a Data project (just a regular .NET Standard 2.0 library), which is where we’ll be importing the Swagger document. The Data project is referenced by each of the head projects for both Xamarin.Forms and Uno.

The REST service that we’re going to call is one that I setup as part of generating the OCS files for Build 2020 (more details here). The swagger documentation can be found at https://build2020.builttoroam.com/swagger/index.html, which is just a pretty version of the actual Swagger/OpenAPI document that’s located at https://build2020.builttoroam.com/swagger/v1/swagger.json

The process for referencing a REST API that has a Swagger or OpenAPI is now one of the global tools that ships with the dotnet cli. High level documentation for the OpenAPI tool can be found on the Microsoft docs website. We’ll step through the process here as it’s relatively simple.

To start with, we need to make sure that the openapi tool has been installed. This can be done by running the following at a command prompt:

dotnet tool install -g Microsoft.dotnet-openapi

Next, navigate to the folder of the project you want to reference the swagger document and call the openapi command, along with the “add url” parameter (and of course the url of the swagger document).

dotnet openapi add url https://build2020.builttoroam.com/swagger/v1/swagger.json

Returning to Visual Studio we can see that a file, swagger.json, has been added to the Data project.

We’ll add a small snippet of code to call one of the REST APIs.

var client = new swaggerClient("https://build2020.builttoroam.com", new System.Net.Http.HttpClient());
var sessions = await client.SessionsAsync();
System.Diagnostics.Debug.WriteLine(sessions.Count);

This is all well and good but what happens if you want to customize the generated code….oh wait, what….generated code… I forgot to mention, as part of building the Data library, the swagger.json document that was added to your project is converted into a set of classes that make invoking the REST APIs super easy (as you saw above in the code example).

If you press F12 when the cursor is located in swaggerClient class name (or just right-click on the swaggerClient class and select Go To Definition) you’ll be taken to the generated swaggerClient.cs file (in the obj folder). Don’t make changes directly to this file as they will get overwritten the next time you build the Data project.

As I was about to say, there are a couple of ways you can customize the generated code. The first way is to adjust the code generation itself. You can do this by supplying options that configure the way the code generation is done. When the “dotnet openapi” command was invoked earlier, in addition to adding the swagger.json file to the project, an entry was added to the csproj file.

<OpenApiReference Include="swagger.json" SourceUrl="https://build2020.builttoroam.com/swagger/v1/swagger.json" />

The OpenApiReference element in the csproj invokes the code generation during the build process by calling out to the popular NSwag command line tool. As such the options that can be used are the same as for the Nswag tool (A full list of options can be found in the NSwag source code here). For example, let’s ensure an interface is generated for the swaggerClient class, we can set the GenerateClientInterfaces property to true.

<Project Sdk="Microsoft.NET.Sdk">
	<PropertyGroup>
		<TargetFramework>netstandard2.0</TargetFramework>
	</PropertyGroup>
	<ItemGroup>
		<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
		<PackageReference Include="NSwag.ApiDescription.Client" Version="13.0.5" />
	</ItemGroup>
	<ItemGroup>
		<OpenApiReference Include="swagger.json" SourceUrl="https://build2020.builttoroam.com/swagger/v1/swagger.json">
			<Options>/GenerateClientInterfaces:true</Options>
		</OpenApiReference>
	</ItemGroup>
</Project>

The other way we can extend the swaggerClient class is by adding the implementation of one or more of the partial methods that has been created. Again, if you go to definition on the swaggerClient class, you can see these partial method declarations.

To implement these partial methods, simply add a partial class called swaggerClient to the project, and add the method you want to implement. For example, adding the PrepareRequest method, you can log out the actual url of each API call.

And there you have it, a super simple way to reference a REST API from a .NET Standard library, which can then be used by your Xamarin.Forms, Uno or even a console app.

Big shout out to Rico Suter for the amazing work he’s done with the NSwag library

iOS Hot Restart, Multi-Targeting and Solution Template for Cross-Platform Applications with Uno

In my last couple of posts I covered setting up multi-targeting and improving the developer experience with multi-targeting. This was in the context of an application that supported iOS (Xamarin iOS), Android (Xamarin Android) and Windows (UWP). In this post we’re going to look at a template for Uno cross-platform applications. If you’re new to … Continue reading “iOS Hot Restart, Multi-Targeting and Solution Template for Cross-Platform Applications with Uno”

In my last couple of posts I covered setting up multi-targeting and improving the developer experience with multi-targeting. This was in the context of an application that supported iOS (Xamarin iOS), Android (Xamarin Android) and Windows (UWP). In this post we’re going to look at a template for Uno cross-platform applications.

If you’re new to the Uno Platform, head over to https://platform.uno/ and get started with building cross-platform mobile, desktop and Web applications.

The main points we’ll review are:

  • Creating a Uno application that targets iOS, Android, MacOS, Windows and Wasm (browsers/web).
  • Add class library, Core, for housing business logic (eg ViewModels)
  • Add class library, UI, for housing pages, controls etc
  • Add Hot Restart to the iOS head project
  • Add target framework switching and solution filters.
  • Refactoring project files to extract Uno references to make it easier to update references

If you want to jump straight to the final project/solution structure, then you can take a look at the final source code on the GitHub repository

Creating Your First Uno Application

For the purposes of this post we’re going to work with a new Uno application. However, there’s no reason why you can’t retrofit any, or all, of these steps to an existing project.

Start by searching for the Uno application template (If you don’t have these templates, then install them from the marketplace).

Give the application a name – We’ll use MultiTargetingWithUno.

Creating Uno Class Libaries

In this example, we’re going to create separate class libraries to house our pages and our ViewModels. But before we get on with creating them, we’re going to do a quick bit of tidying up.

Create PlatformHeads Solution Folder, and move all existing projects into PlatformHeads folder. This will group the head projects (i.e. the application projects that you can deploy and run) so that they don’t have other projects intermingled with them.

The next thing to do is to create two libraries: Core and UI. Search for Uno again but this time pick the Cross Platform Library option.

Next, give your library a name, and then repeat the process for both MultiTargetingWithUno.Core and MultiTargetingWithUno.UI libraries.

Delete the default Class1.cs file from both Core and UI projects, and make sure there is a reference from the UI project to Core.

Add reference to both UI and Core projects to the Shared project (so it gets added to each head project).

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    . . .
    <ItemGroup>
    <ProjectReference Include="..\..\MultiTargetingWithUno.Core\MultiTargetingWithUno.Core.csproj" />
    <ProjectReference Include="..\..\MultiTargetingWithUno.Core\MultiTargetingWithUno.UI.csproj" />
  </ItemGroup>
</Project>

MSBuild.Sdk.Extras

This step is primarily around refactoring the Core and UI projects to make them easier to maintain by relocating various properties so that both projects share the same declarations.

In both Core and UI project files, the sdk references is MSBuild.Sdk.Extras/2.0.54. Change this by removing the specific version number, i.e. 2.0.54. Instead of specifying the version of the package inline, we’re going to specify it in a global.json file located in the solution folder.

Inside the globa.json we just need to include the sdk and its corresponding version number.

{
    "msbuild-sdks": {
        "MSBuild.Sdk.Extras": "2.0.54"
    }
}

Your solution structure should look similar to the following.

Next we’re going to add a new solution item, libraries.targets. This is going to contain most of the properties that are in common across the Core and UI projects. This way if we need to adjust properties, we can do it in one place and have it applied to both libraries.

<Project>
	<PropertyGroup>
		<TargetFrameworks>netstandard2.0;xamarinios10;xamarinmac20;MonoAndroid90;monoandroid10.0;uap10.0.16299</TargetFrameworks>
		<!-- Ensures the .xr.xml files are generated in a proper layout folder -->
		<GenerateLibraryLayout>true</GenerateLibraryLayout>
	</PropertyGroup>
	<PropertyGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
		<DefineConstants>$(DefineConstants);__WASM__</DefineConstants>
	</PropertyGroup>
	<ItemGroup Condition="'$(TargetFramework)'=='xamarinios10' or '$(TargetFramework)'=='MonoAndroid90' or '$(TargetFramework)'=='monoandroid10.0' or '$(TargetFramework)'=='netstandard2.0'">
		<PackageReference Include="Uno.UI" Version="2.4.0" />
	</ItemGroup>
	<ItemGroup>
		<Page Include="**\*.xaml" Exclude="bin\**\*.xaml;obj\**\*.xaml" />
		<Compile Update="**\*.xaml.cs">
			<DependentUpon>%(Filename)</DependentUpon>
		</Compile>
	</ItemGroup>
	<ItemGroup>
		<UpToDateCheckInput Include="**\*.xaml" Exclude="bin\**\*.xaml;obj\**\*.xaml" />
	</ItemGroup>
</Project>

Update the Core csproj file to remove everything inside the Project element, except the Import that references the newly created libraries.targets file.

<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="MSBuild.Sdk.Extras">
	<Import Project="..\libraries.targets" />
</Project>

Repeat this for the UI csproj file. However, you need to keep the project reference to the Core project.

<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="MSBuild.Sdk.Extras">
	<Import Project="..\libraries.targets" />
	<ItemGroup>
		<ProjectReference Include="..\MultiTargetingWithUno.Core\MultiTargetingWithUno.Core.csproj" />
	</ItemGroup>
</Project>

Target Framework Switching and Solution Filtering

As we did in my previous post, we’re going to update the Core and UI projects to allow for easy switching between active target framewrks.

We first need to add Directory.build.props to the solution folder. This is used to setup various project properties that can be used to conditionally set what target frameworks are defined.

<Project>
	<PropertyGroup>
		<IsWasmHeadProject>$(MSBuildProjectName.Contains('.Wasm'))</IsWasmHeadProject>
		<IsAndroidHeadProject>$(MSBuildProjectName.Contains('.Droid'))</IsAndroidHeadProject>
		<IsiOSHeadProject>$(MSBuildProjectName.Contains('.iOS'))</IsiOSHeadProject>
		<IsMacOSHeadProject>$(MSBuildProjectName.Contains('.macOS'))</IsMacOSHeadProject>
		<IsWindowsHeadProject>$(MSBuildProjectName.Contains('.UWP'))</IsWindowsHeadProject>
		<IsLibraryProject>!$(IsWasmHeadProject) and !$(IsAndroidHeadProject) and !$(IsiOSHeadProject) and !$(IsMacOSHeadProject) and !$(IsWindowsHeadProject)</IsLibraryProject>
	</PropertyGroup>

	<PropertyGroup>
		<IsWindows>$(TargetFramework.StartsWith('uap'))</IsWindows>
		<IsAndroid>$(TargetFramework.StartsWith('monoandroid'))</IsAndroid>
		<IsiOS>$(TargetFramework.StartsWith('xamarinios'))</IsiOS>
		<IsMac>$(TargetFramework.StartsWith('xamarinmac'))</IsMac>
		<IsWasm>$(TargetFramework.StartsWith('netstandard'))</IsWasm>
	</PropertyGroup>

	<PropertyGroup>
		<TargetsToBuildDeveloperOverride>All</TargetsToBuildDeveloperOverride>
	</PropertyGroup>

	<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
		<TargetsToBuild>$(TargetsToBuildDeveloperOverride)</TargetsToBuild>
	</PropertyGroup>

	<PropertyGroup Condition=" '$(Configuration)' != 'Debug' ">
		<TargetsToBuild>All</TargetsToBuild>
	</PropertyGroup>
</Project>

Next, update the libraries.targets. The main changes are in bold, where the TargetsToBuild property is used to determine what target frameworks are defined.

<Project>
	<PropertyGroup Condition=" '$(TargetsToBuild)' == 'All' ">
		<TargetFrameworks>netstandard2.0;xamarinios10;xamarinmac20;monoandroid10.0;</TargetFrameworks>
		<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">uap10.0.16299;$(TargetFrameworks)</TargetFrameworks>
	</PropertyGroup>

	<PropertyGroup Condition=" '$(TargetsToBuild)' != 'All' ">
		<TargetFrameworks Condition=" '$(TargetsToBuild)' == 'Android' ">monoandroid10.0;</TargetFrameworks>
		<TargetFrameworks Condition=" '$(TargetsToBuild)' == 'Windows' ">uap10.0.16299</TargetFrameworks>
		<TargetFrameworks Condition=" '$(TargetsToBuild)' == 'iOS' ">xamarinios10</TargetFrameworks>
		<TargetFrameworks Condition=" '$(TargetsToBuild)' == 'Mac' ">xamarinmac20</TargetFrameworks>
		<TargetFrameworks Condition=" '$(TargetsToBuild)' == 'Wasm' ">netstandard2.0</TargetFrameworks>
	</PropertyGroup>

	<ItemGroup>
		<Compile Remove="**\*.netstandard.cs" Condition="'$(IsWASM)' != 'true'"/>
		<Compile Remove="**\*.droid.cs" Condition="'$(IsAndroid)' != 'true'"/>
		<Compile Remove="**\*.mac.cs" Condition="'$(IsMac)' != 'true'" />
		<Compile Remove="**\*.ios.cs" Condition="'$(IsiOS)' != 'true'"/>
		<Compile Remove="**\*.windows.cs" Condition="'$(IsWindows)' != 'true'" />
	</ItemGroup>
	
	<PropertyGroup>
		<GenerateLibraryLayout>true</GenerateLibraryLayout>
	</PropertyGroup>
	<PropertyGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
		<DefineConstants>$(DefineConstants);__WASM__</DefineConstants>
	</PropertyGroup>
	<ItemGroup Condition="'$(TargetFramework)'=='xamarinios10' or '$(TargetFramework)'=='MonoAndroid90' or '$(TargetFramework)'=='monoandroid10.0' or '$(TargetFramework)'=='netstandard2.0'">
		<PackageReference Include="Uno.UI" Version="2.4.0" />
	</ItemGroup>
	<ItemGroup>
		<Page Include="**\*.xaml" Exclude="bin\**\*.xaml;obj\**\*.xaml" />
		<Compile Update="**\*.xaml.cs">
			<DependentUpon>%(Filename)</DependentUpon>
		</Compile>
	</ItemGroup>
	<ItemGroup>
		<UpToDateCheckInput Include="**\*.xaml" Exclude="bin\**\*.xaml;obj\**\*.xaml" />
	</ItemGroup>
</Project>

So that we can see what target framework was built, we’ll add the AppProperties.cs and each platform file (eg AppProperties.android.cs) that we created in the previous post.

In order to switch target frameworks, add the launch batch files and solution filters – see previous post.

MainPage and MainViewModel

Ok, so now we can get on to adding some functionality. We’ll start by creating a ViewModels folder in Core project and then adding a basic class, MainViewModel.

namespace MultiTargetingWithUno.Core.ViewModels
{
    public class MainViewModel
    {
        public string WelcomeText { get; } = "Hello World!";
    }
}

The MainPage already exists, but it’s currently in the Shared project. Move the MainPage to UI project. After moving the MainPage, make sure you double check the csproj file for the UI project – Visual Studio has a nasty habit of adding unnecessary elements to the csproj. In this case, you shouldn’t have any explicit entries for MainPage.xaml or MainPage.xml.cs, since they will both be added to the project by default with the correct build action.

Update MainPage to correct the namespace from MultiTargetingWithUno.MainPage to MultiTargetingWithUno.UI.MainPage. We’ll also update the Text attribute on the TextBlock to use x:Bind to show the welcome message from WelcomeText property on the the ViewModel that’s created in the code behind.

<Page
    x:Class="MultiTargetingWithUno.UI.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock Text="{x:Bind ViewModel.WelcomeText}" Margin="20" FontSize="30" />
    </StackPanel>
</Page>

And the code behind which instantiates the MainViewModel.

using MultiTargetingWithUno.Core.ViewModels;

namespace MultiTargetingWithUno.UI
{
    public sealed partial class MainPage 
    {
        public MainViewModel ViewModel { get; } = new MainViewModel();

        public MainPage()
        {
            InitializeComponent();
        }
    }
}

Update App.xaml.cs in the Shared project to reference the MainPage (you may need to rebuild the solution in order for the intellisense to kick in and help you add the necessary using statements)

Set Windows head project (ie MultiTargetingWithUno.UWP) as the startup project and attempt to build and run the application.

If you see a message in Output window such as “Project not selected to build for this solution configuration”, change the Solution Platform to either x86 or x64 (alternatively you can modify the configuration to include the UWP head project when AnyCPU is selected).

Sometimes building for the first time you may need to force a rebuild of Core, UI and then the UWP head project.

At the time of writing, if you’d followed the steps to this point you’ll actually get an error similar to “error CS0103: The name ‘InitializeComponent’ does not exist in the current context”.

This is because the Uno.UI package hasn’t been added to the Mac target framework. To fix this, change the Condition for ItemGroup in the UI csproj from “‘$(TargetFramework)’==’xamarinios10’ or ‘$(TargetFramework)’==’MonoAndroid90’ or ‘$(TargetFramework)’==’monoandroid10.0’ or ‘$(TargetFramework)’==’netstandard2.0′” to just “‘$(TargetFramework)’!=’uap10.0.16299′” eg.

<ItemGroup Condition="'$(TargetFramework)'!='uap10.0.16299'">
	<PackageReference Include="Uno.UI" Version="2.4.0" />
</ItemGroup>

And then, just when you thought we’d be done, you’ll also see another error, this time when you launch the UWP application:

System.AccessViolationException (0x80004003). Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

This is a nasty error introduced by Microsoft at some point, preventing UWP applications from launching if there’s no pages defined in the application project. The solution is to add a dummy page to the UWP head project, call it “DoNotRemoveOrUsePage.xaml”.

Now you should be able to build and run the application but as you can see, there’s no indication of what platform we’re running on.

Let’s just update the WelcomeText on the MainViewModel to return platform information

public string WelcomeText { get; } = "Hello World! " + AppProperties.AppName;

That’s looking better – at this point you should be able to run the other platforms and see the welcome text vary with each platform.

Project File Refactor

In this section we’re going to do a bit of a tidy up of the Uno package references, extracting them out of the various project files so that they’re easier to manage in a central location.

Firstly we’ll add the logging packages to Directory.build.props, since they’ll be used by the Core, UI and all the head projects.

<ItemGroup>
	<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.1.1" />
	<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.1" />
</ItemGroup>

Next, we’ll create a new file, uno.targets, in the solution folder. This will include the Uno.Core (which all projects will refernece), along with Imports for different platforms. This will seem quite messy, and to be honest it is, but the way this is structured is based on a limitation of the legacy project system used by UWP, iOS, Android and Mac. The legacy project system won’t process conditional ItemGroups. However, they do support conditional Import statements, such as those in the uno.targets.

<Project>
  <ItemGroup>
     <PackageReference Include="Uno.Core" Version="2.0.0" />
  </ItemGroup>

  <Import Project="uno.ui.targets"  Condition="'$(IsWindows)' != 'true' and '$(IsWindowsHeadProject)' != 'true'"/> 

  <Import Project="uno.android.targets"  Condition="'$(IsAndroidHeadProject)' == 'true'"/>
  <Import Project="uno.wasm.targets"  Condition="'$(IsWasmHeadProject)' == 'true'"/>

  <Import Project="uno.debugging.targets"  Condition="'$(Configuration)' == 'Debug'"/> 
</Project>

The uno.targets is imported into the Directory.build.props so that it gets added to each project in the solution.

<Import Project="uno.targets" />

Create the uno.ui.targets file which includes a reference to the Uno.UI package. As you can see from the uno.targets file, the Uno.UI package needs to be imported into every project other than the Windows or Windows head projects

<Project>
	<ItemGroup>
		<PackageReference Include="Uno.UI" Version="2.4.4"  />
	</ItemGroup>
</Project>

Next up is the uno.android.targets, which is used to add the Uno.UniversalimageLoader package to the Android head project.

<Project>
	<ItemGroup>
		<PackageReference Include="Uno.UniversalImageLoader" Version="1.9.32" />
	</ItemGroup>
</Project>

Then, the uno.wasm.targets includes the Bootstrap packages which are required by the Wasm head project:

<Project>
	<ItemGroup>
		<PackageReference Include="Uno.Wasm.Bootstrap" Version="1.2.0" />
		<PackageReference Include="Uno.Wasm.Bootstrap.DevServer" Version="1.2.0" />
	</ItemGroup>
</Project>

Lastly the uno.debugging.targets is only included for the Debug configuration.

<Project>
	<ItemGroup>
		<PackageReference Include="Uno.UI.RemoteControl" Version="2.4.4"  />
	</ItemGroup>
</Project>

With the uno.targets (and its nested imports) added to the Directory.build.props, we can go through an clean up any references to Uno packages throughout the various project files.

  • In libraries.targets, remove ItemGroup with Condition “‘$(TargetFramework)’!=’uap10.0.16299′” that includes a package reference for Uno.UI
  • Remove references to Uno from head projects: Uno.UI, Uno.UI.RemoteControl, Uno.UniversalImageLoader, Uno.Wasm.Bootstrap, Uno.Wasm.Bootstrap.DevServer, Microsoft.Extensions.Logging.Console and Microsoft.Extensions.Logging.Filter

The other piece of refactoring we’ll do is to add a Directory.build.targets file to the solution folder, which defines various helpful properties and constants. These are useful if you ever have to write conditional logic in your code, or tweak debug output etc.

<Project>
	<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
		<DefineConstants>$(DefineConstants);TRACE;DEBUG</DefineConstants>
		<DebugType>full</DebugType>
		<DebugSymbols>true</DebugSymbols>
	</PropertyGroup>
	<PropertyGroup Condition="$(IsWASM)">
		<DefineConstants>$(DefineConstants);NETSTANDARD;PORTABLE;__WASM__</DefineConstants>
	</PropertyGroup>
	<PropertyGroup Condition="$(IsWindows)">
		<DefineConstants>$(DefineConstants);NETFX_CORE;XAML;WINDOWS;WINDOWS_UWP;UWP</DefineConstants>
		<TargetPlatformVersion>10.0.16299.0</TargetPlatformVersion>
		<TargetPlatformMinVersion>10.0.16299.0</TargetPlatformMinVersion>
	</PropertyGroup>
	<PropertyGroup Condition="$(IsiOS)">
		<DefineConstants>$(DefineConstants);MONO;UIKIT;COCOA;APPLE;IOS</DefineConstants>
	</PropertyGroup>
	<PropertyGroup Condition="$(IsMac)">
		<DefineConstants>$(DefineConstants);MONO;COCOA;APPLE;MAC</DefineConstants>
	</PropertyGroup>
	<PropertyGroup Condition="$(IsAndroid)">
		<DefineConstants>$(DefineConstants);MONO;ANDROID</DefineConstants>
		<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
		<AndroidResgenClass>Resource</AndroidResgenClass>
		<AndroidResgenFile>Resources\Resource.designer.cs</AndroidResgenFile>
	</PropertyGroup>
</Project>

iOS Hot Restart

The last feature we’re going to add is support for iOS Hot Restart – the ability to run on an iOS device without needing a Mac! The Uno team blogged about this already but I’ll repeat it here to make things easier.

Add the following to main.cs – this will only be included in a debug build, so you don’t need to worry about it polluting your production code. Hot Restart works by pushing out a pre-built Xamarin.Forms application to the iOS device and then dynamically loading your application into it. This Uno doesn’t use Xamarin.Forms, this extra code is required to piggy-back on the process Microsoft has created for Hot Restart.

#if DEBUG
	public class HotRestartDelegate : Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
	{
		public override bool FinishedLaunching(UIApplication uiApplication, NSDictionary launchOptions)
		{
			Windows.UI.Xaml.Application.Start(_ => new App());
			return base.FinishedLaunching(uiApplication, launchOptions);
		}
	}
#endif

Create uno.debugging.hotrestart.targets that references the Xamarin.Forms package.

<Project>
	<ItemGroup>
		<PackageReference Include="Xamarin.Forms" Version="4.6.0.800" />
	</ItemGroup>
</Project>

Update uno.debugging.targets to conditionally import the uno.debugging.hotrestart.targets for the iOS head project.

<Import Project="uno.debugging.hotrestart.targets" Condition=" '$(IsiOSHeadProject)' == 'true' " />

Launch, Build and Run

There, we’re done and ready to run out application. If you load the MultiTargetingwithUno solution file, you should see all five head projects, along with the shared project, and then two class libraries. In this mode you can pick any head project as the start up project and run that application. However, it will be slow as it will have to build every target framework for the Core and UI libraries.

iOS

If instead, you double-click the Launch.iOS.bat, it will restrict the target framework to just iOS and then launch the ios solution filter. The Solution Explorer only loads the iOS head project and the two class libaries and the build is significantly quicker as it’s only building for iOS.

Android

Next up is Android. Again by running the Launch.Android.bat you only see the Droid head project.

Windows (UWP)

Launch.Windows.bat will load the UWP project and again limit the build to only include the UWP target framework.

Web (WebAssembly / WASM)

The same process applies to WebAssembly (WASM).

MacOS

Finally for MacOS, the bat files won’t work (since they won’t run on Mac). However, you can still manually adjust the TargetsToBuildDeveloperOverride property and set it to Mac.

Solution filters aren’t supported yet by visual Studio for Mac, so you may consider creating a Mac specific solution file, rather than having to load all of the projects.

One really neat feature I noticed as i was debugging on Mac is that the application automatically supports theming – here’s screenshot running in Light and Dark mode. This also works on the other platforms!!

Now you’ve been through it, make sure you check out the raw source code at the GitHub repository